001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.xml.content; 029 030import org.opencms.ade.configuration.CmsConfigurationReader; 031import org.opencms.ade.contenteditor.CmsWidgetUtil; 032import org.opencms.configuration.CmsConfigurationManager; 033import org.opencms.configuration.CmsParameterConfiguration; 034import org.opencms.db.log.CmsLogEntry; 035import org.opencms.file.CmsDataAccessException; 036import org.opencms.file.CmsFile; 037import org.opencms.file.CmsGroup; 038import org.opencms.file.CmsObject; 039import org.opencms.file.CmsProperty; 040import org.opencms.file.CmsPropertyDefinition; 041import org.opencms.file.CmsResource; 042import org.opencms.file.CmsResourceFilter; 043import org.opencms.file.CmsUser; 044import org.opencms.file.CmsVfsResourceNotFoundException; 045import org.opencms.i18n.CmsEncoder; 046import org.opencms.i18n.CmsListResourceBundle; 047import org.opencms.i18n.CmsLocaleManager; 048import org.opencms.i18n.CmsMessageContainer; 049import org.opencms.i18n.CmsMessages; 050import org.opencms.i18n.CmsMultiMessages; 051import org.opencms.i18n.CmsMultiMessages.I_KeyFallbackHandler; 052import org.opencms.i18n.CmsResourceBundleLoader; 053import org.opencms.lock.CmsLock; 054import org.opencms.main.CmsException; 055import org.opencms.main.CmsLog; 056import org.opencms.main.CmsRuntimeException; 057import org.opencms.main.CmsStaticResourceHandler; 058import org.opencms.main.OpenCms; 059import org.opencms.relations.CmsCategory; 060import org.opencms.relations.CmsCategoryService; 061import org.opencms.relations.CmsLink; 062import org.opencms.relations.CmsRelationType; 063import org.opencms.search.fields.CmsGeoCoordinateFieldMapping; 064import org.opencms.search.fields.CmsSearchField; 065import org.opencms.search.fields.CmsSearchFieldMapping; 066import org.opencms.search.fields.CmsSearchFieldMappingType; 067import org.opencms.search.fields.I_CmsSearchFieldMapping; 068import org.opencms.search.galleries.CmsGalleryNameMacroResolver; 069import org.opencms.search.solr.CmsSolrField; 070import org.opencms.security.CmsAccessControlEntry; 071import org.opencms.security.CmsPrincipal; 072import org.opencms.security.CmsRole; 073import org.opencms.security.I_CmsPrincipal; 074import org.opencms.site.CmsSite; 075import org.opencms.util.CmsDefaultSet; 076import org.opencms.util.CmsFileUtil; 077import org.opencms.util.CmsHtmlConverter; 078import org.opencms.util.CmsMacroResolver; 079import org.opencms.util.CmsStringUtil; 080import org.opencms.util.CmsUUID; 081import org.opencms.util.I_CmsMacroResolver; 082import org.opencms.widgets.CmsCategoryWidget; 083import org.opencms.widgets.CmsDisplayWidget; 084import org.opencms.widgets.I_CmsComplexWidget; 085import org.opencms.widgets.I_CmsWidget; 086import org.opencms.workplace.CmsWorkplace; 087import org.opencms.workplace.editors.CmsXmlContentWidgetVisitor; 088import org.opencms.workplace.editors.directedit.I_CmsEditHandler; 089import org.opencms.xml.CmsXmlContentDefinition; 090import org.opencms.xml.CmsXmlEntityResolver; 091import org.opencms.xml.CmsXmlException; 092import org.opencms.xml.CmsXmlGenericWrapper; 093import org.opencms.xml.CmsXmlUtils; 094import org.opencms.xml.containerpage.CmsFormatterBean; 095import org.opencms.xml.containerpage.CmsFormatterConfiguration; 096import org.opencms.xml.containerpage.CmsSchemaFormatterBeanWrapper; 097import org.opencms.xml.containerpage.I_CmsFormatterBean; 098import org.opencms.xml.content.CmsGeoMappingConfiguration.Entry; 099import org.opencms.xml.content.CmsGeoMappingConfiguration.EntryType; 100import org.opencms.xml.content.CmsMappingResolutionContext.AttributeType; 101import org.opencms.xml.types.CmsXmlCategoryValue; 102import org.opencms.xml.types.CmsXmlDisplayFormatterValue; 103import org.opencms.xml.types.CmsXmlDynamicCategoryValue; 104import org.opencms.xml.types.CmsXmlNestedContentDefinition; 105import org.opencms.xml.types.CmsXmlStringValue; 106import org.opencms.xml.types.CmsXmlVarLinkValue; 107import org.opencms.xml.types.CmsXmlVfsFileValue; 108import org.opencms.xml.types.I_CmsXmlContentValue; 109import org.opencms.xml.types.I_CmsXmlContentValue.SearchContentType; 110import org.opencms.xml.types.I_CmsXmlSchemaType; 111import org.opencms.xml.types.I_CmsXmlValidateWithMessage; 112 113import java.io.IOException; 114import java.io.InputStream; 115import java.util.ArrayList; 116import java.util.Arrays; 117import java.util.Collections; 118import java.util.HashMap; 119import java.util.HashSet; 120import java.util.Iterator; 121import java.util.LinkedHashMap; 122import java.util.LinkedHashSet; 123import java.util.List; 124import java.util.Locale; 125import java.util.Map; 126import java.util.Set; 127import java.util.TreeSet; 128import java.util.regex.Pattern; 129import java.util.regex.PatternSyntaxException; 130 131import javax.servlet.ServletRequest; 132 133import org.apache.commons.logging.Log; 134 135import org.antlr.stringtemplate.StringTemplate; 136import org.antlr.stringtemplate.StringTemplateGroup; 137import org.dom4j.Document; 138import org.dom4j.DocumentException; 139import org.dom4j.DocumentHelper; 140import org.dom4j.Element; 141import org.dom4j.Node; 142 143import com.google.common.base.Optional; 144import com.google.common.collect.ArrayListMultimap; 145import com.google.common.collect.Lists; 146import com.google.common.collect.Maps; 147import com.google.common.collect.Multimap; 148 149/** 150 * Default implementation for the XML content handler, will be used by all XML contents that do not 151 * provide their own handler.<p> 152 * 153 * @since 6.0.0 154 */ 155public class CmsDefaultXmlContentHandler implements I_CmsXmlContentHandler, I_CmsXmlContentVisibilityHandler { 156 157 /** 158 * Enum for IfInvalidRelation field setting values. 159 */ 160 public enum InvalidRelationAction { 161 /** Remove the field's parent. */ 162 removeParent, 163 164 /** Only remove the field itself. */ 165 removeSelf 166 } 167 168 /** 169 * Contains the visibility handler configuration for a content field path.<p> 170 */ 171 protected static class VisibilityConfiguration { 172 173 /** The handler instance. */ 174 private I_CmsXmlContentVisibilityHandler m_handler; 175 176 /** The handler configuration parameters. */ 177 private String m_params; 178 179 /** 180 * Constructor.<p> 181 * 182 * @param handler the handler instance 183 * @param params the handler configuration parameteres 184 */ 185 protected VisibilityConfiguration(I_CmsXmlContentVisibilityHandler handler, String params) { 186 187 m_handler = handler; 188 m_params = params; 189 } 190 191 /** 192 * Returns the visibility handler instance.<p> 193 * 194 * @return the handler instance 195 */ 196 public I_CmsXmlContentVisibilityHandler getHandler() { 197 198 return m_handler; 199 } 200 201 /** 202 * Returns the visibility handler configuration parameters.<p> 203 * 204 * @return the configuration parameters 205 */ 206 public String getParams() { 207 208 return m_params; 209 } 210 } 211 212 /** Enum for field setting element names which are not already defined elsewhere. */ 213 enum FieldSettingElems { 214 /** Element name. */ 215 Class, 216 217 /** Element name. */ 218 DefaultResolveMacros, 219 220 /** Element name. */ 221 Display, 222 223 /** Element name. */ 224 FieldVisibility, 225 226 /** Element name. */ 227 IfInvalidRelation, 228 229 /** Element name. */ 230 Invalidate, 231 232 /** Element name. */ 233 Mapping, 234 235 /** Element name. */ 236 MapTo, 237 238 /** Element name. */ 239 NestedFormatter, 240 241 /** Element name. */ 242 Params, 243 244 /** Element name. */ 245 Relation, 246 247 /** Element name. */ 248 Search, 249 250 /** Element name. */ 251 Synchronization, 252 253 /** Element name. */ 254 Type, 255 256 /** Element name. */ 257 UseDefault, 258 259 /** Element name. */ 260 Visibility 261 } 262 263 /** 264 * Callback interface for methods that take an XML element and throw CmsXmlException.<p> 265 */ 266 interface I_Callback { 267 268 /** 269 * Callback method.<p> 270 * 271 * @param elem the parameter element 272 * @throws CmsXmlException for XML errors 273 */ 274 void accept(Element elem) throws CmsXmlException; 275 } 276 277 /** 278 * Bean for holding information about a single mapping. 279 */ 280 private class MappingInfo { 281 282 /** The mapping source. */ 283 private String m_source; 284 285 /** The mapping target. */ 286 private String m_target; 287 288 /** 289 * Creates a new instance. 290 * 291 * @param source the mapping source 292 * @param target the mapping target 293 */ 294 public MappingInfo(String source, String target) { 295 296 super(); 297 m_source = source; 298 m_target = target; 299 } 300 301 /** 302 * Checks if the mapping can be used for reverse mapping of availability data. 303 * 304 * @return true if the mapping can be used for reverse availability mapping 305 */ 306 public boolean canBeUsedForReverseAvailabilityMapping() { 307 308 return exists() && !isMappingUsingDefault(m_source, m_target) && checkIndexesNotSetOrOne(m_source); 309 } 310 311 /** 312 * Checks if the mapping actually exists. 313 * 314 * @return true if the mapping exists 315 */ 316 public boolean exists() { 317 318 return (m_source != null) && (m_target != null); 319 } 320 321 /** 322 * Gets the mapping source. 323 * 324 * @return the mapping source 325 */ 326 public String getSource() { 327 328 return m_source; 329 } 330 331 /** 332 * Gets the mapping target. 333 * 334 * @return the mapping target 335 */ 336 public String getTarget() { 337 338 return m_target; 339 } 340 341 /** 342 * Checks that the components of an xpath have no indexes or index [1]. 343 * 344 * @param xpath the xpath to check 345 * 346 * @return true if all indexes are either [1] or not set 347 */ 348 private boolean checkIndexesNotSetOrOne(String xpath) { 349 350 return CmsXmlUtils.splitXpath(xpath).stream().allMatch( 351 component -> indexesNotSetOrOne.contains(CmsXmlUtils.getXpathIndex(component))); 352 353 } 354 355 } 356 357 /** Attribute name for configuration string. */ 358 public static final String A_CONFIGURATION = "configuration"; 359 360 /** Constant for the "appinfo" element name itself. */ 361 public static final String APPINFO_APPINFO = "appinfo"; 362 363 /** Constant for the "addto" appinfo attribute name. */ 364 public static final String APPINFO_ATTR_ADD_TO = "addto"; 365 366 /** Constant for the "boost" appinfo attribute name. */ 367 public static final String APPINFO_ATTR_BOOST = "boost"; 368 369 /** Constant for the "class" appinfo attribute name. */ 370 public static final String APPINFO_ATTR_CLASS = "class"; 371 372 /** Constant for the "collapse" appinfo attribute name. */ 373 public static final String APPINFO_ATTR_COLLAPSE = "collapse"; 374 375 /** Constant for the "configuration" appinfo attribute name. */ 376 public static final String APPINFO_ATTR_CONFIGURATION = "configuration"; 377 378 /** The exclude from index attribute. */ 379 public static final String APPINFO_ATTR_CONTAINER_PAGE_ONLY = "containerPageOnly"; 380 381 /** Constant for the "copyfields" appinfo attribute name. */ 382 public static final String APPINFO_ATTR_COPY_FIELDS = "copyfields"; 383 384 /** Constant for the "default" appinfo attribute name. */ 385 public static final String APPINFO_ATTR_DEFAULT = "default"; 386 387 /** Constant for the "description" appinfo attribute name. */ 388 public static final String APPINFO_ATTR_DESCRIPTION = "description"; 389 390 /** Constant for the "displaycompact" appinfo attribute name. */ 391 public static final String APPINFO_ATTR_DISPLAY = "display"; 392 393 /** Constant for the "element" appinfo attribute name. */ 394 public static final String APPINFO_ATTR_ELEMENT = "element"; 395 396 /** Constant for the "error" appinfo attribute name. */ 397 public static final String APPINFO_ATTR_ERROR = "error"; 398 399 /** Constant for the "invalidate" appinfo attribute name. */ 400 public static final String APPINFO_ATTR_INVALIDATE = "invalidate"; 401 402 /** Constant for the "key" appinfo attribute name. */ 403 public static final String APPINFO_ATTR_KEY = "key"; 404 405 /** Constant for the "locale" appinfo attribute name. */ 406 public static final String APPINFO_ATTR_LOCALE = "locale"; 407 408 /** Constant for the "mapping" appinfo attribute name. */ 409 public static final String APPINFO_ATTR_MAPPING = "mapping"; 410 411 /** Constant for the "mapto" appinfo attribute name. */ 412 public static final String APPINFO_ATTR_MAPTO = "mapto"; 413 414 /** Constant for the "maxwidth" appinfo attribute name. */ 415 public static final String APPINFO_ATTR_MAXWIDTH = "maxwidth"; 416 417 /** Constant for the "message" appinfo attribute name. */ 418 public static final String APPINFO_ATTR_MESSAGE = "message"; 419 420 /** Constant for the "minwidth" appinfo attribute name. */ 421 public static final String APPINFO_ATTR_MINWIDTH = "minwidth"; 422 423 /** Constant for the "name" appinfo attribute name. */ 424 public static final String APPINFO_ATTR_NAME = "name"; 425 426 /** Constant for the "nice-name" appinfo attribute name. */ 427 public static final String APPINFO_ATTR_NICE_NAME = "nice-name"; 428 429 /** Constant for the "params" appinfo attribute name. */ 430 public static final String APPINFO_ATTR_PARAMS = "params"; 431 432 /** Constant for the "preview" appinfo attribute name. */ 433 public static final String APPINFO_ATTR_PREVIEW = "preview"; 434 435 /** Constant for the "regex" appinfo attribute name. */ 436 public static final String APPINFO_ATTR_REGEX = "regex"; 437 438 /** Constant for the "resolveMacros" attribute name. */ 439 public static final String APPINFO_ATTR_RESOLVE_MACROS = "resolveMacros"; 440 441 /** Constant for the "rule-regex" appinfo attribute name. */ 442 public static final String APPINFO_ATTR_RULE_REGEX = "rule-regex"; 443 444 /** Constant for the "rule-type" appinfo attribute name. */ 445 public static final String APPINFO_ATTR_RULE_TYPE = "rule-type"; 446 447 /** Constant for the "scope" appinfo attribute name. */ 448 public static final String APPINFO_ATTR_SCOPE = "scope"; 449 450 /** Constant for the "searchcontent" appinfo attribute name. */ 451 public static final String APPINFO_ATTR_SEARCHCONTENT = "searchcontent"; 452 453 /** Constant for the "select-inherit" appinfo attribute name. */ 454 public static final String APPINFO_ATTR_SELECT_INHERIT = "select-inherit"; 455 456 /** Constant for the "sourcefield" appinfo attribute name. */ 457 public static final String APPINFO_ATTR_SOURCE_FIELD = "sourcefield"; 458 459 /** Constant for the "targetfield" appinfo attribute name. */ 460 public static final String APPINFO_ATTR_TARGET_FIELD = "targetfield"; 461 462 /** Constant for the "type" appinfo attribute name. */ 463 public static final String APPINFO_ATTR_TYPE = "type"; 464 465 /** Constant for the "node" appinfo attribute value. */ 466 public static final String APPINFO_ATTR_TYPE_NODE = "node"; 467 468 /** Constant for the "parent" appinfo attribute value. */ 469 public static final String APPINFO_ATTR_TYPE_PARENT = "parent"; 470 471 /** Constant for the "warning" appinfo attribute value. */ 472 public static final String APPINFO_ATTR_TYPE_WARNING = "warning"; 473 474 /** Constant for the "uri" appinfo attribute name. */ 475 public static final String APPINFO_ATTR_URI = "uri"; 476 477 /** Constant for the "useall" appinfo attribute name. */ 478 public static final String APPINFO_ATTR_USEALL = "useall"; 479 480 /** Constant for the "value" appinfo attribute name. */ 481 public static final String APPINFO_ATTR_VALUE = "value"; 482 483 /** Constant for the "widget" appinfo attribute name. */ 484 public static final String APPINFO_ATTR_WIDGET = "widget"; 485 486 /** Constant for the "widget-config" appinfo attribute name. */ 487 public static final String APPINFO_ATTR_WIDGET_CONFIG = "widget-config"; 488 489 /** Constant for formatter include resource type 'CSS'. */ 490 public static final String APPINFO_ATTRIBUTE_TYPE_CSS = "css"; 491 492 /** Constant for formatter include resource type 'JAVASCRIPT'. */ 493 public static final String APPINFO_ATTRIBUTE_TYPE_JAVASCRIPT = "javascript"; 494 495 /** Constant for the "bundle" appinfo element name. */ 496 public static final String APPINFO_BUNDLE = "bundle"; 497 498 /** Constant for the "default" appinfo element name. */ 499 public static final String APPINFO_DEFAULT = "default"; 500 501 /** Constant for the "defaults" appinfo element name. */ 502 public static final String APPINFO_DEFAULTS = "defaults"; 503 504 /** Constant for the "edithandler" appinfo element name. */ 505 public static final String APPINFO_EDIT_HANDLER = "edithandler"; 506 507 /** Constant for the "editorchangehandler" appinfo element name. */ 508 public static final String APPINFO_EDITOR_CHANGE_HANDLER = "editorchangehandler"; 509 510 /** Constant for the "editorchangehandlers" appinfo element name. */ 511 public static final String APPINFO_EDITOR_CHANGE_HANDLERS = "editorchangehandlers"; 512 513 /** Constant for the "forbidden-contexts" appinfo attribute name. */ 514 public static final String APPINFO_FORBIDDEN_CONTEXTS = "forbidden-contexts"; 515 516 /** Constant for the "formatter" appinfo element name. */ 517 public static final String APPINFO_FORMATTER = "formatter"; 518 519 /** Constant for the "formatters" appinfo element name. */ 520 public static final String APPINFO_FORMATTERS = "formatters"; 521 522 /** Constant for the 'geomapping' node. */ 523 public static final String APPINFO_GEOMAPPING = "geomapping"; 524 525 /** Constant for the "headinclude" appinfo element name. */ 526 public static final String APPINFO_HEAD_INCLUDE = "headinclude"; 527 528 /** Constant for the "headincludes" appinfo element name. */ 529 public static final String APPINFO_HEAD_INCLUDES = "headincludes"; 530 531 /** Constant for the "layout" appinfo element name. */ 532 public static final String APPINFO_LAYOUT = "layout"; 533 534 /** Constant for the "layouts" appinfo element name. */ 535 public static final String APPINFO_LAYOUTS = "layouts"; 536 537 /** Constant for the "mapping" appinfo element name. */ 538 public static final String APPINFO_MAPPING = "mapping"; 539 540 /** Constant for the "mappings" appinfo element name. */ 541 public static final String APPINFO_MAPPINGS = "mappings"; 542 543 /** Constant for the 'messagekeyhandler' node. */ 544 public static final String APPINFO_MESSAGEKEYHANDLER = "messagekeyhandler"; 545 546 /** Constant for the "modelfolder" appinfo element name. */ 547 public static final String APPINFO_MODELFOLDER = "modelfolder"; 548 549 /** Constant for the "nestedformatter" appinfo element name. */ 550 public static final String APPINFO_NESTED_FORMATTER = "nestedformatter"; 551 552 /** Constant for the "nestedformatters" appinfo element name. */ 553 public static final String APPINFO_NESTED_FORMATTERS = "nestedformatters"; 554 555 /** Constant for the "param" appinfo attribute name. */ 556 public static final String APPINFO_PARAM = "param"; 557 558 /** Constant for the "parameters" appinfo element name. */ 559 public static final String APPINFO_PARAMETERS = "parameters"; 560 561 /** version-transformation node name. */ 562 public static final String APPINFO_VERSION_TRANSFORMATION = "versiontransformation"; 563 564 /** Constant for the "preview" appinfo element name. */ 565 public static final String APPINFO_PREVIEW = "preview"; 566 567 /** Constant for the "propertybundle" appinfo element name. */ 568 public static final String APPINFO_PROPERTYBUNDLE = "propertybundle"; 569 570 /** Constant for the "relation" appinfo element name. */ 571 public static final String APPINFO_RELATION = "relation"; 572 573 /** Constant for the "relations" appinfo element name. */ 574 public static final String APPINFO_RELATIONS = "relations"; 575 576 /** Constant for the "resource" appinfo element name. */ 577 public static final String APPINFO_RESOURCE = "resource"; 578 579 /** Constant for the "resourcebundle" appinfo element name. */ 580 public static final String APPINFO_RESOURCEBUNDLE = "resourcebundle"; 581 582 /** Constant for the "resourcebundles" appinfo element name. */ 583 public static final String APPINFO_RESOURCEBUNDLES = "resourcebundles"; 584 585 /** Constant for the reverse-mapping-enabled appinfo element name. */ 586 public static final String APPINFO_REVERSE_MAPPING_ENABLED = "reverse-mapping-enabled"; 587 588 /** Constant for the "rule" appinfo element name. */ 589 public static final String APPINFO_RULE = "rule"; 590 591 /** The file where the default appinfo schema is located. */ 592 public static final String APPINFO_SCHEMA_FILE = "org/opencms/xml/content/DefaultAppinfo.xsd"; 593 594 /** The file where the default appinfo schema types are located. */ 595 public static final String APPINFO_SCHEMA_FILE_TYPES = "org/opencms/xml/content/DefaultAppinfoTypes.xsd"; 596 597 /** The XML system id for the default appinfo schema types. */ 598 public static final String APPINFO_SCHEMA_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX 599 + APPINFO_SCHEMA_FILE; 600 601 /** The XML system id for the default appinfo schema types. */ 602 public static final String APPINFO_SCHEMA_TYPES_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX 603 + APPINFO_SCHEMA_FILE_TYPES; 604 605 /** Constant for the "searchsetting" appinfo element name. */ 606 public static final String APPINFO_SEARCHSETTING = "searchsetting"; 607 608 /** Constant for the "searchsettings" appinfo element name. */ 609 public static final String APPINFO_SEARCHSETTINGS = "searchsettings"; 610 611 /** Constant for the "setting" appinfo element name. */ 612 public static final String APPINFO_SETTING = "setting"; 613 614 /** Constant for the "settings" appinfo element name. */ 615 public static final String APPINFO_SETTINGS = "settings"; 616 617 /** Constant for the "solrfield" appinfo element name. */ 618 public static final String APPINFO_SOLR_FIELD = "solrfield"; 619 620 /** Constant for the "synchronization" appinfo element name. */ 621 public static final String APPINFO_SYNCHRONIZATION = "synchronization"; 622 623 /** Constant for the "synchronizations" appinfo element name. */ 624 public static final String APPINFO_SYNCHRONIZATIONS = "synchronizations"; 625 626 /** Constant for the "tab" appinfo element name. */ 627 public static final String APPINFO_TAB = "tab"; 628 629 /** Constant for the "tabs" appinfo element name. */ 630 public static final String APPINFO_TABS = "tabs"; 631 632 /** Node name. */ 633 public static final String APPINFO_TEMPLATE = "template"; 634 635 /** Node name. */ 636 public static final String APPINFO_TEMPLATES = "templates"; 637 638 /** Constant for the "validationrule" appinfo element name. */ 639 public static final String APPINFO_VALIDATIONRULE = "validationrule"; 640 641 /** Constant for the "validationrules" appinfo element name. */ 642 public static final String APPINFO_VALIDATIONRULES = "validationrules"; 643 644 /** Constant for the "element" value of the appinfo attribute "addto". */ 645 public static final String APPINFO_VALUE_ADD_TO_CONTENT = "element"; 646 647 /** Constant for the "page" value of the appinfo attribute "addto". */ 648 public static final String APPINFO_VALUE_ADD_TO_PAGE = "page"; 649 650 /** Constant for the "visibilities" appinfo element name. */ 651 public static final String APPINFO_VISIBILITIES = "visibilities"; 652 653 /** Constant for the "visibility" appinfo element name. */ 654 public static final String APPINFO_VISIBILITY = "visibility"; 655 656 /** Constant for the "xmlbundle" appinfo element name. */ 657 public static final String APPINFO_XMLBUNDLE = "xmlbundle"; 658 659 /** Attribute name. */ 660 public static final String ATTR_ENABLED = "enabled"; 661 662 /** Attribute name. */ 663 public static final String ATTR_ENABLED_BY_DEFAULT = "enabledByDefault"; 664 665 /** Attribute name. */ 666 public static final String ATTR_USE_ACACIA = "useAcacia"; 667 668 /** Constant for head include type attribute: CSS. */ 669 public static final String ATTRIBUTE_INCLUDE_TYPE_CSS = "css"; 670 671 /** Constant for head include type attribute: java-script. */ 672 public static final String ATTRIBUTE_INCLUDE_TYPE_JAVASCRIPT = "javascript"; 673 674 /** Field for mapping geo-coordinates. */ 675 public static final String GEOMAPPING_FIELD = "geocoords_loc"; 676 677 /** Macro for resolving the preview URI. */ 678 public static final String MACRO_PREVIEW_TEMPFILE = "previewtempfile"; 679 680 /** Node name for change handler. */ 681 public static final String N_CHANGEHANDLER = "ChangeHandler"; 682 683 /** Constant for the 'Setting' node name. */ 684 public static final String N_SETTING = "Setting"; 685 686 /** Default message for validation errors. */ 687 protected static final String MESSAGE_VALIDATION_DEFAULT_ERROR = "${validation.path}: " 688 + "${key." 689 + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_ERROR_2 690 + "|${validation.value}|[${validation.regex}]}"; 691 692 /** Default message for validation warnings. */ 693 protected static final String MESSAGE_VALIDATION_DEFAULT_WARNING = "${validation.path}: " 694 + "${key." 695 + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_WARNING_2 696 + "|${validation.value}|[${validation.regex}]}"; 697 698 /** Set of xpath indexes that allow reverse availability mappings. */ 699 static Set<String> indexesNotSetOrOne = new HashSet<>(Arrays.asList("", "[1]")); 700 701 /** The attribute name for the "prefer folder" option for properties. */ 702 private static final String APPINFO_ATTR_PREFERFOLDER = "PreferFolder"; 703 704 /** The 'useDefault' attribute name. */ 705 private static final String APPINFO_ATTR_USE_DEFAULT = "useDefault"; 706 707 /** The node name for the default complex widget configuration. */ 708 private static final Object APPINFO_DEFAULTWIDGET = "defaultwidget"; 709 710 /** Node name for the list of field declarations. */ 711 private static final Object APPINFO_FIELD_SETTINGS = "FieldSettings"; 712 713 /** JSON renderer node name. */ 714 private static final Object APPINFO_JSON_RENDERER = "jsonrenderer"; 715 716 /** Attribute name for the context used for resolving content mappings. */ 717 private static final String ATTR_MAPPING_RESOLUTION_CONTEXT = "MAPPING_RESOLUTION_CONTEXT"; 718 719 /** The log object for this class. */ 720 private static final Log LOG = CmsLog.getLog(CmsDefaultXmlContentHandler.class); 721 722 /** The principal list separator. */ 723 private static final String PRINCIPAL_LIST_SEPARATOR = ","; 724 725 /** The title property individual mapping key. */ 726 private static final String TITLE_PROPERTY_INDIVIDUAL_MAPPING = MAPTO_PROPERTY_INDIVIDUAL 727 + CmsPropertyDefinition.PROPERTY_TITLE; 728 729 /** The title property mapping key. */ 730 private static final String TITLE_PROPERTY_MAPPING = MAPTO_PROPERTY + CmsPropertyDefinition.PROPERTY_TITLE; 731 732 /** The title property shared mapping key. */ 733 private static final String TITLE_PROPERTY_SHARED_MAPPING = MAPTO_PROPERTY_SHARED 734 + CmsPropertyDefinition.PROPERTY_TITLE; 735 736 /** 737 * Static initializer for caching the default appinfo validation schema.<p> 738 */ 739 static { 740 741 // the schema definition is located in 2 separates file for easier editing 742 // 2 files are required in case an extended schema want to use the default definitions, 743 // but with an extended "appinfo" node 744 byte[] appinfoSchemaTypes; 745 try { 746 // first read the default types 747 appinfoSchemaTypes = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE_TYPES); 748 } catch (Exception e) { 749 throw new CmsRuntimeException( 750 Messages.get().container( 751 org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1, 752 APPINFO_SCHEMA_FILE_TYPES), 753 e); 754 } 755 CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_TYPES_SYSTEM_ID, appinfoSchemaTypes); 756 byte[] appinfoSchema; 757 try { 758 // now read the default base schema 759 appinfoSchema = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE); 760 } catch (Exception e) { 761 throw new CmsRuntimeException( 762 Messages.get().container( 763 org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1, 764 APPINFO_SCHEMA_FILE), 765 e); 766 } 767 CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_SYSTEM_ID, appinfoSchema); 768 } 769 770 /** The set of allowed templates. */ 771 protected CmsDefaultSet<String> m_allowedTemplates = new CmsDefaultSet<String>(); 772 773 /** The configuration values for the element widgets (as defined in the annotations). */ 774 protected Map<String, String> m_configurationValues; 775 776 /** The CSS resources to include into the html-page head. */ 777 protected Set<String> m_cssHeadIncludes; 778 779 /** The default values for the elements (as defined in the annotations). */ 780 protected Map<String, String> m_defaultValues; 781 782 /** The element mappings (as defined in the annotations). */ 783 protected Map<String, List<String>> m_elementMappings; 784 785 /** The formatter configuration. */ 786 protected CmsFormatterConfiguration m_formatterConfiguration; 787 788 /** The list of formatters from the XSD. */ 789 protected List<CmsFormatterBean> m_formatters; 790 791 /** The configured geo-coordinate mapping configuration entries. */ 792 protected List<CmsGeoMappingConfiguration.Entry> m_geomappingEntries = new ArrayList<>(); 793 794 /** Relation actions. */ 795 protected Map<String, InvalidRelationAction> m_invalidRelationActions = new HashMap<>(); 796 797 /** The java-script resources to include into the html-page head. */ 798 protected Set<String> m_jsHeadIncludes; 799 800 /** The resource bundle name to be used for localization of this content handler. */ 801 protected List<String> m_messageBundleNames; 802 803 /** The folder containing the model file(s) for the content. */ 804 protected String m_modelFolder; 805 806 /** The preview location (as defined in the annotations). */ 807 protected String m_previewLocation; 808 809 /** Name of the field used for geo-coordinate mapping. */ 810 protected String m_primaryGeomappingField; 811 812 /** The relation check rules. */ 813 protected Map<String, Boolean> m_relationChecks; 814 815 /** The relation check rules. */ 816 protected Map<String, CmsRelationType> m_relations; 817 818 /** The Solr field configurations. */ 819 protected Map<String, CmsSearchField> m_searchFields; 820 821 /** The Solr field configurations added to the container pages contents are on. */ 822 protected Map<String, CmsSearchField> m_searchFieldsPage; 823 824 /** The search settings. */ 825 protected Map<String, SearchContentType> m_searchSettings; 826 827 /** String template group for the simple search setting expansions. */ 828 protected StringTemplateGroup m_searchTemplateGroup; 829 830 /** The configured settings for the formatters (as defined in the annotations). */ 831 protected Map<String, CmsXmlContentProperty> m_settings; 832 833 /** Path to XSL transform in VFS to use for version transformation. */ 834 protected String m_versionTransformation; 835 836 /** The configured locale synchronization elements. */ 837 protected List<String> m_synchronizations; 838 839 /** The configured tabs. */ 840 protected List<CmsXmlContentTab> m_tabs; 841 842 /** The list of mappings to the "Title" property. */ 843 protected List<String> m_titleMappings; 844 845 /** Flag which controls whether the Acacia editor should be disabled for this type. */ 846 protected boolean m_useAcacia = true; 847 848 /** The messages for the error validation rules. */ 849 protected Map<String, String> m_validationErrorMessages; 850 851 /** The validation rules that cause an error (as defined in the annotations). */ 852 protected Map<String, String> m_validationErrorRules; 853 854 /** The messages for the warning validation rules. */ 855 protected Map<String, String> m_validationWarningMessages; 856 857 /** The validation rules that cause a warning (as defined in the annotations). */ 858 protected Map<String, String> m_validationWarningRules; 859 860 /** Change handler configurations. */ 861 private List<CmsChangeHandlerConfig> m_changeHandlerConfigs = new ArrayList<>(); 862 863 /** The container page only flag, indicating if this XML content should be indexed on container pages only. */ 864 private boolean m_containerPageOnly; 865 866 /** The content definition for which this content handler is configured. */ 867 private CmsXmlContentDefinition m_contentDefinition; 868 869 /** The default complex widget class name. */ 870 private String m_defaultWidget; 871 872 /** The default complex widget configuration. */ 873 private String m_defaultWidgetConfig; 874 875 /** The default complex widget for this type. */ 876 private I_CmsComplexWidget m_defaultWidgetInstance; 877 878 /** The elements to display in ncompact view. */ 879 private HashMap<String, DisplayType> m_displayTypes; 880 881 /** An optional edit handler. */ 882 private I_CmsEditHandler m_editHandler; 883 884 /** The editor change handlers. */ 885 private List<I_CmsXmlContentEditorChangeHandler> m_editorChangeHandlers; 886 887 /** The descriptions for the fields. */ 888 private Map<String, String> m_fieldDescriptions = new HashMap<>(); 889 890 /** The nice names for the fields. */ 891 private Map<String, String> m_fieldNiceNames = new HashMap<>(); 892 893 /** Cached boolean indicating whether the content has category widgets. */ 894 private volatile Boolean m_hasCategoryWidget; 895 896 /** The JSON renderer settings. */ 897 private JsonRendererSettings m_jsonRendererSettings; 898 899 /** A set of keys identifying the mappings which should use default values if the corresponding values are not set in the XML content. */ 900 private Set<String> m_mappingsUsingDefault = new HashSet<String>(); 901 902 /** Message key fallback handler for the editor. */ 903 private CmsMultiMessages.I_KeyFallbackHandler m_messageKeyHandler = new CmsMultiMessages.I_KeyFallbackHandler() { 904 905 public Optional<String> getFallbackKey(String key) { 906 907 return Optional.absent(); 908 } 909 }; 910 911 /** The nested formatter elements. */ 912 private Set<String> m_nestedFormatterElements; 913 914 /** The paths of values for which no macros should be resolved when getting the default value. */ 915 private Set<String> m_nonMacroResolvableDefaults = new HashSet<String>(); 916 917 /** The parameters. */ 918 private CmsParameterConfiguration m_parameters = new CmsParameterConfiguration(); 919 920 /** Option to disable reverse mapping for this content type. */ 921 private boolean m_reverseMappingEnabled = true; 922 923 /** The visibility configurations by element path. */ 924 private Map<String, VisibilityConfiguration> m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>(); 925 926 /** The map of widget names by path. */ 927 private Map<String, String> m_widgetNames = new HashMap<>(); 928 929 /** 930 * Creates a new instance of the default XML content handler.<p> 931 */ 932 public CmsDefaultXmlContentHandler() { 933 934 init(); 935 } 936 937 /** 938 * Collects change handler confiugrations for all nested contents. 939 * 940 * @param contentDef the content definition 941 * @param parentPath the parent path 942 * @param result the multimap to collect the handler configurations in, with the key being the path of the nested content in whose schema they are configured 943 */ 944 private static void collectNestedChangeHandlerConfigs( 945 CmsXmlContentDefinition contentDef, 946 String parentPath, 947 Multimap<String, CmsChangeHandlerConfig> result) { 948 949 I_CmsXmlContentHandler handler = contentDef.getContentHandler(); 950 List<CmsChangeHandlerConfig> handlerConfigs = handler.getChangeHandlerConfigs(); 951 for (CmsChangeHandlerConfig handlerConfig : handlerConfigs) { 952 result.put(parentPath, handlerConfig); 953 } 954 955 for (I_CmsXmlSchemaType schemaType : contentDef.getTypeSequence()) { 956 String name = schemaType.getName(); 957 if (schemaType instanceof CmsXmlNestedContentDefinition) { 958 CmsXmlNestedContentDefinition nested = (CmsXmlNestedContentDefinition)schemaType; 959 collectNestedChangeHandlerConfigs(nested.getNestedContentDefinition(), parentPath + "/" + name, result); 960 } 961 } 962 } 963 964 /** 965 * Gets the invalid relation action for the given value. 966 * @param value the value 967 * @return the invalid relation action 968 */ 969 private static InvalidRelationAction getInvalidRelationActionForValue(I_CmsXmlContentValue value) { 970 971 try { 972 String path = value.getPath(); 973 String simpleName = CmsXmlUtils.getLastXpathElement(path); 974 return value.getContentDefinition().getContentHandler().getInvalidRelationAction(simpleName); 975 } catch (Exception e) { 976 LOG.error(e.getLocalizedMessage(), e); 977 return null; 978 } 979 } 980 981 /** 982 * Makes a path suitable for use as a change handler scope by appending wildcards to every path segment. 983 * 984 * @param path the path to process 985 * @return the scope for the editor change handler 986 */ 987 private static String normalizeChangeHandlerScope(String path) { 988 989 List<String> normalizedKeyParts = new ArrayList<>(); 990 // Append wildcard to every path component that doesn't end with a wildcard or index 991 for (String keyPart : path.split("/")) { 992 String normalizedKeyPart = null; 993 if (keyPart.endsWith("*") || keyPart.endsWith("]")) { 994 normalizedKeyPart = keyPart; 995 } else { 996 normalizedKeyPart = keyPart + "*"; 997 } 998 normalizedKeyParts.add(normalizedKeyPart); 999 } 1000 String normalizedKey = CmsStringUtil.listAsString(normalizedKeyParts, "/"); 1001 return normalizedKey; 1002 1003 } 1004 1005 /** 1006 * @see org.opencms.xml.content.I_CmsXmlContentHandler#applyReverseAvailabilityMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.content.CmsMappingResolutionContext.AttributeType, java.util.List, long) 1007 */ 1008 public boolean applyReverseAvailabilityMapping( 1009 CmsObject cms, 1010 CmsXmlContent content, 1011 AttributeType attr, 1012 List<Locale> resourceLocales, 1013 long valueToSet) { 1014 1015 MappingInfo info = getAttributeMapping(attr); 1016 if (!info.canBeUsedForReverseAvailabilityMapping()) { 1017 return false; 1018 } 1019 1020 long defaultValue = -1; 1021 switch (attr) { 1022 case expiration: 1023 defaultValue = CmsResource.DATE_EXPIRED_DEFAULT; 1024 break; 1025 case release: 1026 defaultValue = CmsResource.DATE_RELEASED_DEFAULT; 1027 break; 1028 default: 1029 return false; 1030 } 1031 String mappedElement = info.getSource(); 1032 1033 I_CmsXmlSchemaType type = m_contentDefinition.getSchemaType(mappedElement); 1034 // change value in the first locale from the list of resource locales that we have in the content 1035 List<Locale> localesToProcess = Collections.emptyList(); 1036 for (Locale locale : resourceLocales) { 1037 if (content.hasLocale(locale)) { 1038 localesToProcess = Collections.singletonList(locale); 1039 break; 1040 } 1041 } 1042 if (localesToProcess.size() > 0) { 1043 Locale locale = localesToProcess.get(0); 1044 if (content.hasValue(mappedElement, locale)) { 1045 I_CmsXmlContentValue value = content.getValue(mappedElement, locale); 1046 String stringValue = value.getStringValue(cms); 1047 if (stringValue.contains("%")) { 1048 LOG.debug( 1049 content.getFile().getRootPath() 1050 + ": Didn't apply reverse availability mapping because of macro value " 1051 + stringValue); 1052 return false; 1053 } 1054 if (valueToSet == defaultValue) { 1055 if (type.getMinOccurs() == 0) { 1056 content.removeValue(mappedElement, locale, 0); 1057 } else if (type instanceof CmsXmlStringValue) { 1058 content.getValue(mappedElement, locale).setStringValue(cms, ""); 1059 } else { 1060 LOG.warn( 1061 content.getFile().getRootPath() 1062 + ": Could not apply reverse availability mapping because the field " 1063 + mappedElement 1064 + " is neither optional nor of type OpenCmsString."); 1065 } 1066 } else { 1067 content.getValue(mappedElement, locale).setStringValue(cms, "" + valueToSet); 1068 } 1069 } else if (valueToSet != defaultValue) { 1070 Set<String> parentSet = new HashSet<>(); 1071 String currentPath = mappedElement; 1072 while (!parentSet.contains(currentPath)) { 1073 parentSet.add(currentPath); 1074 currentPath = CmsXmlUtils.removeLastXpathElement(currentPath); 1075 } 1076 List<String> sortedParents = new ArrayList<>(parentSet); 1077 Collections.sort(sortedParents); 1078 for (String parent : sortedParents) { 1079 if (!content.hasValue(parent, locale)) { 1080 content.addValue(cms, parent, locale, 0); 1081 } 1082 } 1083 content.getValue(mappedElement, locale).setStringValue(cms, "" + valueToSet); 1084 } 1085 } 1086 return true; 1087 } 1088 1089 /** 1090 * @see org.opencms.xml.content.I_CmsXmlContentHandler#canUseReverseAvailabilityMapping(org.opencms.xml.content.CmsMappingResolutionContext.AttributeType) 1091 */ 1092 public boolean canUseReverseAvailabilityMapping(AttributeType attr) { 1093 1094 if (!m_reverseMappingEnabled) { 1095 return false; 1096 } 1097 1098 MappingInfo info = getAttributeMapping(attr); 1099 return info.canBeUsedForReverseAvailabilityMapping(); 1100 1101 } 1102 1103 /** 1104 * Copies a given CMS context and set the copy's site root to '/'.<p> 1105 * 1106 * @param cms the CMS context to copy 1107 * @return the copy 1108 * 1109 * @throws CmsException if something goes wrong 1110 */ 1111 public CmsObject createRootCms(CmsObject cms) throws CmsException { 1112 1113 CmsObject rootCms = OpenCms.initCmsObject(cms); 1114 Object logEntry = cms.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY); 1115 if (logEntry != null) { 1116 rootCms.getRequestContext().setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, logEntry); 1117 } 1118 rootCms.getRequestContext().setSiteRoot("/"); 1119 return rootCms; 1120 } 1121 1122 /** 1123 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getAllowedTemplates() 1124 */ 1125 public CmsDefaultSet<String> getAllowedTemplates() { 1126 1127 return m_allowedTemplates; 1128 1129 } 1130 1131 /** 1132 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getChangeHandlerConfigs() 1133 */ 1134 public List<CmsChangeHandlerConfig> getChangeHandlerConfigs() { 1135 1136 return Collections.unmodifiableList(m_changeHandlerConfigs); 1137 } 1138 1139 /** 1140 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getComplexWidget(org.opencms.file.CmsObject, java.lang.String) 1141 */ 1142 public I_CmsComplexWidget getComplexWidget(CmsObject cms, String path) { 1143 1144 String widgetName = m_widgetNames.get(path); 1145 if (widgetName == null) { 1146 return null; 1147 } 1148 if (cms != null) { 1149 CmsMacroResolver resolver = new CmsMacroResolver(); 1150 resolver.setCmsObject(cms); 1151 widgetName = resolver.resolveMacros(widgetName); 1152 } 1153 if (CmsStringUtil.isValidJavaClassName(widgetName)) { 1154 try { 1155 Class<?> cls = Class.forName(widgetName, false, getClass().getClassLoader()); 1156 if (I_CmsComplexWidget.class.isAssignableFrom(cls)) { 1157 return (I_CmsComplexWidget)(cls.newInstance()); 1158 } 1159 } catch (Exception e) { 1160 LOG.warn(e.getLocalizedMessage(), e); 1161 return null; 1162 } 1163 } 1164 return null; 1165 1166 } 1167 1168 /** 1169 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType) 1170 */ 1171 public String getConfiguration(I_CmsXmlSchemaType type) { 1172 1173 String elementName = type.getName(); 1174 return m_configurationValues.get(elementName); 1175 1176 } 1177 1178 /** 1179 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType) 1180 */ 1181 public String getConfiguration(String path) { 1182 1183 return m_configurationValues.get(path); 1184 1185 } 1186 1187 /** 1188 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguredDisplayType(java.lang.String, org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType) 1189 */ 1190 public DisplayType getConfiguredDisplayType(String path, DisplayType defaultValue) { 1191 1192 DisplayType result = m_displayTypes.get(path); 1193 if (result == null) { 1194 result = defaultValue; 1195 } 1196 return result; 1197 1198 } 1199 1200 /** 1201 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getCSSHeadIncludes() 1202 */ 1203 public Set<String> getCSSHeadIncludes() { 1204 1205 return Collections.unmodifiableSet(m_cssHeadIncludes); 1206 } 1207 1208 /*** 1209 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getCSSHeadIncludes(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 1210 */ 1211 @SuppressWarnings("unused") 1212 public Set<String> getCSSHeadIncludes(CmsObject cms, CmsResource resource) throws CmsException { 1213 1214 return getCSSHeadIncludes(); 1215 } 1216 1217 /** 1218 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, java.util.Locale) 1219 */ 1220 public String getDefault(CmsObject cms, CmsResource resource, I_CmsXmlSchemaType type, String path, Locale locale) { 1221 1222 String defaultValue; 1223 if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) { 1224 // ( path can be empty if this is called from createValue ) 1225 // use the "getDefault" method of the given value, will use value from standard XML schema 1226 defaultValue = type.getDefault(locale); 1227 } else { 1228 // look up the default from the configured mappings 1229 defaultValue = m_defaultValues.get(path); 1230 if (defaultValue == null) { 1231 // no value found, try default xpath 1232 path = CmsXmlUtils.removeXpath(path); 1233 path = CmsXmlUtils.createXpath(path, 1); 1234 // look up the default value again with default index of 1 in all path elements 1235 defaultValue = m_defaultValues.get(path); 1236 } 1237 } 1238 if (defaultValue != null) { 1239 CmsObject newCms = cms; 1240 if (resource != null) { 1241 try { 1242 // switch the current URI to the XML document resource so that properties can be read 1243 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(resource.getRootPath()); 1244 if (site != null) { 1245 newCms = OpenCms.initCmsObject(cms); 1246 newCms.getRequestContext().setSiteRoot(site.getSiteRoot()); 1247 newCms.getRequestContext().setUri(newCms.getSitePath(resource)); 1248 } 1249 } catch (Exception e) { 1250 // on any error just use the default input OpenCms context 1251 } 1252 } 1253 // return the default value with processed macros 1254 String result = defaultValue; 1255 if (!m_nonMacroResolvableDefaults.contains(path)) { 1256 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(newCms).setMessages( 1257 getMessages(locale)); 1258 result = resolver.resolveMacros(defaultValue); 1259 } 1260 return result; 1261 } else if (!CmsStringUtil.isEmptyOrWhitespaceOnly(path) && CmsXmlUtils.isDeepXpath(path)) { 1262 1263 // try to delegate to content handler of nested content 1264 1265 String subPath = CmsXmlUtils.removeFirstXpathElement(path); 1266 I_CmsXmlSchemaType nestedType = m_contentDefinition.getSchemaType( 1267 CmsXmlUtils.removeXpath(CmsXmlUtils.getFirstXpathElement(path))); 1268 if (nestedType instanceof CmsXmlNestedContentDefinition) { 1269 CmsXmlContentDefinition nestedDef = ((CmsXmlNestedContentDefinition)nestedType).getNestedContentDefinition(); 1270 if (nestedDef != null) { 1271 I_CmsXmlContentHandler subHandler = nestedDef.getContentHandler(); 1272 if (subHandler != null) { 1273 return subHandler.getDefault(cms, resource, nestedType, subPath, locale); 1274 } 1275 } 1276 } 1277 } 1278 // no default value is available 1279 return null; 1280 } 1281 1282 /** 1283 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, I_CmsXmlContentValue, java.util.Locale) 1284 */ 1285 public String getDefault(CmsObject cms, I_CmsXmlContentValue value, Locale locale) { 1286 1287 String path = null; 1288 if (value.getElement() != null) { 1289 path = value.getPath(); 1290 } 1291 1292 return getDefault(cms, value.getDocument() != null ? value.getDocument().getFile() : null, value, path, locale); 1293 } 1294 1295 /** 1296 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidget() 1297 */ 1298 public I_CmsComplexWidget getDefaultComplexWidget() { 1299 1300 return m_defaultWidgetInstance; 1301 } 1302 1303 /** 1304 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidgetClass() 1305 */ 1306 public String getDefaultComplexWidgetClass() { 1307 1308 return m_defaultWidget; 1309 } 1310 1311 /** 1312 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidgetConfiguration() 1313 */ 1314 public String getDefaultComplexWidgetConfiguration() { 1315 1316 return m_defaultWidgetConfig; 1317 } 1318 1319 /** 1320 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDisplayType(org.opencms.xml.types.I_CmsXmlSchemaType) 1321 */ 1322 public DisplayType getDisplayType(I_CmsXmlSchemaType type) { 1323 1324 if (m_displayTypes.containsKey(type.getName())) { 1325 return m_displayTypes.get(type.getName()); 1326 } else { 1327 return DisplayType.none; 1328 } 1329 } 1330 1331 /** 1332 * Returns the edit handler if configured.<p> 1333 * 1334 * @return the edit handler 1335 */ 1336 public I_CmsEditHandler getEditHandler() { 1337 1338 return m_editHandler; 1339 } 1340 1341 /** 1342 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getEditorChangeHandlers(boolean) 1343 */ 1344 public List<I_CmsXmlContentEditorChangeHandler> getEditorChangeHandlers(boolean selfOnly) { 1345 1346 if (selfOnly) { 1347 return Collections.unmodifiableList(m_editorChangeHandlers); 1348 } else { 1349 List<I_CmsXmlContentEditorChangeHandler> result = new ArrayList<>(m_editorChangeHandlers); 1350 List<I_CmsXmlContentEditorChangeHandler> nestedHandlers = getNestedEditorChangeHandlers(); 1351 result.addAll(nestedHandlers); 1352 return result; 1353 } 1354 } 1355 1356 /** 1357 * Gets the help texts for the fields.<p> 1358 * 1359 * @return the help texts for the fields 1360 */ 1361 public Map<String, String> getFieldHelp() { 1362 1363 return Collections.unmodifiableMap(m_fieldDescriptions); 1364 } 1365 1366 /** 1367 * Gets the labels for the fields.<p> 1368 * 1369 * @return the labels for the fields 1370 */ 1371 public Map<String, String> getFieldLabels() { 1372 1373 return Collections.unmodifiableMap(m_fieldNiceNames); 1374 } 1375 1376 /** 1377 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getFormatterConfiguration(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 1378 */ 1379 public CmsFormatterConfiguration getFormatterConfiguration(CmsObject cms, CmsResource resource) { 1380 1381 List<I_CmsFormatterBean> wrappers = Lists.newArrayList(); 1382 for (CmsFormatterBean formatter : m_formatters) { 1383 CmsSchemaFormatterBeanWrapper wrapper = new CmsSchemaFormatterBeanWrapper(cms, formatter, this, resource); 1384 wrappers.add(wrapper); 1385 } 1386 return CmsFormatterConfiguration.create(cms, wrappers); 1387 } 1388 1389 /** 1390 * Gets the geo mapping configuration. 1391 * 1392 * @return the geo mapping configuration 1393 */ 1394 public CmsGeoMappingConfiguration getGeoMappingConfiguration() { 1395 1396 if ((m_primaryGeomappingField == null) && (m_geomappingEntries.size() == 0)) { 1397 return null; 1398 } 1399 List<CmsGeoMappingConfiguration.Entry> configEntries = new ArrayList<>(); 1400 if (m_primaryGeomappingField != null) { 1401 configEntries.add(new CmsGeoMappingConfiguration.Entry(EntryType.field, m_primaryGeomappingField)); 1402 } 1403 configEntries.addAll(m_geomappingEntries); 1404 return new CmsGeoMappingConfiguration(configEntries); 1405 } 1406 1407 /** 1408 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getInvalidRelationAction(java.lang.String) 1409 */ 1410 public InvalidRelationAction getInvalidRelationAction(String name) { 1411 1412 return m_invalidRelationActions.get(name); 1413 } 1414 1415 /** 1416 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJSHeadIncludes() 1417 */ 1418 public Set<String> getJSHeadIncludes() { 1419 1420 return Collections.<String> unmodifiableSet(m_jsHeadIncludes); 1421 } 1422 1423 /** 1424 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJSHeadIncludes(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 1425 */ 1426 @SuppressWarnings("unused") 1427 public Set<String> getJSHeadIncludes(CmsObject cms, CmsResource resource) throws CmsException { 1428 1429 return getJSHeadIncludes(); 1430 } 1431 1432 /** 1433 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJsonRendererSettings() 1434 */ 1435 public JsonRendererSettings getJsonRendererSettings() { 1436 1437 return m_jsonRendererSettings; 1438 } 1439 1440 /** 1441 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMappings() 1442 */ 1443 public Map<String, List<String>> getMappings() { 1444 1445 Map<String, List<String>> result = new HashMap<>(); 1446 for (Map.Entry<String, List<String>> entry : m_elementMappings.entrySet()) { 1447 result.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); 1448 } 1449 return result; 1450 } 1451 1452 /** 1453 * Returns the all mappings defined for the given element xpath.<p> 1454 * 1455 * @since 7.0.2 1456 * 1457 * @param elementName the element xpath to look up the mapping for 1458 * 1459 * @return the mapping defined for the given element xpath 1460 */ 1461 public List<String> getMappings(String elementName) { 1462 1463 List<String> result = m_elementMappings.get(elementName); 1464 if (result == null) { 1465 result = Collections.emptyList(); 1466 } 1467 return result; 1468 } 1469 1470 /** 1471 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessageKeyHandler() 1472 */ 1473 public I_KeyFallbackHandler getMessageKeyHandler() { 1474 1475 return m_messageKeyHandler; 1476 } 1477 1478 /** 1479 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessages(java.util.Locale) 1480 */ 1481 public CmsMessages getMessages(Locale locale) { 1482 1483 CmsMessages result = null; 1484 if ((m_messageBundleNames == null) || m_messageBundleNames.isEmpty()) { 1485 return new CmsMessages(Messages.get().getBundleName(), locale); 1486 } else { 1487 // a message bundle was initialized 1488 CmsMultiMessages multiMessages = new CmsMultiMessages(locale); 1489 for (String messageBundleName : m_messageBundleNames) { 1490 multiMessages.addMessages(new CmsMessages(messageBundleName, locale)); 1491 } 1492 if (!m_messageBundleNames.contains(Messages.get().getBundleName())) { 1493 multiMessages.addMessages(new CmsMessages(Messages.get().getBundleName(), locale)); 1494 } 1495 result = multiMessages; 1496 1497 } 1498 return result; 1499 } 1500 1501 /** 1502 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getModelFolder() 1503 */ 1504 public String getModelFolder() { 1505 1506 return m_modelFolder; 1507 } 1508 1509 /** 1510 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getNestedFormatters(org.opencms.file.CmsObject, org.opencms.file.CmsResource, java.util.Locale, javax.servlet.ServletRequest) 1511 */ 1512 public List<String> getNestedFormatters(CmsObject cms, CmsResource res, Locale locale, ServletRequest req) { 1513 1514 List<String> result = new ArrayList<String>(); 1515 if (hasNestedFormatters()) { 1516 try { 1517 CmsXmlContent content; 1518 if (req != null) { 1519 content = CmsXmlContentFactory.unmarshal(cms, res, req); 1520 } else { 1521 content = CmsXmlContentFactory.unmarshal(cms, cms.readFile(res)); 1522 } 1523 Locale matchingLocale = content.getBestMatchingLocale(locale); 1524 if (matchingLocale == null) { 1525 matchingLocale = content.getLocales().get(0); 1526 } 1527 if (matchingLocale != null) { 1528 for (String elementPath : m_nestedFormatterElements) { 1529 List<I_CmsXmlContentValue> values = content.getValues(elementPath, matchingLocale); 1530 for (I_CmsXmlContentValue value : values) { 1531 if (value instanceof CmsXmlDisplayFormatterValue) { 1532 String formatterId = ((CmsXmlDisplayFormatterValue)value).getFormatterId(); 1533 if ((formatterId != null) && !CmsUUID.getNullUUID().toString().equals(formatterId)) { 1534 result.add(formatterId); 1535 } 1536 } else if (value instanceof CmsXmlVarLinkValue) { 1537 CmsLink link = ((CmsXmlVarLinkValue)value).getLink(cms); 1538 CmsUUID formatterId = link.getStructureId(); 1539 if ((formatterId != null) && !formatterId.isNullUUID()) { 1540 result.add(formatterId.toString()); 1541 } 1542 } else if (value instanceof CmsXmlVfsFileValue) { 1543 CmsLink link = ((CmsXmlVfsFileValue)value).getLink(cms); 1544 CmsUUID formatterId = link.getStructureId(); 1545 if ((formatterId != null) && !formatterId.isNullUUID()) { 1546 result.add(formatterId.toString()); 1547 } 1548 } 1549 } 1550 } 1551 } 1552 } catch (Exception e) { 1553 LOG.error(e.getLocalizedMessage(), e); 1554 } 1555 } 1556 return result; 1557 } 1558 1559 /** 1560 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getParameter(java.lang.String) 1561 */ 1562 public String getParameter(String name) { 1563 1564 return m_parameters.get(name); 1565 } 1566 1567 /** 1568 * 1569 * Gets the set of parameters.<p> 1570 * 1571 * @return zhr drz og pstsmrzrtd d 1572 */ 1573 public CmsParameterConfiguration getParameters() { 1574 1575 return m_parameters; 1576 } 1577 1578 /** 1579 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getPreview(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.lang.String) 1580 */ 1581 public String getPreview(CmsObject cms, CmsXmlContent content, String resourcename) { 1582 1583 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms); 1584 resolver.addMacro(MACRO_PREVIEW_TEMPFILE, resourcename); 1585 1586 return resolver.resolveMacros(m_previewLocation); 1587 } 1588 1589 /** 1590 * @see I_CmsXmlContentHandler#getRelationType(I_CmsXmlContentValue) 1591 */ 1592 @Deprecated 1593 public CmsRelationType getRelationType(I_CmsXmlContentValue value) { 1594 1595 if (value == null) { 1596 return CmsRelationType.XML_WEAK; 1597 } 1598 return getRelationType(value.getPath()); 1599 } 1600 1601 /** 1602 * @see I_CmsXmlContentHandler#getRelationType(String) 1603 */ 1604 public CmsRelationType getRelationType(String xpath) { 1605 1606 return getRelationType(xpath, CmsRelationType.XML_WEAK); 1607 } 1608 1609 /** 1610 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getRelationType(java.lang.String, org.opencms.relations.CmsRelationType) 1611 */ 1612 public CmsRelationType getRelationType(String xpath, CmsRelationType defaultType) { 1613 1614 CmsRelationType relationType = null; 1615 if (xpath != null) { 1616 1617 // look up the default from the configured mappings 1618 relationType = m_relations.get(xpath); 1619 if (relationType == null) { 1620 // no value found, try default xpath 1621 String path = CmsXmlUtils.removeAllXpathIndices(xpath); 1622 // look up the default value again without indexes 1623 relationType = m_relations.get(path); 1624 } 1625 if (relationType == null) { 1626 // no value found, try the last simple type path 1627 String path = CmsXmlUtils.getLastXpathElement(xpath); 1628 // look up the default value again for the last simple type 1629 relationType = m_relations.get(path); 1630 } 1631 } 1632 if (relationType == null) { 1633 relationType = defaultType; 1634 } 1635 return relationType; 1636 } 1637 1638 /** 1639 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchContentType(org.opencms.xml.types.I_CmsXmlContentValue) 1640 */ 1641 public SearchContentType getSearchContentType(I_CmsXmlContentValue value) { 1642 1643 String path = CmsXmlUtils.removeXpath(value.getPath()); 1644 // check for name configured in the annotations 1645 SearchContentType searchSetting = m_searchSettings.get(path); 1646 // if no search setting is found within the root handler, move the path upwards to look for other configurations 1647 if (searchSetting == null) { 1648 String[] pathElements = path.split("/"); 1649 I_CmsXmlSchemaType type = value.getDocument().getContentDefinition().getSchemaType(pathElements[0]); 1650 for (int i = 1; i < pathElements.length; i++) { 1651 type = ((CmsXmlNestedContentDefinition)type).getNestedContentDefinition().getSchemaType( 1652 pathElements[i]); 1653 String subPath = getSubPath(pathElements, i); 1654 searchSetting = type.getContentDefinition().getContentHandler().getSearchSettings().get(subPath); 1655 if (searchSetting != null) { 1656 break; 1657 } 1658 } 1659 } 1660 // if no annotation has been found, use default for value 1661 return (searchSetting == null) ? value.getSearchContentType() : searchSetting; 1662 } 1663 1664 /** 1665 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchFields() 1666 */ 1667 public Set<CmsSearchField> getSearchFields() { 1668 1669 return Collections.unmodifiableSet(new HashSet<CmsSearchField>(m_searchFields.values())); 1670 } 1671 1672 /** 1673 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchFieldsForPage() 1674 */ 1675 public Set<CmsSearchField> getSearchFieldsForPage() { 1676 1677 return Collections.unmodifiableSet(new HashSet<CmsSearchField>(m_searchFieldsPage.values())); 1678 } 1679 1680 /** 1681 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchSettings() 1682 */ 1683 public Map<String, SearchContentType> getSearchSettings() { 1684 1685 return m_searchSettings; 1686 } 1687 1688 /** 1689 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSettings(org.opencms.file.CmsObject, org.opencms.file.CmsResource) 1690 */ 1691 public Map<String, CmsXmlContentProperty> getSettings(CmsObject cms, CmsResource resource) { 1692 1693 return Collections.unmodifiableMap(m_settings); 1694 } 1695 1696 /** 1697 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSynchronizations() 1698 */ 1699 public List<String> getSynchronizations() { 1700 1701 return Collections.unmodifiableList(m_synchronizations); 1702 } 1703 1704 /** 1705 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getTabs() 1706 */ 1707 public List<CmsXmlContentTab> getTabs() { 1708 1709 return Collections.unmodifiableList(m_tabs); 1710 } 1711 1712 /** 1713 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getTitleMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.util.Locale) 1714 */ 1715 public String getTitleMapping(CmsObject cms, CmsXmlContent document, Locale locale) { 1716 1717 String result = null; 1718 if (m_titleMappings.size() > 0) { 1719 // a title mapping is available 1720 String xpath = m_titleMappings.get(0); 1721 // currently just use the first mapping found, unsure if multiple "Title" mappings would make sense anyway 1722 result = document.getStringValue(cms, xpath, locale); 1723 if ((result == null) 1724 && (isMappingUsingDefault(xpath, TITLE_PROPERTY_MAPPING) 1725 || isMappingUsingDefault(xpath, TITLE_PROPERTY_SHARED_MAPPING) 1726 || isMappingUsingDefault(xpath, TITLE_PROPERTY_INDIVIDUAL_MAPPING))) { 1727 result = getDefault(cms, document.getFile(), null, xpath, locale); 1728 } 1729 if (result != null) { 1730 try { 1731 CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver( 1732 createRootCms(cms), 1733 document, 1734 locale); 1735 resolver.setKeepEmptyMacros(true); 1736 result = resolver.resolveMacros(result); 1737 } catch (Exception e) { 1738 LOG.error(e.getMessage(), e); 1739 } 1740 } 1741 } 1742 return result; 1743 } 1744 1745 /** 1746 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getVersionTransformation() 1747 */ 1748 public String getVersionTransformation() { 1749 1750 return m_versionTransformation; 1751 } 1752 1753 /** 1754 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.file.CmsObject, java.lang.String) 1755 */ 1756 public I_CmsWidget getWidget(CmsObject cms, String path) { 1757 1758 String widgetName = m_widgetNames.get(path); 1759 if (widgetName == null) { 1760 return null; 1761 } 1762 1763 // First resolve macros, then try resulting string as widget alias, finally try interpreting it as a class name 1764 if (cms != null) { 1765 CmsMacroResolver resolver = new CmsMacroResolver(); 1766 resolver.setCmsObject(cms); 1767 widgetName = resolver.resolveMacros(widgetName); 1768 } 1769 I_CmsWidget result = null; 1770 result = OpenCms.getXmlContentTypeManager().getWidget(widgetName); 1771 if (result != null) { 1772 return result.newInstance(); 1773 } 1774 if (CmsStringUtil.isValidJavaClassName(widgetName)) { 1775 try { 1776 Class<?> cls = Class.forName(widgetName, false, getClass().getClassLoader()); 1777 if (I_CmsWidget.class.isAssignableFrom(cls)) { 1778 return (I_CmsWidget)(cls.newInstance()); 1779 } 1780 } catch (Exception e) { 1781 LOG.warn(e.getLocalizedMessage(), e); 1782 return null; 1783 } 1784 } 1785 return null; 1786 1787 } 1788 1789 /** 1790 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.xml.types.I_CmsXmlSchemaType) 1791 */ 1792 @Deprecated 1793 public I_CmsWidget getWidget(I_CmsXmlSchemaType value) { 1794 1795 // try the specific widget settings first 1796 I_CmsWidget result = getWidget(null, value.getName()); 1797 if (result == null) { 1798 // use default widget mappings 1799 result = OpenCms.getXmlContentTypeManager().getWidgetDefault(value.getTypeName()); 1800 } else { 1801 result = result.newInstance(); 1802 } 1803 if (result != null) { 1804 // set the configuration value for this widget 1805 String configuration = getConfiguration(value); 1806 if (configuration == null) { 1807 // no individual configuration defined, try to get global default configuration 1808 configuration = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(result); 1809 } 1810 result.setConfiguration(configuration); 1811 } 1812 return result; 1813 } 1814 1815 /** 1816 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasModifiableFormatters() 1817 */ 1818 public boolean hasModifiableFormatters() { 1819 1820 return (m_formatters != null) && (m_formatters.size() > 0); 1821 } 1822 1823 /** 1824 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasNestedFormatters() 1825 */ 1826 public boolean hasNestedFormatters() { 1827 1828 return !m_nestedFormatterElements.isEmpty(); 1829 } 1830 1831 /** 1832 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasSynchronizedElements() 1833 */ 1834 public boolean hasSynchronizedElements() { 1835 1836 return !m_synchronizations.isEmpty(); 1837 } 1838 1839 /** 1840 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasVisibilityHandlers() 1841 */ 1842 public boolean hasVisibilityHandlers() { 1843 1844 return (m_visibilityConfigurations != null) && !m_visibilityConfigurations.isEmpty(); 1845 } 1846 1847 /** 1848 * @see org.opencms.xml.content.I_CmsXmlContentHandler#initialize(org.dom4j.Element, org.opencms.xml.CmsXmlContentDefinition) 1849 */ 1850 public synchronized void initialize(Element appInfoElement, CmsXmlContentDefinition contentDefinition) 1851 throws CmsXmlException { 1852 1853 if (appInfoElement != null) { 1854 // validate the appinfo element XML content with the default appinfo handler schema 1855 validateAppinfoElement(appInfoElement); 1856 1857 // re-initialize the local variables 1858 init(); 1859 1860 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(appInfoElement); 1861 while (i.hasNext()) { 1862 // iterate all elements in the appinfo node 1863 Element element = i.next(); 1864 String nodeName = element.getName(); 1865 if (nodeName.equals(APPINFO_MAPPINGS)) { 1866 initMappings(element, contentDefinition); 1867 } else if (nodeName.equals(APPINFO_LAYOUTS)) { 1868 initLayouts(element, contentDefinition); 1869 } else if (nodeName.equals(APPINFO_VALIDATIONRULES)) { 1870 initValidationRules(element, contentDefinition); 1871 } else if (nodeName.equals(APPINFO_RELATIONS)) { 1872 initRelations(element, contentDefinition); 1873 } else if (nodeName.equals(APPINFO_DEFAULTS)) { 1874 initDefaultValues(element, contentDefinition); 1875 } else if (nodeName.equals(APPINFO_MODELFOLDER)) { 1876 initModelFolder(element, contentDefinition); 1877 } else if (nodeName.equals(APPINFO_PREVIEW)) { 1878 initPreview(element, contentDefinition); 1879 } else if (nodeName.equals(APPINFO_RESOURCEBUNDLE)) { 1880 initResourceBundle(element, contentDefinition, true); 1881 } else if (nodeName.equals(APPINFO_RESOURCEBUNDLES)) { 1882 initResourceBundle(element, contentDefinition, false); 1883 } else if (nodeName.equals(APPINFO_SEARCHSETTINGS)) { 1884 initSearchSettings(element, contentDefinition); 1885 } else if (nodeName.equals(APPINFO_TABS)) { 1886 initTabs(element, contentDefinition); 1887 } else if (nodeName.equals(APPINFO_FORMATTERS)) { 1888 initFormatters(element, contentDefinition); 1889 } else if (nodeName.equals(APPINFO_HEAD_INCLUDES)) { 1890 initHeadIncludes(element, contentDefinition); 1891 } else if (nodeName.equals(APPINFO_SETTINGS)) { 1892 initSettings(element, contentDefinition); 1893 } else if (nodeName.equals(APPINFO_EDIT_HANDLER)) { 1894 initEditHandler(element); 1895 } else if (nodeName.equals(APPINFO_NESTED_FORMATTERS)) { 1896 initNestedFormatters(element, contentDefinition); 1897 } else if (nodeName.equals(APPINFO_TEMPLATES)) { 1898 initTemplates(element, contentDefinition); 1899 } else if (nodeName.equals(APPINFO_DEFAULTWIDGET)) { 1900 initDefaultWidget(element); 1901 } else if (nodeName.equals(APPINFO_VISIBILITIES)) { 1902 initVisibilities(element, contentDefinition); 1903 } else if (nodeName.equals(APPINFO_SYNCHRONIZATIONS)) { 1904 initSynchronizations(element, contentDefinition); 1905 } else if (nodeName.equals(APPINFO_EDITOR_CHANGE_HANDLERS)) { 1906 initEditorChangeHandlers(element); 1907 } else if (nodeName.equals(APPINFO_MESSAGEKEYHANDLER)) { 1908 initMessageKeyHandler(element); 1909 } else if (nodeName.equals(APPINFO_PARAMETERS)) { 1910 initParameters(element); 1911 } else if (nodeName.equals(APPINFO_FIELD_SETTINGS)) { 1912 initFields(element, contentDefinition); 1913 } else if (nodeName.equals(APPINFO_JSON_RENDERER)) { 1914 initJsonRenderer(element); 1915 } else if (nodeName.equals(APPINFO_REVERSE_MAPPING_ENABLED)) { 1916 m_reverseMappingEnabled = Boolean.parseBoolean(element.getTextTrim()); 1917 } else if (nodeName.equals(APPINFO_GEOMAPPING)) { 1918 initGeoMappingEntries(element); 1919 } else if (nodeName.equals(APPINFO_VERSION_TRANSFORMATION)) { 1920 m_versionTransformation = element.getTextTrim(); 1921 } 1922 } 1923 } 1924 m_contentDefinition = contentDefinition; 1925 addGeoMappingField(); 1926 1927 // at the end, add default check rules for optional file references 1928 addDefaultCheckRules(contentDefinition, null, null); 1929 } 1930 1931 /** 1932 * @see org.opencms.xml.content.I_CmsXmlContentHandler#invalidateBrokenLinks(CmsObject, CmsXmlContent) 1933 */ 1934 public void invalidateBrokenLinks(CmsObject cms, CmsXmlContent document) { 1935 1936 if ((cms == null) || (cms.getRequestContext().getRequestTime() == CmsResource.DATE_RELEASED_EXPIRED_IGNORE)) { 1937 // do not check if the request comes the editor 1938 return; 1939 } 1940 boolean needReinitialization = false; 1941 // iterate the locales 1942 Iterator<Locale> itLocales = document.getLocales().iterator(); 1943 while (itLocales.hasNext()) { 1944 Locale locale = itLocales.next(); 1945 List<String> removedNodes = new ArrayList<String>(); 1946 Map<String, I_CmsXmlContentValue> valuesToRemove = Maps.newHashMap(); 1947 // iterate the values 1948 Iterator<I_CmsXmlContentValue> itValues = document.getValues(locale).iterator(); 1949 while (itValues.hasNext()) { 1950 I_CmsXmlContentValue value = itValues.next(); 1951 InvalidRelationAction invalidRelationAction = getInvalidRelationActionForValue(value); 1952 String path = value.getPath(); 1953 // check if this value has already been deleted by parent rules 1954 boolean alreadyRemoved = false; 1955 Iterator<String> itRemNodes = removedNodes.iterator(); 1956 while (itRemNodes.hasNext()) { 1957 String remNode = itRemNodes.next(); 1958 if (path.startsWith(remNode)) { 1959 alreadyRemoved = true; 1960 break; 1961 } 1962 } 1963 // only continue if not already removed and if a rule match 1964 if (alreadyRemoved 1965 || ((m_relationChecks.get(path) == null) 1966 && (invalidRelationAction == null) 1967 && (m_relationChecks.get(CmsXmlUtils.removeXpath(path)) == null))) { 1968 continue; 1969 } 1970 1971 // check rule matched 1972 if (LOG.isDebugEnabled()) { 1973 LOG.debug(Messages.get().getBundle().key(Messages.LOG_XMLCONTENT_CHECK_RULE_MATCH_1, path)); 1974 } 1975 if (validateLink(cms, value, null)) { 1976 // invalid link 1977 if (LOG.isDebugEnabled()) { 1978 LOG.debug( 1979 Messages.get().getBundle().key( 1980 Messages.LOG_XMLCONTENT_CHECK_WARNING_2, 1981 path, 1982 value.getStringValue(cms))); 1983 } 1984 // find the node to remove 1985 String parentPath = path; 1986 boolean firstIteration = true; 1987 while (isInvalidateParent(parentPath) 1988 || (firstIteration && (invalidRelationAction == InvalidRelationAction.removeParent))) { 1989 firstIteration = false; 1990 // check parent 1991 parentPath = CmsXmlUtils.removeLastXpathElement(parentPath); 1992 // log info 1993 if (LOG.isDebugEnabled()) { 1994 LOG.debug( 1995 Messages.get().getBundle().key( 1996 Messages.LOG_XMLCONTENT_CHECK_PARENT_2, 1997 path, 1998 parentPath)); 1999 } 2000 } 2001 value = document.getValue(parentPath, locale); 2002 // Doing the actual DOM modifications here would make the bookmarks for this locale invalid, 2003 // so we delay it until later because we need the bookmarks for document.getValue() in the next loop iterations 2004 valuesToRemove.put(parentPath, value); 2005 // mark node as deleted 2006 removedNodes.add(parentPath); 2007 } 2008 } 2009 if (!removedNodes.isEmpty()) { 2010 needReinitialization = true; 2011 } 2012 for (I_CmsXmlContentValue valueToRemove : valuesToRemove.values()) { 2013 // detach the value node from the XML document 2014 valueToRemove.getElement().detach(); 2015 } 2016 } 2017 if (needReinitialization) { 2018 // re-initialize the XML content 2019 document.initDocument(); 2020 } 2021 } 2022 2023 /** 2024 * Returns true if the Acacia editor is disabled for this type.<p> 2025 * 2026 * @return true if the acacia editor is disabled 2027 */ 2028 public boolean isAcaciaEditorDisabled() { 2029 2030 return !m_useAcacia; 2031 } 2032 2033 /** 2034 * @see org.opencms.xml.content.I_CmsXmlContentHandler#isContainerPageOnly() 2035 */ 2036 public boolean isContainerPageOnly() { 2037 2038 return m_containerPageOnly; 2039 } 2040 2041 /** 2042 * Returns the content field visibilty.<p> 2043 * 2044 * This implementation will be used as default if no other <link>org.opencms.xml.content.I_CmsXmlContentVisibilityHandler</link> is configured.<p> 2045 * 2046 * Only users that are member in one of the specified groups will be allowed to view and edit the given content field.<p> 2047 * The parameter should contain a '|' separated list of group names.<p> 2048 * 2049 * @see org.opencms.xml.content.I_CmsXmlContentVisibilityHandler#isValueVisible(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, java.lang.String, org.opencms.file.CmsResource, java.util.Locale) 2050 */ 2051 public boolean isValueVisible( 2052 CmsObject cms, 2053 I_CmsXmlSchemaType value, 2054 String elementName, 2055 String params, 2056 CmsResource resource, 2057 Locale contentLocale) { 2058 2059 CmsUser user = cms.getRequestContext().getCurrentUser(); 2060 boolean result = false; 2061 2062 try { 2063 List<CmsRole> roles = OpenCms.getRoleManager().getRolesOfUser(cms, user.getName(), "", true, false, true); 2064 List<CmsGroup> groups = cms.getGroupsOfUser(user.getName(), false); 2065 CmsMacroResolver resolver = new CmsMacroResolver(); 2066 resolver.setCmsObject(cms); 2067 Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2068 resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale)); 2069 params = resolver.resolveMacros(params); 2070 2071 if ("visible".equals(params.trim())) { 2072 return true; 2073 } 2074 String[] allowedPrincipals = params.split("\\|"); 2075 List<String> groupNames = new ArrayList<String>(); 2076 List<String> roleNames = new ArrayList<String>(); 2077 2078 for (CmsGroup group : groups) { 2079 groupNames.add(group.getName()); 2080 } 2081 for (CmsRole role : roles) { 2082 roleNames.add(role.getRoleName()); 2083 } 2084 for (String principal : allowedPrincipals) { 2085 if (CmsRole.hasPrefix(principal)) { 2086 // prefixed as a role 2087 principal = CmsRole.removePrefix(principal); 2088 if (roleNames.contains(principal)) { 2089 result = true; 2090 break; 2091 } 2092 } else { 2093 // otherwise we always assume this is a group, will work if prefixed or not 2094 principal = CmsGroup.removePrefix(principal); 2095 if (groupNames.contains(principal)) { 2096 result = true; 2097 break; 2098 } 2099 } 2100 } 2101 } catch (CmsException e) { 2102 LOG.error(e.getLocalizedMessage(), e); 2103 } 2104 2105 return result; 2106 } 2107 2108 /** 2109 * @see org.opencms.xml.content.I_CmsXmlContentHandler#isVisible(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, org.opencms.file.CmsResource, java.util.Locale) 2110 */ 2111 public boolean isVisible( 2112 CmsObject cms, 2113 I_CmsXmlSchemaType contentValue, 2114 String valuePath, 2115 CmsResource resource, 2116 Locale contentLocale) { 2117 2118 if (hasVisibilityHandlers() && m_visibilityConfigurations.containsKey(valuePath)) { 2119 VisibilityConfiguration config = m_visibilityConfigurations.get(valuePath); 2120 return config.getHandler().isValueVisible( 2121 cms, 2122 contentValue, 2123 valuePath, 2124 config.getParams(), 2125 resource, 2126 contentLocale); 2127 } 2128 return true; 2129 2130 } 2131 2132 /** 2133 * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForUse(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent) 2134 */ 2135 public CmsXmlContent prepareForUse(CmsObject cms, CmsXmlContent content) { 2136 2137 // NOOP, just return the unmodified content 2138 return content; 2139 } 2140 2141 /** 2142 * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForWrite(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.file.CmsFile) 2143 */ 2144 public CmsFile prepareForWrite(CmsObject cms, CmsXmlContent content, CmsFile file) throws CmsException { 2145 2146 if (!content.isAutoCorrectionEnabled()) { 2147 // check if the XML should be corrected automatically (if not already set) 2148 Object attribute = cms.getRequestContext().getAttribute(CmsXmlContent.AUTO_CORRECTION_ATTRIBUTE); 2149 // set the auto correction mode as required 2150 boolean autoCorrectionEnabled = (attribute != null) && ((Boolean)attribute).booleanValue(); 2151 content.setAutoCorrectionEnabled(autoCorrectionEnabled); 2152 } 2153 // validate the XML structure before writing the file if required 2154 if (!content.isAutoCorrectionEnabled()) { 2155 // an exception will be thrown if the structure is invalid 2156 content.validateXmlStructure(new CmsXmlEntityResolver(cms)); 2157 } 2158 // read the content-conversion property 2159 String contentConversion = CmsHtmlConverter.getConversionSettings(cms, file); 2160 if (CmsStringUtil.isEmptyOrWhitespaceOnly(contentConversion)) { 2161 // enable pretty printing and XHTML conversion of XML content html fields by default 2162 contentConversion = CmsHtmlConverter.PARAM_XHTML; 2163 } 2164 content.setConversion(contentConversion); 2165 // correct the HTML structure 2166 file = content.correctXmlStructure(cms); 2167 content.setFile(file); 2168 2169 // check if any field has a configured attribute mapping 2170 boolean hasAttributeMappings = m_elementMappings.values().stream().flatMap(List::stream).filter( 2171 mapping -> mapping.startsWith(MAPTO_ATTRIBUTE)).findAny().isPresent(); 2172 2173 // resolve the file mappings 2174 CmsMappingResolutionContext mappingContext = new CmsMappingResolutionContext(content, hasAttributeMappings); 2175 mappingContext.setCmsObject(cms); 2176 // pass the mapping context as a request context attribute to preserve interface compatibility 2177 cms.getRequestContext().setAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT, mappingContext); 2178 content.resolveMappings(cms); 2179 // ensure all property or permission mappings of deleted optional values are removed 2180 removeEmptyMappings(cms, file, content); 2181 resolveDefaultMappings(cms, file, content); 2182 cms.getRequestContext().removeAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT); 2183 mappingContext.finalizeMappings(); 2184 // write categories (if there is a category widget present) 2185 file = writeCategories(cms, file, content); 2186 // return the result 2187 return file; 2188 } 2189 2190 /** 2191 * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue) 2192 */ 2193 public void resolveMapping(CmsObject cms, CmsXmlContent content, I_CmsXmlContentValue value) throws CmsException { 2194 2195 if (content.getFile() == null) { 2196 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0)); 2197 } 2198 2199 // get the mappings for the element name 2200 boolean valueIsSimple = value.isSimpleType(); 2201 String valuePath = value.getPath(); 2202 int valueIndex = value.getIndex(); 2203 Locale valueLocale = value.getLocale(); 2204 CmsObject rootCms1 = createRootCms(cms); 2205 String originalStringValue = null; 2206 if (valueIsSimple) { 2207 originalStringValue = value.getStringValue(rootCms1); 2208 } 2209 resolveMapping(cms, content, valuePath, valueIsSimple, valueIndex, valueLocale, originalStringValue); 2210 } 2211 2212 /** 2213 * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveValidation(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler) 2214 */ 2215 public CmsXmlContentErrorHandler resolveValidation( 2216 CmsObject cms, 2217 I_CmsXmlContentValue value, 2218 CmsXmlContentErrorHandler errorHandler) { 2219 2220 if (errorHandler == null) { 2221 // init a new error handler if required 2222 errorHandler = new CmsXmlContentErrorHandler(); 2223 } 2224 2225 if (!value.isSimpleType()) { 2226 // no validation for a nested schema is possible 2227 // note that the sub-elements of the nested schema ARE validated by the node visitor, 2228 // it's just the nested schema value itself that does not support validation 2229 return errorHandler; 2230 } 2231 2232 // validate the error rules 2233 errorHandler = validateValue(cms, value, errorHandler, m_validationErrorRules, false); 2234 // validate the warning rules 2235 errorHandler = validateValue(cms, value, errorHandler, m_validationWarningRules, true); 2236 // validate categories 2237 errorHandler = validateCategories(cms, value, errorHandler); 2238 // return the result 2239 return errorHandler; 2240 } 2241 2242 /** 2243 * Adds a check rule for a specified element.<p> 2244 * 2245 * @param contentDefinition the XML content definition this XML content handler belongs to 2246 * @param elementName the element name to add the rule to 2247 * @param invalidate <code>false</code>, to disable link check / 2248 * <code>true</code> or <code>node</code>, to invalidate just the single node if the link is broken / 2249 * <code>parent</code>, if this rule will invalidate the whole parent node in nested content 2250 * @param type the relation type 2251 * 2252 * @throws CmsXmlException in case an unknown element name is used 2253 */ 2254 protected void addCheckRule( 2255 CmsXmlContentDefinition contentDefinition, 2256 String elementName, 2257 String invalidate, 2258 String type) 2259 throws CmsXmlException { 2260 2261 I_CmsXmlSchemaType schemaType = contentDefinition.getSchemaType(elementName); 2262 if (schemaType == null) { 2263 // no element with the given name 2264 throw new CmsXmlException( 2265 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_ELEM_1, elementName)); 2266 } 2267 if (!CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName()) 2268 && !CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) { 2269 // element is not a OpenCmsVfsFile 2270 throw new CmsXmlException( 2271 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_TYPE_1, elementName)); 2272 } 2273 2274 // cache the check rule data 2275 Boolean invalidateParent = null; 2276 if ((invalidate == null) 2277 || invalidate.equalsIgnoreCase(Boolean.TRUE.toString()) 2278 || invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_NODE)) { 2279 invalidateParent = Boolean.FALSE; 2280 } else if (invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_PARENT)) { 2281 invalidateParent = Boolean.TRUE; 2282 } 2283 if (invalidateParent != null) { 2284 m_relationChecks.put(elementName, invalidateParent); 2285 } 2286 CmsRelationType relationType = (type == null ? CmsRelationType.XML_WEAK : CmsRelationType.valueOfXml(type)); 2287 m_relations.put(elementName, relationType); 2288 2289 if (invalidateParent != null) { 2290 // check the whole xpath hierarchy 2291 String path = elementName; 2292 while (CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)) { 2293 if (!isInvalidateParent(path)) { 2294 // if invalidate type = node, then the node needs to be optional 2295 if (contentDefinition.getSchemaType(path).getMinOccurs() > 0) { 2296 // element is not optional 2297 throw new CmsXmlException( 2298 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_OPTIONAL_1, path)); 2299 } 2300 // no need to further check 2301 break; 2302 } else if (!CmsXmlUtils.isDeepXpath(path)) { 2303 // if invalidate type = parent, then the node needs to be nested 2304 // document root can not be invalidated 2305 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_EMPTY_DOC_0)); 2306 } 2307 path = CmsXmlUtils.removeLastXpathElement(path); 2308 } 2309 } 2310 } 2311 2312 /** 2313 * Adds a configuration value for an element widget.<p> 2314 * 2315 * @param contentDefinition the XML content definition this XML content handler belongs to 2316 * @param elementName the element name 2317 * @param configurationValue the configuration value to use 2318 * 2319 * @throws CmsXmlException in case an unknown element name is used 2320 */ 2321 protected void addConfiguration( 2322 CmsXmlContentDefinition contentDefinition, 2323 String elementName, 2324 String configurationValue) 2325 throws CmsXmlException { 2326 2327 if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) { 2328 throw new CmsXmlException( 2329 Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName)); 2330 } 2331 2332 m_configurationValues.put(elementName, configurationValue); 2333 } 2334 2335 /** 2336 * Adds a default value for an element.<p> 2337 * 2338 * @param contentDefinition the XML content definition this XML content handler belongs to 2339 * @param elementName the element name to map 2340 * @param defaultValue the default value to use 2341 * @param resolveMacrosValue the value of the 'resolveMacros' attribute 2342 * 2343 * @throws CmsXmlException in case an unknown element name is used 2344 */ 2345 protected void addDefault( 2346 CmsXmlContentDefinition contentDefinition, 2347 String elementName, 2348 String defaultValue, 2349 String resolveMacrosValue) 2350 throws CmsXmlException { 2351 2352 if (contentDefinition.getSchemaType(elementName) == null) { 2353 throw new CmsXmlException( 2354 org.opencms.xml.types.Messages.get().container( 2355 Messages.ERR_XMLCONTENT_INVALID_ELEM_DEFAULT_1, 2356 elementName)); 2357 } 2358 // store mappings as xpath to allow better control about what is mapped 2359 String xpath = CmsXmlUtils.createXpath(elementName, 1); 2360 m_defaultValues.put(xpath, defaultValue); 2361 2362 // macros are resolved by default 2363 if ((resolveMacrosValue != null) && !Boolean.parseBoolean(resolveMacrosValue)) { 2364 m_nonMacroResolvableDefaults.add(xpath); 2365 } 2366 } 2367 2368 /** 2369 * Adds all needed default check rules recursively for the given schema type.<p> 2370 * 2371 * @param rootContentDefinition the root content definition 2372 * @param schemaType the schema type to check 2373 * @param elementPath the current element path 2374 * 2375 * @throws CmsXmlException if something goes wrong 2376 */ 2377 protected void addDefaultCheckRules( 2378 CmsXmlContentDefinition rootContentDefinition, 2379 I_CmsXmlSchemaType schemaType, 2380 String elementPath) 2381 throws CmsXmlException { 2382 2383 if ((schemaType != null) && schemaType.isSimpleType()) { 2384 if ((schemaType.getMinOccurs() == 0) 2385 && (CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName()) 2386 || CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) 2387 && !m_relationChecks.containsKey(elementPath) 2388 && !m_relations.containsKey(elementPath)) { 2389 // add default check rule for the element 2390 addCheckRule(rootContentDefinition, elementPath, null, null); 2391 } 2392 } else { 2393 // recursion required 2394 CmsXmlContentDefinition nestedContentDefinition = rootContentDefinition; 2395 if (schemaType != null) { 2396 CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition)schemaType; 2397 nestedContentDefinition = nestedDefinition.getNestedContentDefinition(); 2398 } 2399 Iterator<String> itElems = nestedContentDefinition.getSchemaTypes().iterator(); 2400 while (itElems.hasNext()) { 2401 String element = itElems.next(); 2402 String path = (schemaType != null) ? CmsXmlUtils.concatXpath(elementPath, element) : element; 2403 I_CmsXmlSchemaType nestedSchema = nestedContentDefinition.getSchemaType(element); 2404 if ((schemaType == null) || !nestedSchema.equals(schemaType)) { 2405 addDefaultCheckRules(rootContentDefinition, nestedSchema, path); 2406 } 2407 } 2408 } 2409 } 2410 2411 /** 2412 * Adds the given element to the compact view set.<p> 2413 * 2414 * @param contentDefinition the XML content definition this XML content handler belongs to 2415 * @param elementName the element name 2416 * @param displayType the display type to use for the element widget 2417 * 2418 * @throws CmsXmlException in case an unknown element name is used 2419 */ 2420 protected void addDisplayType( 2421 CmsXmlContentDefinition contentDefinition, 2422 String elementName, 2423 DisplayType displayType) 2424 throws CmsXmlException { 2425 2426 if (contentDefinition.getSchemaType(elementName) == null) { 2427 throw new CmsXmlException( 2428 Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName)); 2429 } 2430 m_displayTypes.put(elementName, displayType); 2431 } 2432 2433 /** 2434 * Finally adds the field used for geo-coordinate mapping by combining the configuration 2435 * from the geomapping section and the field settings. 2436 */ 2437 protected void addGeoMappingField() { 2438 2439 CmsGeoMappingConfiguration mappingConfig = getGeoMappingConfiguration(); 2440 if (mappingConfig != null) { 2441 CmsSolrField field = new CmsSolrField( 2442 GEOMAPPING_FIELD, 2443 Collections.emptyList(), 2444 CmsLocaleManager.getDefaultLocale(), 2445 "0.000000,0.000000"); 2446 I_CmsSearchFieldMapping mapping = new CmsGeoCoordinateFieldMapping(getGeoMappingConfiguration()); 2447 field.addMapping(mapping); 2448 m_searchFields.put("__geocoord__", field); 2449 } 2450 } 2451 2452 /** 2453 * Adds an element mapping.<p> 2454 * 2455 * @param contentDefinition the XML content definition this XML content handler belongs to 2456 * @param elementName the element name to map 2457 * @param mapping the mapping to use 2458 * @param useDefault the 'useDefault' attribute 2459 * 2460 * @throws CmsXmlException in case an unknown element name is used 2461 */ 2462 protected void addMapping( 2463 CmsXmlContentDefinition contentDefinition, 2464 String elementName, 2465 String mapping, 2466 String useDefault) 2467 throws CmsXmlException { 2468 2469 if (contentDefinition.getSchemaType(elementName) == null) { 2470 throw new CmsXmlException( 2471 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName)); 2472 } 2473 2474 // store mappings as xpath to allow better control about what is mapped 2475 String xpath = CmsXmlUtils.createXpath(elementName, 1); 2476 // since 7.0.2 multiple mappings are possible, so the mappings are stored in an array 2477 List<String> values = m_elementMappings.get(xpath); 2478 if (values == null) { 2479 // there should not really be THAT much multiple mappings per value... 2480 values = new ArrayList<String>(4); 2481 m_elementMappings.put(xpath, values); 2482 } 2483 if (Boolean.parseBoolean(useDefault)) { 2484 m_mappingsUsingDefault.add(xpath + ":" + mapping); 2485 } 2486 values.add(mapping); 2487 if (mapping.startsWith(MAPTO_PROPERTY) && mapping.endsWith(":" + CmsPropertyDefinition.PROPERTY_TITLE)) { 2488 // this is a title mapping 2489 m_titleMappings.add(xpath); 2490 } 2491 } 2492 2493 /** 2494 * Adds a nested formatter element.<p> 2495 * 2496 * @param elementName the element name 2497 * @param contentDefinition the content definition 2498 * 2499 * @throws CmsXmlException in case something goes wrong 2500 */ 2501 protected void addNestedFormatter(String elementName, CmsXmlContentDefinition contentDefinition) 2502 throws CmsXmlException { 2503 2504 if (contentDefinition.getSchemaType(elementName) == null) { 2505 throw new CmsXmlException( 2506 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName)); 2507 } 2508 m_nestedFormatterElements.add(elementName); 2509 } 2510 2511 /** 2512 * Adds a Solr field for an element.<p> 2513 * 2514 * @param contentDefinition the XML content definition this XML content handler belongs to 2515 * @param field the Solr field 2516 */ 2517 @Deprecated 2518 protected void addSearchField(CmsXmlContentDefinition contentDefinition, CmsSearchField field) { 2519 2520 addSearchField(contentDefinition, field, I_CmsXmlContentHandler.MappingType.ELEMENT); 2521 } 2522 2523 /** 2524 * Adds a Solr field for an element.<p> 2525 * 2526 * @param contentDefinition the XML content definition this XML content handler belongs to 2527 * @param field the Solr field 2528 * @param type the type, specifying if the field should be attached to the document of the XML content or to all container pages the content is placed on 2529 */ 2530 protected void addSearchField( 2531 CmsXmlContentDefinition contentDefinition, 2532 CmsSearchField field, 2533 I_CmsXmlContentHandler.MappingType type) { 2534 2535 Locale locale = null; 2536 if (field instanceof CmsSolrField) { 2537 locale = ((CmsSolrField)field).getLocale(); 2538 } 2539 String key = CmsXmlUtils.concatXpath(locale != null ? locale.toString() : null, field.getName()); 2540 switch (type) { 2541 case PAGE: 2542 m_searchFieldsPage.put(key, field); 2543 break; 2544 case ELEMENT: 2545 default: 2546 m_searchFields.put(key, field); 2547 break; 2548 } 2549 } 2550 2551 /** 2552 * Adds a search setting for an element.<p> 2553 * 2554 * @param contentDefinition the XML content definition this XML content handler belongs to 2555 * @param elementName the element name to map 2556 * @param value the search setting value to store 2557 * 2558 * @throws CmsXmlException in case an unknown element name is used 2559 */ 2560 protected void addSearchSetting( 2561 CmsXmlContentDefinition contentDefinition, 2562 String elementName, 2563 SearchContentType value) 2564 throws CmsXmlException { 2565 2566 if (contentDefinition.getSchemaType(elementName) == null) { 2567 throw new CmsXmlException( 2568 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_SEARCHSETTINGS_1, elementName)); 2569 } 2570 // store the search exclusion as defined 2571 m_searchSettings.put(elementName, value); 2572 } 2573 2574 /** 2575 * Adds search settings as defined by 'simple' syntax in fields.<p> 2576 * 2577 * @param contentDef the content definition 2578 * @param name the element name 2579 * @param value the search setting value 2580 * @throws CmsXmlException if something goes wrong 2581 */ 2582 protected void addSimpleSearchSetting(CmsXmlContentDefinition contentDef, String name, String value) 2583 throws CmsXmlException { 2584 2585 SearchContentType searchContentType = SearchContentType.fromString(value); 2586 if (null != searchContentType) { 2587 addSearchSetting(contentDef, name, searchContentType); 2588 } else { 2589 if ("geocoords".equals(value) || "listgeocoords".equals(value)) { 2590 m_primaryGeomappingField = name; 2591 m_searchSettings.put(CmsXmlUtils.removeXpath(name), I_CmsXmlContentValue.SearchContentType.FALSE); 2592 } else { 2593 StringTemplate template = m_searchTemplateGroup.getInstanceOf(value); 2594 if ((template != null) && (template.getFormalArgument("name") != null)) { 2595 template.setAttribute("name", CmsEncoder.escapeXml(name)); 2596 String xml = template.toString(); 2597 try { 2598 Document doc = DocumentHelper.parseText(xml); 2599 initSearchSettings(doc.getRootElement(), contentDef); 2600 } catch (DocumentException e) { 2601 LOG.error(e.getLocalizedMessage(), e); 2602 } 2603 } 2604 } 2605 } 2606 } 2607 2608 /** 2609 * Adds a validation rule for a specified element.<p> 2610 * 2611 * @param contentDefinition the XML content definition this XML content handler belongs to 2612 * @param elementName the element name to add the rule to 2613 * @param regex the validation rule regular expression 2614 * @param message the message in case validation fails (may be null) 2615 * @param isWarning if true, this rule is used for warnings, otherwise it's an error 2616 * 2617 * @throws CmsXmlException in case an unknown element name is used 2618 */ 2619 protected void addValidationRule( 2620 CmsXmlContentDefinition contentDefinition, 2621 String elementName, 2622 String regex, 2623 String message, 2624 boolean isWarning) 2625 throws CmsXmlException { 2626 2627 if (contentDefinition.getSchemaType(elementName) == null) { 2628 throw new CmsXmlException( 2629 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_VALIDATION_1, elementName)); 2630 } 2631 2632 if (isWarning) { 2633 m_validationWarningRules.put(elementName, regex); 2634 if (message != null) { 2635 m_validationWarningMessages.put(elementName, message); 2636 } 2637 } else { 2638 m_validationErrorRules.put(elementName, regex); 2639 if (message != null) { 2640 m_validationErrorMessages.put(elementName, message); 2641 } 2642 } 2643 } 2644 2645 /** 2646 * Adds a GUI widget for a specified element.<p> 2647 * 2648 * @param contentDefinition the XML content definition this XML content handler belongs to 2649 * @param elementName the element name to map 2650 * @param name the widget to use as GUI for the element (registered alias or class name) 2651 * 2652 * @throws CmsXmlException in case an unknown element name is used 2653 */ 2654 protected void addWidget(CmsXmlContentDefinition contentDefinition, String elementName, String name) 2655 throws CmsXmlException { 2656 2657 if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) { 2658 throw new CmsXmlException( 2659 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_LAYOUTWIDGET_1, elementName)); 2660 } 2661 2662 if (name.indexOf(I_CmsMacroResolver.MACRO_DELIMITER) == -1) { 2663 // we can only validate this if we don't have macros 2664 if (OpenCms.getXmlContentTypeManager().getWidget(name) == null) { 2665 if (CmsStringUtil.isValidJavaClassName(name)) { 2666 try { 2667 Class<?> cls = Class.forName(name, false, getClass().getClassLoader()); 2668 if (!I_CmsWidget.class.isAssignableFrom(cls) 2669 && !I_CmsComplexWidget.class.isAssignableFrom(cls)) { 2670 throw new CmsXmlException( 2671 Messages.get().container( 2672 Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3, 2673 name, 2674 elementName, 2675 contentDefinition.getSchemaLocation())); 2676 2677 } 2678 } catch (Exception e) { 2679 throw new CmsXmlException( 2680 Messages.get().container( 2681 Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3, 2682 name, 2683 elementName, 2684 contentDefinition.getSchemaLocation()), 2685 e); 2686 } 2687 } 2688 } 2689 2690 } 2691 m_widgetNames.put(elementName, name); 2692 } 2693 2694 /** 2695 * Helper method to create a visibility configuration.<p> 2696 * 2697 * @param className the visibility handler class name 2698 * @param params the parameters for the visibility 2699 * 2700 * @return the visibility configuration 2701 */ 2702 protected VisibilityConfiguration createVisibilityConfiguration(String className, String params) { 2703 2704 I_CmsXmlContentVisibilityHandler handler = this; 2705 if (className != null) { 2706 try { 2707 handler = (I_CmsXmlContentVisibilityHandler)(Class.forName(className).newInstance()); 2708 } catch (Exception e) { 2709 LOG.error(e.getLocalizedMessage(), e); 2710 } 2711 } 2712 VisibilityConfiguration result = new VisibilityConfiguration(handler, params); 2713 return result; 2714 } 2715 2716 /** 2717 * Returns information about the availability mapping for the given availability attribute. 2718 * 2719 * @param attr the availability attribute 2720 * @return the information about the mapping 2721 */ 2722 protected MappingInfo getAttributeMapping(AttributeType attr) { 2723 2724 String target = null; 2725 String source = null; 2726 switch (attr) { 2727 case expiration: 2728 target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATEEXPIRED; 2729 break; 2730 2731 case release: 2732 target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATERELEASED; 2733 break; 2734 2735 default: 2736 break; 2737 } 2738 if (target != null) { 2739 source = getMappingSource(target); 2740 } 2741 2742 return new MappingInfo(source, target); 2743 } 2744 2745 /** 2746 * Returns the configured default locales for the content of the given resource.<p> 2747 * 2748 * @param cms the cms context 2749 * @param resource the resource path to get the default locales for 2750 * 2751 * @return the default locales of the resource 2752 */ 2753 protected List<Locale> getLocalesForResource(CmsObject cms, String resource) { 2754 2755 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(cms, resource); 2756 if ((locales == null) || locales.isEmpty()) { 2757 locales = OpenCms.getLocaleManager().getAvailableLocales(); 2758 } 2759 return locales; 2760 } 2761 2762 /** 2763 * Creates editor change handler instances for all nested fields that have configured them in their field settings 2764 * 2765 * @return editor change handlers for all nested fields for which they are configured 2766 */ 2767 protected List<I_CmsXmlContentEditorChangeHandler> getNestedEditorChangeHandlers() { 2768 2769 Multimap<String, CmsChangeHandlerConfig> configMap = ArrayListMultimap.create(); 2770 collectNestedChangeHandlerConfigs(m_contentDefinition, "", configMap); 2771 List<I_CmsXmlContentEditorChangeHandler> result = new ArrayList<>(); 2772 for (String key : configMap.keySet()) { 2773 for (CmsChangeHandlerConfig handlerConfig : configMap.get(key)) { 2774 String path = CmsStringUtil.joinPaths(key, handlerConfig.getField()); 2775 path = CmsFileUtil.removeLeadingSeparator(path); 2776 String scope = normalizeChangeHandlerScope(path); 2777 java.util.Optional<I_CmsXmlContentEditorChangeHandler> optHandler = handlerConfig.newHandler(scope); 2778 if (optHandler.isPresent()) { 2779 result.add(optHandler.get()); 2780 } 2781 } 2782 } 2783 List<I_CmsXmlContentEditorChangeHandler> nestedHandlers = result; 2784 return nestedHandlers; 2785 } 2786 2787 /** 2788 * Returns the category reference path for the given value.<p> 2789 * 2790 * @param cms the cms context 2791 * @param value the xml content value 2792 * 2793 * @return the category reference path for the given value 2794 */ 2795 protected String getReferencePath(CmsObject cms, I_CmsXmlContentValue value) { 2796 2797 // get the original file instead of the temp file 2798 CmsFile file = value.getDocument().getFile(); 2799 String resourceName = cms.getSitePath(file); 2800 if (CmsWorkplace.isTemporaryFile(file)) { 2801 StringBuffer result = new StringBuffer(resourceName.length() + 2); 2802 result.append(CmsResource.getFolderPath(resourceName)); 2803 result.append(CmsResource.getName(resourceName).substring(1)); 2804 resourceName = result.toString(); 2805 } 2806 try { 2807 List<CmsResource> listsib = cms.readSiblings(resourceName, CmsResourceFilter.ALL); 2808 for (int i = 0; i < listsib.size(); i++) { 2809 CmsResource resource = listsib.get(i); 2810 // get the default locale of the resource and set the categories 2811 List<Locale> locales = getLocalesForResource(cms, cms.getSitePath(resource)); 2812 for (Locale l : locales) { 2813 if (value.getLocale().equals(l)) { 2814 return cms.getSitePath(resource); 2815 } 2816 } 2817 } 2818 } catch (CmsVfsResourceNotFoundException e) { 2819 // may hapen if editing a new resource 2820 if (LOG.isDebugEnabled()) { 2821 LOG.debug(e.getLocalizedMessage(), e); 2822 } 2823 } catch (CmsException e) { 2824 if (LOG.isErrorEnabled()) { 2825 LOG.error(e.getLocalizedMessage(), e); 2826 } 2827 } 2828 // if the locale can not be found, just take the current file 2829 return cms.getSitePath(file); 2830 } 2831 2832 /** 2833 * Returns the validation message to be displayed if a certain rule was violated.<p> 2834 * 2835 * @param cms the current users OpenCms context 2836 * @param value the value to validate 2837 * @param regex the rule that was violated 2838 * @param valueStr the string value of the given value 2839 * @param matchResult if false, the rule was negated 2840 * @param isWarning if true, this validation indicate a warning, otherwise an error 2841 * 2842 * @return the validation message to be displayed 2843 */ 2844 protected String getValidationMessage( 2845 CmsObject cms, 2846 I_CmsXmlContentValue value, 2847 String regex, 2848 String valueStr, 2849 boolean matchResult, 2850 boolean isWarning) { 2851 2852 String message = null; 2853 if (isWarning) { 2854 message = m_validationWarningMessages.get(value.getName()); 2855 } else { 2856 message = m_validationErrorMessages.get(value.getName()); 2857 } 2858 2859 if (message == null) { 2860 if (isWarning) { 2861 message = MESSAGE_VALIDATION_DEFAULT_WARNING; 2862 } else { 2863 message = MESSAGE_VALIDATION_DEFAULT_ERROR; 2864 } 2865 } 2866 2867 // create additional macro values 2868 Map<String, String> additionalValues = new HashMap<String, String>(); 2869 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_VALUE, valueStr); 2870 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_REGEX, ((!matchResult) ? "!" : "") + regex); 2871 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_PATH, value.getPath()); 2872 2873 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages( 2874 getMessages(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms))).setAdditionalMacros(additionalValues); 2875 2876 return resolver.resolveMacros(message); 2877 } 2878 2879 /** 2880 * Called when this content handler is initialized.<p> 2881 */ 2882 protected void init() { 2883 2884 m_elementMappings = new HashMap<String, List<String>>(); 2885 m_validationErrorRules = new HashMap<String, String>(); 2886 m_validationErrorMessages = new HashMap<String, String>(); 2887 m_validationWarningRules = new HashMap<String, String>(); 2888 m_validationWarningMessages = new HashMap<String, String>(); 2889 m_defaultValues = new HashMap<String, String>(); 2890 m_configurationValues = new HashMap<String, String>(); 2891 m_searchSettings = new HashMap<String, SearchContentType>(); 2892 m_relations = new HashMap<String, CmsRelationType>(); 2893 m_relationChecks = new HashMap<String, Boolean>(); 2894 m_previewLocation = null; 2895 m_modelFolder = null; 2896 m_tabs = new ArrayList<CmsXmlContentTab>(); 2897 m_cssHeadIncludes = new LinkedHashSet<String>(); 2898 m_jsHeadIncludes = new LinkedHashSet<String>(); 2899 m_settings = new LinkedHashMap<String, CmsXmlContentProperty>(); 2900 m_titleMappings = new ArrayList<String>(2); 2901 m_formatters = new ArrayList<CmsFormatterBean>(); 2902 m_searchFields = new HashMap<String, CmsSearchField>(); 2903 m_searchFieldsPage = new HashMap<String, CmsSearchField>(); 2904 m_allowedTemplates = new CmsDefaultSet<String>(); 2905 m_allowedTemplates.setDefaultMembership(true); 2906 m_displayTypes = new HashMap<String, DisplayType>(); 2907 m_synchronizations = new ArrayList<String>(); 2908 m_editorChangeHandlers = new ArrayList<I_CmsXmlContentEditorChangeHandler>(); 2909 m_nestedFormatterElements = new HashSet<String>(); 2910 try ( 2911 InputStream stream = CmsDefaultXmlContentHandler.class.getResourceAsStream("simple-searchsetting-configs.st")) { 2912 m_searchTemplateGroup = CmsStringUtil.readStringTemplateGroup(stream); 2913 } catch (IOException e) { 2914 LOG.error(e.getLocalizedMessage(), e); 2915 } 2916 } 2917 2918 /** 2919 * Initializes the default values for this content handler.<p> 2920 * 2921 * Using the default values from the appinfo node, it's possible to have more 2922 * sophisticated logic for generating the defaults then just using the XML schema "default" 2923 * attribute.<p> 2924 * 2925 * @param root the "defaults" element from the appinfo node of the XML content definition 2926 * @param contentDefinition the content definition the default values belong to 2927 * @throws CmsXmlException if something goes wrong 2928 */ 2929 protected void initDefaultValues(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 2930 2931 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_DEFAULT); 2932 while (i.hasNext()) { 2933 // iterate all "default" elements in the "defaults" node 2934 Element element = i.next(); 2935 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 2936 String defaultValue = element.attributeValue(APPINFO_ATTR_VALUE); 2937 String resolveMacrosValue = element.attributeValue(APPINFO_ATTR_RESOLVE_MACROS); 2938 if ((elementName != null) && (defaultValue != null)) { 2939 // add a default value mapping for the element 2940 addDefault(contentDefinition, elementName, defaultValue, resolveMacrosValue); 2941 } 2942 } 2943 } 2944 2945 /** 2946 * Initializes the default complex widget.<p> 2947 * 2948 * @param element the element in which the default complex widget is configured 2949 */ 2950 protected void initDefaultWidget(Element element) { 2951 2952 m_defaultWidget = element.attributeValue(APPINFO_ATTR_WIDGET); 2953 m_defaultWidgetConfig = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 2954 try { 2955 m_defaultWidgetInstance = (I_CmsComplexWidget)(Class.forName(m_defaultWidget).newInstance()); 2956 } catch (Exception e) { 2957 LOG.error(e.getLocalizedMessage(), e); 2958 } 2959 } 2960 2961 /** 2962 * Initializes the edit handler.<p> 2963 * 2964 * @param handlerElement the edit handler element 2965 */ 2966 protected void initEditHandler(Element handlerElement) { 2967 2968 String editHandlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS); 2969 Map<String, String> params = Maps.newHashMap(); 2970 Element paramsElement = handlerElement.element(APPINFO_PARAMETERS); 2971 if (paramsElement != null) { 2972 for (Element paramElement : paramsElement.elements(APPINFO_PARAM)) { 2973 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 2974 String value = paramElement.getText(); 2975 params.put(name, value); 2976 } 2977 } 2978 try { 2979 m_editHandler = (I_CmsEditHandler)Class.forName(editHandlerClass).newInstance(); 2980 m_editHandler.setParameters(params); 2981 } catch (Exception e) { 2982 LOG.error(e.getMessage(), e); 2983 } 2984 } 2985 2986 /** 2987 * Initializes the editor change handlers.<p> 2988 * 2989 * @param element the editorchangehandlers node of the app info 2990 */ 2991 protected void initEditorChangeHandlers(Element element) { 2992 2993 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_EDITOR_CHANGE_HANDLER); 2994 while (i.hasNext()) { 2995 // iterate all "default" elements in the "defaults" node 2996 Element handlerElement = i.next(); 2997 String handlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS); 2998 String configuration = handlerElement.attributeValue(APPINFO_ATTR_CONFIGURATION); 2999 String scope = handlerElement.attributeValue(APPINFO_ATTR_SCOPE); 3000 try { 3001 I_CmsXmlContentEditorChangeHandler handler = (I_CmsXmlContentEditorChangeHandler)Class.forName( 3002 handlerClass).newInstance(); 3003 handler.setConfiguration(configuration); 3004 handler.setScope(scope); 3005 m_editorChangeHandlers.add(handler); 3006 } catch (Exception e) { 3007 LOG.error(e.getLocalizedMessage(), e); 3008 } 3009 } 3010 } 3011 3012 /** 3013 * Processes a single field definition.<p> 3014 * 3015 * @param elem the parent element 3016 * @param contentDef the content definition 3017 * 3018 * @throws CmsXmlException if something goes wrong 3019 */ 3020 protected void initField(Element elem, CmsXmlContentDefinition contentDef) throws CmsXmlException { 3021 3022 String nameVal = elem.elementText(CmsConfigurationReader.N_PROPERTY_NAME); 3023 if (nameVal == null) { 3024 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_BAD_FIELD_NAME_1, nameVal)); 3025 } 3026 final String name = nameVal.trim(); 3027 3028 String ruleRegex = elem.elementText(CmsConfigurationReader.N_RULE_REGEX); 3029 String ruleType = elem.elementText(CmsConfigurationReader.N_RULE_TYPE); 3030 String error = elem.elementText(CmsConfigurationReader.N_ERROR); 3031 if (error == null) { 3032 error = ""; 3033 } 3034 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(ruleRegex)) { 3035 addValidationRule(contentDef, name, ruleRegex, error, "warning".equalsIgnoreCase(ruleType)); 3036 } 3037 3038 String defaultValue = elem.elementText(CmsConfigurationReader.N_DEFAULT); 3039 String defaultResolveMacros = elem.elementTextTrim(FieldSettingElems.DefaultResolveMacros.name()); 3040 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(defaultValue)) { 3041 addDefault(contentDef, name, defaultValue, defaultResolveMacros); 3042 } 3043 3044 String widget = elem.elementText(CmsConfigurationReader.N_WIDGET); 3045 String widgetConfig = elem.elementText(CmsConfigurationReader.N_WIDGET_CONFIG); 3046 if (widget != null) { 3047 addWidget(contentDef, name, widget); 3048 } 3049 if (widgetConfig != null) { 3050 widgetConfig = widgetConfig.trim(); 3051 addConfiguration(contentDef, name, widgetConfig); 3052 } 3053 3054 String niceName = elem.elementText(CmsConfigurationReader.N_DISPLAY_NAME); 3055 if (niceName != null) { 3056 m_fieldNiceNames.put(name, niceName); 3057 } 3058 String description = elem.elementText(CmsConfigurationReader.N_DESCRIPTION); 3059 if (description != null) { 3060 m_fieldDescriptions.put(name, description); 3061 } 3062 for (Element mappingElem : elem.elements(FieldSettingElems.Mapping.name())) { 3063 String mapTo = mappingElem.elementText(FieldSettingElems.MapTo.name()); 3064 String useDefault = mappingElem.elementText(FieldSettingElems.UseDefault.name()); 3065 if (mapTo != null) { 3066 addMapping(contentDef, name, mapTo, useDefault); 3067 } 3068 } 3069 String display = elem.elementTextTrim(FieldSettingElems.Display.name()); 3070 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(display)) { 3071 try { 3072 addDisplayType(contentDef, name, DisplayType.valueOf(display)); 3073 } catch (Exception e) { 3074 LOG.error(e.getLocalizedMessage(), e); 3075 } 3076 } 3077 String synchronization = elem.elementTextTrim(FieldSettingElems.Synchronization.name()); 3078 if (Boolean.parseBoolean(synchronization)) { 3079 m_synchronizations.add(name); 3080 } 3081 for (Element relElem : elem.elements(FieldSettingElems.Relation.name())) { 3082 String type = relElem.elementTextTrim(FieldSettingElems.Type.name()); 3083 String invalidate = relElem.elementTextTrim(FieldSettingElems.Invalidate.name()); 3084 if (type != null) { 3085 type = type.toLowerCase(); 3086 } 3087 if (invalidate != null) { 3088 invalidate = invalidate.toLowerCase(); 3089 } 3090 addCheckRule(contentDef, name, invalidate, type); 3091 } 3092 3093 for (Element visElem : elem.elements(FieldSettingElems.Visibility.name())) { 3094 String params = visElem.getText(); 3095 VisibilityConfiguration visConfig = createVisibilityConfiguration(null, params); 3096 m_visibilityConfigurations.put(name, visConfig); 3097 } 3098 3099 for (Element visElem : elem.elements(FieldSettingElems.FieldVisibility.name())) { 3100 String className = visElem.elementTextTrim(FieldSettingElems.Class.name()); 3101 String params = visElem.elementTextTrim(FieldSettingElems.Params.name()); 3102 VisibilityConfiguration visConfig = createVisibilityConfiguration(className, params); 3103 m_visibilityConfigurations.put(name, visConfig); 3104 } 3105 3106 String nestedFormatter = elem.elementTextTrim(FieldSettingElems.NestedFormatter.name()); 3107 if (Boolean.parseBoolean(nestedFormatter)) { 3108 m_nestedFormatterElements.add(name); 3109 } 3110 3111 String search = elem.elementTextTrim(FieldSettingElems.Search.name()); 3112 if (search != null) { 3113 addSimpleSearchSetting(contentDef, name, search); 3114 } 3115 3116 String ifInvalidRelationStr = elem.elementTextTrim(FieldSettingElems.IfInvalidRelation.name()); 3117 if (CmsStringUtil.isEmptyOrWhitespaceOnly(ifInvalidRelationStr)) { 3118 ifInvalidRelationStr = null; 3119 } 3120 if (ifInvalidRelationStr != null) { 3121 if (name.contains("[") || name.contains("/")) { 3122 LOG.error("Only simple field names allowed for the IfInvalidRelation field setting."); 3123 } else { 3124 try { 3125 InvalidRelationAction ifInvalidRelation = InvalidRelationAction.valueOf(ifInvalidRelationStr); 3126 m_invalidRelationActions.put(name, ifInvalidRelation); 3127 } catch (Exception e) { 3128 LOG.error(e.getLocalizedMessage(), e); 3129 } 3130 } 3131 3132 } 3133 3134 for (Element changeHandlerElem : elem.elements(N_CHANGEHANDLER)) { 3135 String config = changeHandlerElem.attributeValue(A_CONFIGURATION); 3136 String className = changeHandlerElem.getText().trim(); 3137 CmsChangeHandlerConfig entry = new CmsChangeHandlerConfig(name, className, config); 3138 m_changeHandlerConfigs.add(entry); 3139 3140 } 3141 } 3142 3143 /** 3144 * Processes all field declarations in the schema.<p> 3145 * 3146 * @param parent the parent element 3147 * @param contentDef the content definition 3148 * 3149 * @throws CmsXmlException if something goes wrong 3150 */ 3151 protected void initFields(Element parent, CmsXmlContentDefinition contentDef) throws CmsXmlException { 3152 3153 for (Element fieldElem : parent.elements(N_SETTING)) { 3154 initField(fieldElem, contentDef); 3155 } 3156 } 3157 3158 /** 3159 * Initializes the formatters for this content handler.<p> 3160 * 3161 * @param root the "formatters" element from the appinfo node of the XML content definition 3162 * @param contentDefinition the content definition the formatters belong to 3163 */ 3164 protected void initFormatters(Element root, CmsXmlContentDefinition contentDefinition) { 3165 3166 // reading the include resources common for all formatters 3167 Iterator<Element> itFormatter = CmsXmlGenericWrapper.elementIterator(root, APPINFO_FORMATTER); 3168 while (itFormatter.hasNext()) { 3169 // iterate all "formatter" elements in the "formatters" node 3170 Element element = itFormatter.next(); 3171 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3172 if (CmsStringUtil.isEmptyOrWhitespaceOnly(type)) { 3173 // if not set use "*" as default for type 3174 type = CmsFormatterBean.WILDCARD_TYPE; 3175 } 3176 String jspRootPath = element.attributeValue(APPINFO_ATTR_URI); 3177 String minWidthStr = element.attributeValue(APPINFO_ATTR_MINWIDTH); 3178 String maxWidthStr = element.attributeValue(APPINFO_ATTR_MAXWIDTH); 3179 String preview = element.attributeValue(APPINFO_ATTR_PREVIEW); 3180 String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT); 3181 m_formatters.add( 3182 new CmsFormatterBean( 3183 type, 3184 jspRootPath, 3185 minWidthStr, 3186 maxWidthStr, 3187 preview, 3188 searchContent, 3189 contentDefinition.getSchemaLocation())); 3190 } 3191 } 3192 3193 /** 3194 * Initializes the head includes for this content handler.<p> 3195 * 3196 * @param root the "headincludes" element from the appinfo node of the XML content definition 3197 * @param contentDefinition the content definition the head-includes belong to 3198 */ 3199 protected void initHeadIncludes(Element root, CmsXmlContentDefinition contentDefinition) { 3200 3201 Iterator<Element> itInclude = CmsXmlGenericWrapper.elementIterator(root, APPINFO_HEAD_INCLUDE); 3202 while (itInclude.hasNext()) { 3203 Element element = itInclude.next(); 3204 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3205 String uri = element.attributeValue(APPINFO_ATTR_URI); 3206 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(uri)) { 3207 if (ATTRIBUTE_INCLUDE_TYPE_CSS.equals(type)) { 3208 m_cssHeadIncludes.add(uri); 3209 } else if (ATTRIBUTE_INCLUDE_TYPE_JAVASCRIPT.equals(type)) { 3210 m_jsHeadIncludes.add(uri); 3211 } 3212 } 3213 } 3214 } 3215 3216 /** 3217 * Reads the JSON renderer settings. 3218 * 3219 * @param element the configuration XML element 3220 */ 3221 protected void initJsonRenderer(Element element) { 3222 3223 String cls = element.attributeValue(APPINFO_ATTR_CLASS); 3224 Map<String, String> params = new HashMap<>(); 3225 for (Element paramElement : element.elements(APPINFO_PARAM)) { 3226 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 3227 String value = paramElement.getText(); 3228 params.put(name, value); 3229 } 3230 m_jsonRendererSettings = new JsonRendererSettings(cls, params); 3231 3232 } 3233 3234 /** 3235 * Initializes the layout for this content handler.<p> 3236 * 3237 * Unless otherwise instructed, the editor uses one specific GUI widget for each 3238 * XML value schema type. For example, for a {@link org.opencms.xml.types.CmsXmlStringValue} 3239 * the default widget is the {@link org.opencms.widgets.CmsInputWidget}. 3240 * However, certain values can also use more then one widget, for example you may 3241 * also use a {@link org.opencms.widgets.CmsCheckboxWidget} for a String value, 3242 * and as a result the Strings possible values would be either <code>"false"</code> or <code>"true"</code>, 3243 * but nevertheless be a String.<p> 3244 * 3245 * The widget to use can further be controlled using the <code>widget</code> attribute. 3246 * You can specify either a valid widget alias such as <code>StringWidget</code>, 3247 * or the name of a Java class that implements <code>{@link I_CmsWidget}</code>.<p> 3248 * 3249 * Configuration options to the widget can be passed using the <code>configuration</code> 3250 * attribute. You can specify any String as configuration. This String is then passed 3251 * to the widget during initialization. It's up to the individual widget implementation 3252 * to interpret this configuration String.<p> 3253 * 3254 * @param root the "layouts" element from the appinfo node of the XML content definition 3255 * @param contentDefinition the content definition the layout belongs to 3256 * 3257 * @throws CmsXmlException if something goes wrong 3258 */ 3259 protected void initLayouts(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3260 3261 m_useAcacia = safeParseBoolean(root.attributeValue(ATTR_USE_ACACIA), true); 3262 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_LAYOUT); 3263 while (i.hasNext()) { 3264 // iterate all "layout" elements in the "layouts" node 3265 Element element = i.next(); 3266 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3267 String widgetClassOrAlias = element.attributeValue(APPINFO_ATTR_WIDGET); 3268 String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 3269 String displayStr = element.attributeValue(APPINFO_ATTR_DISPLAY); 3270 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(displayStr) && (elementName != null)) { 3271 addDisplayType(contentDefinition, elementName, DisplayType.valueOf(displayStr)); 3272 } 3273 if ((elementName != null) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(widgetClassOrAlias)) { 3274 // add a widget mapping for the element 3275 addWidget(contentDefinition, elementName, widgetClassOrAlias); 3276 if (configuration != null) { 3277 addConfiguration(contentDefinition, elementName, configuration); 3278 } 3279 } 3280 } 3281 } 3282 3283 /** 3284 * Initializes the element mappings for this content handler.<p> 3285 * 3286 * Element mappings allow storing values from the XML content in other locations. 3287 * For example, if you have an element called "Title", it's likely a good idea to 3288 * store the value of this element also in the "Title" property of a XML content resource.<p> 3289 * 3290 * @param root the "mappings" element from the appinfo node of the XML content definition 3291 * @param contentDefinition the content definition the mappings belong to 3292 * @throws CmsXmlException if something goes wrong 3293 */ 3294 protected void initMappings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3295 3296 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_MAPPING); 3297 while (i.hasNext()) { 3298 // iterate all "mapping" elements in the "mappings" node 3299 Element element = i.next(); 3300 // this is a mapping node 3301 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3302 String maptoName = element.attributeValue(APPINFO_ATTR_MAPTO); 3303 String useDefault = element.attributeValue(APPINFO_ATTR_USE_DEFAULT); 3304 if ((elementName != null) && (maptoName != null)) { 3305 // add the element mapping 3306 addMapping(contentDefinition, elementName, maptoName, useDefault); 3307 } 3308 } 3309 } 3310 3311 /** 3312 * Initializes the folder containing the model file(s) for this content handler.<p> 3313 * 3314 * @param root the "modelfolder" element from the appinfo node of the XML content definition 3315 * @param contentDefinition the content definition the model folder belongs to 3316 * @throws CmsXmlException if something goes wrong 3317 */ 3318 protected void initModelFolder(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3319 3320 String master = root.attributeValue(APPINFO_ATTR_URI); 3321 if (master == null) { 3322 throw new CmsXmlException( 3323 Messages.get().container( 3324 Messages.ERR_XMLCONTENT_MISSING_MODELFOLDER_URI_2, 3325 root.getName(), 3326 contentDefinition.getSchemaLocation())); 3327 } 3328 m_modelFolder = master; 3329 } 3330 3331 /** 3332 * Initializes the nested formatter fields.<p> 3333 * 3334 * @param element the formatters element 3335 * @param contentDefinition the content definition 3336 * 3337 * @throws CmsXmlException in case something goes wron 3338 */ 3339 protected void initNestedFormatters(Element element, CmsXmlContentDefinition contentDefinition) 3340 throws CmsXmlException { 3341 3342 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_NESTED_FORMATTER); 3343 while (i.hasNext()) { 3344 // iterate all "default" elements in the "defaults" node 3345 Element handlerElement = i.next(); 3346 String formatterElement = handlerElement.attributeValue(APPINFO_ATTR_ELEMENT); 3347 addNestedFormatter(formatterElement, contentDefinition); 3348 } 3349 } 3350 3351 /** 3352 * Initializes the parameters from the schema.<p> 3353 * 3354 * @param root the parameter root element 3355 */ 3356 protected void initParameters(Element root) { 3357 3358 m_parameters.clear(); 3359 for (Element paramElement : root.elements(APPINFO_PARAM)) { 3360 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 3361 String value = paramElement.getText(); 3362 m_parameters.put(name, value); 3363 } 3364 3365 } 3366 3367 /** 3368 * Initializes the preview location for this content handler.<p> 3369 * 3370 * @param root the "preview" element from the appinfo node of the XML content definition 3371 * @param contentDefinition the content definition the validation rules belong to 3372 * @throws CmsXmlException if something goes wrong 3373 */ 3374 protected void initPreview(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3375 3376 String preview = root.attributeValue(APPINFO_ATTR_URI); 3377 if (preview == null) { 3378 throw new CmsXmlException( 3379 Messages.get().container( 3380 Messages.ERR_XMLCONTENT_MISSING_PREVIEW_URI_2, 3381 root.getName(), 3382 contentDefinition.getSchemaLocation())); 3383 } 3384 m_previewLocation = preview; 3385 } 3386 3387 /** 3388 * Initializes the relation configuration for this content handler.<p> 3389 * 3390 * OpenCms performs link checks for all OPTIONAL links defined in XML content values of type 3391 * OpenCmsVfsFile. However, for most projects in the real world a more fine-grained control 3392 * over the link check process is required. For these cases, individual relation behavior can 3393 * be defined for the appinfo node.<p> 3394 * 3395 * Additional here can be defined an optional type for the relations, for instance.<p> 3396 * 3397 * @param root the "relations" element from the appinfo node of the XML content definition 3398 * @param contentDefinition the content definition the check rules belong to 3399 * 3400 * @throws CmsXmlException if something goes wrong 3401 */ 3402 protected void initRelations(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3403 3404 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_RELATION); 3405 while (i.hasNext()) { 3406 // iterate all "checkrule" elements in the "checkrule" node 3407 Element element = i.next(); 3408 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3409 String invalidate = element.attributeValue(APPINFO_ATTR_INVALIDATE); 3410 if (invalidate != null) { 3411 invalidate = invalidate.toUpperCase(); 3412 } 3413 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3414 if (type != null) { 3415 type = type.toLowerCase(); 3416 } 3417 if (elementName != null) { 3418 // add a check rule for the element 3419 addCheckRule(contentDefinition, elementName, invalidate, type); 3420 } 3421 } 3422 } 3423 3424 /** 3425 * Initializes the resource bundle to use for localized messages in this content handler.<p> 3426 * 3427 * @param root the "resourcebundle" element from the appinfo node of the XML content definition 3428 * @param contentDefinition the content definition the validation rules belong to 3429 * @param single if <code>true</code> we process the classic sinle line entry, otherwise it's the multiple line setting 3430 * 3431 * @throws CmsXmlException if something goes wrong 3432 */ 3433 protected void initResourceBundle(Element root, CmsXmlContentDefinition contentDefinition, boolean single) 3434 throws CmsXmlException { 3435 3436 if (m_messageBundleNames == null) { 3437 // it's uncommon to have more then one bundle so just initialize an array length of 2 3438 m_messageBundleNames = new ArrayList<String>(2); 3439 } 3440 3441 if (single) { 3442 // single "resourcebundle" node 3443 3444 String messageBundleName = root.attributeValue(APPINFO_ATTR_NAME); 3445 if (messageBundleName == null) { 3446 throw new CmsXmlException( 3447 Messages.get().container( 3448 Messages.ERR_XMLCONTENT_MISSING_RESOURCE_BUNDLE_NAME_2, 3449 root.getName(), 3450 contentDefinition.getSchemaLocation())); 3451 } 3452 if (!m_messageBundleNames.contains(messageBundleName)) { 3453 // avoid duplicates 3454 m_messageBundleNames.add(messageBundleName); 3455 } 3456 // clear the cached resource bundles for this bundle 3457 CmsResourceBundleLoader.flushBundleCache(messageBundleName, false); 3458 3459 } else { 3460 // multiple "resourcebundles" node 3461 3462 // get an iterator for all "propertybundle" subnodes 3463 Iterator<Element> propertybundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_PROPERTYBUNDLE); 3464 while (propertybundles.hasNext()) { 3465 // iterate all "propertybundle" elements in the "resourcebundle" node 3466 Element propBundle = propertybundles.next(); 3467 String propertyBundleName = propBundle.attributeValue(APPINFO_ATTR_NAME); 3468 if (!m_messageBundleNames.contains(propertyBundleName)) { 3469 // avoid duplicates 3470 m_messageBundleNames.add(propertyBundleName); 3471 } 3472 // clear the cached resource bundles for this bundle 3473 CmsResourceBundleLoader.flushBundleCache(propertyBundleName, false); 3474 } 3475 3476 // get an iterator for all "xmlbundle" subnodes 3477 Iterator<Element> xmlbundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_XMLBUNDLE); 3478 while (xmlbundles.hasNext()) { 3479 Element xmlbundle = xmlbundles.next(); 3480 String xmlBundleName = xmlbundle.attributeValue(APPINFO_ATTR_NAME); 3481 // cache the bundle from the XML 3482 if (!m_messageBundleNames.contains(xmlBundleName)) { 3483 // avoid duplicates 3484 m_messageBundleNames.add(xmlBundleName); 3485 } 3486 // clear the cached resource bundles for this bundle 3487 CmsResourceBundleLoader.flushBundleCache(xmlBundleName, true); 3488 Iterator<Element> bundles = CmsXmlGenericWrapper.elementIterator(xmlbundle, APPINFO_BUNDLE); 3489 while (bundles.hasNext()) { 3490 // iterate all "bundle" elements in the "xmlbundle" node 3491 Element bundle = bundles.next(); 3492 String localeStr = bundle.attributeValue(APPINFO_ATTR_LOCALE); 3493 Locale locale; 3494 if (CmsStringUtil.isEmptyOrWhitespaceOnly(localeStr)) { 3495 // no locale set, so use no locale 3496 locale = null; 3497 } else { 3498 // use provided locale 3499 locale = CmsLocaleManager.getLocale(localeStr); 3500 } 3501 boolean isDefaultLocaleAndNotNull = (locale != null) 3502 && locale.equals(CmsLocaleManager.getDefaultLocale()); 3503 3504 CmsListResourceBundle xmlBundle = null; 3505 3506 Iterator<Element> resources = CmsXmlGenericWrapper.elementIterator(bundle, APPINFO_RESOURCE); 3507 while (resources.hasNext()) { 3508 // now collect all resource bundle keys 3509 Element resource = resources.next(); 3510 String key = resource.attributeValue(APPINFO_ATTR_KEY); 3511 String value = resource.attributeValue(APPINFO_ATTR_VALUE); 3512 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) { 3513 // read from inside XML tag if value attribute is not set 3514 value = resource.getTextTrim(); 3515 } 3516 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(key) 3517 && CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 3518 if (xmlBundle == null) { 3519 // use lazy initilaizing of the bundle 3520 xmlBundle = new CmsListResourceBundle(); 3521 } 3522 xmlBundle.addMessage(key.trim(), value.trim()); 3523 } 3524 } 3525 if (xmlBundle != null) { 3526 CmsResourceBundleLoader.addBundleToCache(xmlBundleName, locale, xmlBundle); 3527 if (isDefaultLocaleAndNotNull) { 3528 CmsResourceBundleLoader.addBundleToCache(xmlBundleName, null, xmlBundle); 3529 } 3530 } 3531 } 3532 } 3533 } 3534 } 3535 3536 /** 3537 * Initializes the search exclusions values for this content handler.<p> 3538 * 3539 * For the full text search, the value of all elements in one locale of the XML content are combined 3540 * to one big text, which is referred to as the "content" in the context of the full text search. 3541 * With this option, it is possible to hide certain elements from this "content" that does not make sense 3542 * to include in the full text search.<p> 3543 * 3544 * @param root the "searchsettings" element from the appinfo node of the XML content definition 3545 * @param contentDefinition the content definition the default values belong to 3546 * 3547 * @throws CmsXmlException if something goes wrong 3548 */ 3549 protected void initSearchSettings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3550 3551 String containerPageOnly = root.attributeValue(APPINFO_ATTR_CONTAINER_PAGE_ONLY); 3552 if (!CmsStringUtil.isEmpty(containerPageOnly)) { 3553 m_containerPageOnly = Boolean.valueOf(containerPageOnly).booleanValue(); 3554 } 3555 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SEARCHSETTING); 3556 while (i.hasNext()) { 3557 Element element = i.next(); 3558 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3559 String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT); 3560 SearchContentType searchContentType = SearchContentType.fromString(searchContent); 3561 if (elementName != null) { 3562 addSearchSetting(contentDefinition, elementName, searchContentType); 3563 } 3564 Iterator<Element> it = CmsXmlGenericWrapper.elementIterator(element, APPINFO_SOLR_FIELD); 3565 Element solrElement; 3566 while (it.hasNext()) { 3567 solrElement = it.next(); 3568 3569 String localeNames = solrElement.attributeValue(APPINFO_ATTR_LOCALE); 3570 boolean localized = true; 3571 if ((localeNames != null) 3572 && (localeNames.equals("none") || localeNames.equals("null") || localeNames.trim().equals(""))) { 3573 localized = false; 3574 } 3575 List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales(localeNames); 3576 if (localized && ((locales == null) || locales.isEmpty())) { 3577 locales = OpenCms.getLocaleManager().getAvailableLocales(); 3578 } else if (locales.isEmpty()) { 3579 locales.add(CmsLocaleManager.getDefaultLocale()); 3580 } 3581 for (Locale locale : locales) { 3582 String targetField = solrElement.attributeValue(APPINFO_ATTR_TARGET_FIELD); 3583 if (localized) { 3584 targetField = targetField + "_" + locale.toString(); 3585 } 3586 String sourceField = solrElement.attributeValue(APPINFO_ATTR_SOURCE_FIELD); 3587 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(sourceField)) { 3588 int lastUnderScore = sourceField.lastIndexOf("_"); 3589 if (lastUnderScore > 0) { 3590 sourceField = sourceField.substring(lastUnderScore); 3591 } 3592 targetField += sourceField; 3593 } 3594 3595 String copyFieldNames = solrElement.attributeValue(APPINFO_ATTR_COPY_FIELDS, ""); 3596 List<String> copyFields = CmsStringUtil.splitAsList(copyFieldNames, ','); 3597 String defaultValue = solrElement.attributeValue(APPINFO_ATTR_DEFAULT); 3598 CmsSolrField field = new CmsSolrField(targetField, copyFields, locale, defaultValue); 3599 3600 // create the field mappings for this element 3601 Iterator<Element> ite = CmsXmlGenericWrapper.elementIterator(solrElement, APPINFO_ATTR_MAPPING); 3602 while (ite.hasNext()) { 3603 Element mappingElement = ite.next(); 3604 field.addMapping(createSearchFieldMapping(contentDefinition, mappingElement, locale)); 3605 } 3606 3607 // if no mapping was defined yet, create a mapping for the element itself 3608 if ((field.getMappings() == null) || field.getMappings().isEmpty()) { 3609 String param = localized ? (locale.toString() + "|" + elementName) : elementName; 3610 CmsSearchFieldMapping map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.ITEM, param); 3611 field.addMapping(map); 3612 } 3613 Set<I_CmsXmlContentHandler.MappingType> mappingTypes = parseSearchMappingTypes(solrElement); 3614 for (I_CmsXmlContentHandler.MappingType type : mappingTypes) { 3615 addSearchField(contentDefinition, field, type); 3616 } 3617 } 3618 } 3619 } 3620 } 3621 3622 /** 3623 * Initializes the element settings for this content handler.<p> 3624 * 3625 * @param root the "settings" element from the appinfo node of the XML content definition 3626 * @param contentDefinition the content definition the element settings belong to 3627 */ 3628 protected void initSettings(Element root, CmsXmlContentDefinition contentDefinition) { 3629 3630 Iterator<Element> itProperties = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SETTING); 3631 while (itProperties.hasNext()) { 3632 Element element = itProperties.next(); 3633 CmsXmlContentProperty setting = new CmsXmlContentProperty( 3634 element.attributeValue(APPINFO_ATTR_NAME), 3635 element.attributeValue(APPINFO_ATTR_TYPE), 3636 element.attributeValue(APPINFO_ATTR_WIDGET), 3637 element.attributeValue(APPINFO_ATTR_WIDGET_CONFIG), 3638 element.attributeValue(APPINFO_ATTR_RULE_REGEX), 3639 element.attributeValue(APPINFO_ATTR_RULE_TYPE), 3640 element.attributeValue(APPINFO_ATTR_DEFAULT), 3641 element.attributeValue(APPINFO_ATTR_NICE_NAME), 3642 element.attributeValue(APPINFO_ATTR_DESCRIPTION), 3643 element.attributeValue(APPINFO_ATTR_ERROR), 3644 element.attributeValue(APPINFO_ATTR_PREFERFOLDER)); 3645 String name = setting.getName(); 3646 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(name)) { 3647 m_settings.put(name, setting); 3648 } 3649 } 3650 } 3651 3652 /** 3653 * Initializes the locale synchronizations elements.<p> 3654 * 3655 * @param root the synchronizations element of the content schema appinfo. 3656 * @param contentDefinition the content definition 3657 */ 3658 protected void initSynchronizations(Element root, CmsXmlContentDefinition contentDefinition) { 3659 3660 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_SYNCHRONIZATION)); 3661 for (Element element : elements) { 3662 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3663 m_synchronizations.add(elementName); 3664 } 3665 } 3666 3667 /** 3668 * Initializes the tabs for this content handler.<p> 3669 * 3670 * @param root the "tabs" element from the appinfo node of the XML content definition 3671 * @param contentDefinition the content definition the tabs belong to 3672 */ 3673 protected void initTabs(Element root, CmsXmlContentDefinition contentDefinition) { 3674 3675 if (Boolean.valueOf(root.attributeValue(APPINFO_ATTR_USEALL, CmsStringUtil.FALSE)).booleanValue()) { 3676 // all first level elements should be treated as tabs 3677 Iterator<I_CmsXmlSchemaType> i = contentDefinition.getTypeSequence().iterator(); 3678 while (i.hasNext()) { 3679 // get the type 3680 I_CmsXmlSchemaType type = i.next(); 3681 m_tabs.add(new CmsXmlContentTab(type.getName())); 3682 } 3683 } else { 3684 // manual definition of tabs 3685 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_TAB); 3686 while (i.hasNext()) { 3687 // iterate all "tab" elements in the "tabs" node 3688 Element element = i.next(); 3689 // this is a tab node 3690 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3691 String collapseValue = element.attributeValue(APPINFO_ATTR_COLLAPSE, CmsStringUtil.TRUE); 3692 Node descriptionNode = element.selectSingleNode(APPINFO_ATTR_DESCRIPTION + "/text()"); 3693 String description = null; 3694 if (descriptionNode != null) { 3695 description = descriptionNode.getText(); 3696 } else { 3697 description = element.attributeValue(APPINFO_ATTR_DESCRIPTION); 3698 } 3699 3700 String tabName = element.attributeValue(APPINFO_ATTR_NAME, elementName); 3701 if (elementName != null) { 3702 // add the element tab 3703 m_tabs.add( 3704 new CmsXmlContentTab( 3705 elementName, 3706 Boolean.valueOf(collapseValue).booleanValue(), 3707 tabName, 3708 description)); 3709 } 3710 } 3711 // check if first element has been defined as tab 3712 I_CmsXmlSchemaType type = contentDefinition.getTypeSequence().get(0); 3713 CmsXmlContentTab tab = new CmsXmlContentTab(type.getName()); 3714 if (!m_tabs.contains(tab)) { 3715 m_tabs.add(0, tab); 3716 } 3717 } 3718 } 3719 3720 /** 3721 * Initializes the forbidden template contexts.<p> 3722 * 3723 * @param root the root XML element 3724 * @param contentDefinition the content definition 3725 */ 3726 protected void initTemplates(Element root, CmsXmlContentDefinition contentDefinition) { 3727 3728 String strEnabledByDefault = root.attributeValue(ATTR_ENABLED_BY_DEFAULT); 3729 m_allowedTemplates.setDefaultMembership(safeParseBoolean(strEnabledByDefault, true)); 3730 List<Node> elements = root.selectNodes(APPINFO_TEMPLATE); 3731 for (Node elem : elements) { 3732 boolean enabled = safeParseBoolean(((Element)elem).attributeValue(ATTR_ENABLED), true); 3733 String templateName = elem.getText().trim(); 3734 m_allowedTemplates.setContains(templateName, enabled); 3735 } 3736 m_allowedTemplates.freeze(); 3737 } 3738 3739 /** 3740 * Initializes the validation rules this content handler.<p> 3741 * 3742 * OpenCms always performs XML schema validation for all XML contents. However, 3743 * for most projects in the real world a more fine-grained control over the validation process is 3744 * required. For these cases, individual validation rules can be defined for the appinfo node.<p> 3745 * 3746 * @param root the "validationrules" element from the appinfo node of the XML content definition 3747 * @param contentDefinition the content definition the validation rules belong to 3748 * 3749 * @throws CmsXmlException if something goes wrong 3750 */ 3751 protected void initValidationRules(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3752 3753 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_RULE)); 3754 elements.addAll(CmsXmlGenericWrapper.elements(root, APPINFO_VALIDATIONRULE)); 3755 Iterator<Element> i = elements.iterator(); 3756 while (i.hasNext()) { 3757 // iterate all "rule" or "validationrule" elements in the "validationrules" node 3758 Element element = i.next(); 3759 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3760 String regex = element.attributeValue(APPINFO_ATTR_REGEX); 3761 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3762 if (type != null) { 3763 type = type.toLowerCase(); 3764 } 3765 String message = element.attributeValue(APPINFO_ATTR_MESSAGE); 3766 if ((elementName != null) && (regex != null)) { 3767 // add a validation rule for the element 3768 addValidationRule( 3769 contentDefinition, 3770 elementName, 3771 regex, 3772 message, 3773 APPINFO_ATTR_TYPE_WARNING.equals(type)); 3774 } 3775 } 3776 } 3777 3778 /** 3779 * Initializes the content visibility settings.<p> 3780 * 3781 * @param root the visibilities appinfo element 3782 * @param contentDefinition the content definition 3783 */ 3784 protected void initVisibilities(Element root, CmsXmlContentDefinition contentDefinition) { 3785 3786 m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>(); 3787 String mainHandlerClassName = root.attributeValue(APPINFO_ATTR_CLASS); 3788 // using self as the default visibility handler implementation 3789 I_CmsXmlContentVisibilityHandler mainHandler = this; 3790 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mainHandlerClassName)) { 3791 try { 3792 // in case there is a main handler configured, try to instanciate it 3793 Class<?> handlerClass = Class.forName(mainHandlerClassName); 3794 mainHandler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance(); 3795 } catch (Exception e) { 3796 LOG.error(e.getLocalizedMessage(), e); 3797 } 3798 } 3799 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_VISIBILITY)); 3800 for (Element element : elements) { 3801 try { 3802 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3803 String handlerClassName = element.attributeValue(APPINFO_ATTR_CLASS); 3804 String params = element.attributeValue(APPINFO_ATTR_PARAMS); 3805 I_CmsXmlContentVisibilityHandler handler = null; 3806 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(handlerClassName)) { 3807 3808 Class<?> handlerClass = Class.forName(handlerClassName); 3809 handler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance(); 3810 } else { 3811 handler = mainHandler; 3812 } 3813 m_visibilityConfigurations.put(elementName, new VisibilityConfiguration(handler, params)); 3814 3815 } catch (Exception e) { 3816 LOG.error(e.getLocalizedMessage(), e); 3817 } 3818 } 3819 } 3820 3821 /** 3822 * Returns the is-invalidate-parent flag for the given xpath.<p> 3823 * 3824 * @param xpath the path to get the check rule for 3825 * 3826 * @return the configured is-invalidate-parent flag for the given xpath 3827 */ 3828 protected boolean isInvalidateParent(String xpath) { 3829 3830 if (!CmsXmlUtils.isDeepXpath(xpath)) { 3831 return false; 3832 } 3833 Boolean isInvalidateParent = null; 3834 // look up the default from the configured mappings 3835 isInvalidateParent = m_relationChecks.get(xpath); 3836 if (isInvalidateParent == null) { 3837 // no value found, try default xpath 3838 String path = CmsXmlUtils.removeXpath(xpath); 3839 // look up the default value again without indexes 3840 isInvalidateParent = m_relationChecks.get(path); 3841 } 3842 if (isInvalidateParent == null) { 3843 return false; 3844 } 3845 return isInvalidateParent.booleanValue(); 3846 } 3847 3848 /** 3849 * Returns the localized resource string for a given message key according to the configured resource bundle 3850 * of this content handler.<p> 3851 * 3852 * If the key was not found in the configured bundle, or no bundle is configured for this 3853 * content handler, the return value is 3854 * <code>"??? " + keyName + " ???"</code>.<p> 3855 * 3856 * @param keyName the key for the desired string 3857 * @param locale the locale to get the key from 3858 * 3859 * @return the resource string for the given key 3860 * 3861 * @see CmsMessages#formatUnknownKey(String) 3862 * @see CmsMessages#isUnknownKey(String) 3863 */ 3864 protected String key(String keyName, Locale locale) { 3865 3866 CmsMessages messages = getMessages(locale); 3867 if (messages != null) { 3868 return messages.key(keyName); 3869 } 3870 return CmsMessages.formatUnknownKey(keyName); 3871 } 3872 3873 /** 3874 * @param solrElement the XML node of the <solrfield> node 3875 * @return parsed values of the attribute "addto" 3876 */ 3877 protected Set<MappingType> parseSearchMappingTypes(Element solrElement) { 3878 3879 Set<MappingType> result = new HashSet<MappingType>(); 3880 String mappingTypes = solrElement.attributeValue(APPINFO_ATTR_ADD_TO); 3881 if (mappingTypes != null) { 3882 String[] types = mappingTypes.split(","); 3883 for (int i = 0; i < types.length; i++) { 3884 String type = types[i].trim(); 3885 if (APPINFO_VALUE_ADD_TO_PAGE.equals(type)) { 3886 result.add(MappingType.PAGE); 3887 } else if (APPINFO_VALUE_ADD_TO_CONTENT.equals(type)) { 3888 result.add(MappingType.ELEMENT); 3889 } 3890 } 3891 } else { 3892 // for backwards compatibility 3893 result.add(MappingType.ELEMENT); 3894 } 3895 3896 return result; 3897 } 3898 3899 /** 3900 * Removes property values on resources for non-existing, optional elements.<p> 3901 * 3902 * @param cms the current users OpenCms context 3903 * @param file the file which is currently being prepared for writing 3904 * @param content the XML content to remove the property values for 3905 * @throws CmsException in case of read/write errors accessing the OpenCms VFS 3906 */ 3907 protected void removeEmptyMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 3908 3909 List<CmsResource> siblings = null; 3910 CmsObject rootCms = null; 3911 3912 Iterator<Map.Entry<String, List<String>>> allMappings = m_elementMappings.entrySet().iterator(); 3913 while (allMappings.hasNext()) { 3914 Map.Entry<String, List<String>> e = allMappings.next(); 3915 String path = e.getKey(); 3916 List<String> mappings = e.getValue(); 3917 if (mappings == null) { 3918 // nothing to do if we have no mappings at all 3919 continue; 3920 } 3921 if ((siblings == null) || (rootCms == null)) { 3922 // create OpenCms user context initialized with "/" as site root to read all siblings 3923 rootCms = OpenCms.initCmsObject(cms); 3924 rootCms.getRequestContext().setSiteRoot("/"); 3925 siblings = rootCms.readSiblings(content.getFile().getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION); 3926 } 3927 for (int v = mappings.size() - 1; v >= 0; v--) { 3928 String mapping = mappings.get(v); 3929 3930 if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) { 3931 for (int i = 0; i < siblings.size(); i++) { 3932 3933 // get siblings filename and locale 3934 String filename = siblings.get(i).getRootPath(); 3935 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 3936 3937 if (!content.hasLocale(locale)) { 3938 // only remove property if the locale fits 3939 continue; 3940 } 3941 if (content.hasValue(path, locale)) { 3942 // value is available, property must be kept 3943 continue; 3944 } 3945 3946 if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) { 3947 3948 String property; 3949 boolean shared = false; 3950 if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) { 3951 property = mapping.substring(MAPTO_PROPERTY_LIST_INDIVIDUAL.length()); 3952 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) { 3953 property = mapping.substring(MAPTO_PROPERTY_LIST_SHARED.length()); 3954 shared = true; 3955 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST)) { 3956 property = mapping.substring(MAPTO_PROPERTY_LIST.length()); 3957 } else if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) { 3958 property = mapping.substring(MAPTO_PROPERTY_SHARED.length()); 3959 shared = true; 3960 } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) { 3961 property = mapping.substring(MAPTO_PROPERTY_INDIVIDUAL.length()); 3962 } else { 3963 property = mapping.substring(MAPTO_PROPERTY.length()); 3964 } 3965 rootCms.writePropertyObject( 3966 filename, 3967 new CmsProperty( 3968 property, 3969 CmsProperty.DELETE_VALUE, 3970 shared ? CmsProperty.DELETE_VALUE : null)); 3971 } 3972 } 3973 } else if (mapping.startsWith(MAPTO_PERMISSION)) { 3974 for (int i = 0; i < siblings.size(); i++) { 3975 3976 // get siblings filename and locale 3977 String filename = siblings.get(i).getRootPath(); 3978 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 3979 3980 if (!content.hasLocale(locale)) { 3981 // only remove property if the locale fits 3982 continue; 3983 } 3984 if (content.hasValue(path, locale)) { 3985 // value is available, property must be kept 3986 continue; 3987 } 3988 // remove all existing permissions from the file 3989 List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false); 3990 for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) { 3991 CmsAccessControlEntry ace = j.next(); 3992 if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) { 3993 // remove the entry "All others", which has to be treated in a special way 3994 rootCms.rmacc( 3995 filename, 3996 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME, 3997 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString()); 3998 } else { 3999 // this is a group or user principal 4000 I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal()); 4001 if (principal.isGroup()) { 4002 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName()); 4003 } else if (principal.isUser()) { 4004 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName()); 4005 } 4006 } 4007 } 4008 } 4009 } 4010 } 4011 } 4012 } 4013 4014 /** 4015 * Resolves those mappings for which no content value exists and useDefault is set to true.<p> 4016 * 4017 * @param cms the CMS context to use 4018 * @param file the content file 4019 * @param content the content object 4020 * 4021 * @throws CmsException if something goes wrong 4022 */ 4023 protected void resolveDefaultMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 4024 4025 for (Map.Entry<String, List<String>> e : m_elementMappings.entrySet()) { 4026 String path = e.getKey(); 4027 List<String> mappings = e.getValue(); 4028 if (mappings == null) { 4029 // nothing to do if we have no mappings at all 4030 continue; 4031 } 4032 for (int v = mappings.size() - 1; v >= 0; v--) { 4033 String mapping = mappings.get(v); 4034 if (!isMappingUsingDefault(path, mapping)) { 4035 continue; 4036 } 4037 for (Locale locale : content.getLocales()) { 4038 if (content.hasValue(path, locale)) { 4039 continue; 4040 } else { 4041 String defaultValue = getDefault(cms, file, null, path, locale); 4042 if (defaultValue != null) { 4043 resolveMapping(cms, content, path, true, 0, locale, defaultValue); 4044 } 4045 } 4046 } 4047 4048 } 4049 } 4050 } 4051 4052 /** 4053 * Validates if the given <code>appinfo</code> element node from the XML content definition schema 4054 * is valid according the the capabilities of this content handler.<p> 4055 * 4056 * @param appinfoElement the <code>appinfo</code> element node to validate 4057 * 4058 * @throws CmsXmlException in case the element validation fails 4059 */ 4060 protected void validateAppinfoElement(Element appinfoElement) throws CmsXmlException { 4061 4062 // create a document to validate 4063 Document doc = DocumentHelper.createDocument(); 4064 Element root = doc.addElement(APPINFO_APPINFO); 4065 // attach the default appinfo schema 4066 root.add(I_CmsXmlSchemaType.XSI_NAMESPACE); 4067 root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, APPINFO_SCHEMA_SYSTEM_ID); 4068 // append the content from the appinfo node in the content definition 4069 root.appendContent(appinfoElement); 4070 // now validate the document with the default appinfo schema 4071 CmsXmlUtils.validateXmlStructure(doc, CmsEncoder.ENCODING_UTF_8, new CmsXmlEntityResolver(null)); 4072 } 4073 4074 /** 4075 * The errorHandler parameter is optional, if <code>null</code> is given a new error handler 4076 * instance must be created.<p> 4077 * 4078 * @param cms the current OpenCms user context 4079 * @param value the value to resolve the validation rules for 4080 * @param errorHandler (optional) an error handler instance that contains previous error or warnings 4081 * 4082 * @return an error handler that contains all errors and warnings currently found 4083 */ 4084 protected CmsXmlContentErrorHandler validateCategories( 4085 CmsObject cms, 4086 I_CmsXmlContentValue value, 4087 CmsXmlContentErrorHandler errorHandler) { 4088 4089 if (!value.isSimpleType()) { 4090 // do not validate complex types 4091 return errorHandler; 4092 } 4093 I_CmsWidget widget = null; 4094 4095 widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget(); 4096 if (!(widget instanceof CmsCategoryWidget)) { 4097 // do not validate widget that are not category widgets 4098 return errorHandler; 4099 } 4100 String stringValue = value.getStringValue(cms); 4101 if (stringValue.isEmpty()) { 4102 return errorHandler; 4103 } 4104 try { 4105 String[] values = stringValue.split(","); 4106 for (int i = 0; i < values.length; i++) { 4107 String val = values[i]; 4108 String catPath = CmsCategoryService.getInstance().getCategory(cms, val).getPath(); 4109 String refPath = getReferencePath(cms, value); 4110 CmsCategoryService.getInstance().readCategory(cms, catPath, refPath); 4111 if (((CmsCategoryWidget)widget).isOnlyLeafs()) { 4112 if (!CmsCategoryService.getInstance().readCategories(cms, catPath, false, refPath).isEmpty()) { 4113 errorHandler.addError( 4114 value, 4115 Messages.get().getBundle(value.getLocale()).key( 4116 Messages.GUI_CATEGORY_CHECK_NOLEAF_ERROR_0)); 4117 } 4118 } 4119 } 4120 } catch (CmsDataAccessException e) { 4121 // expected error in case of empty/invalid value 4122 // see CmsCategory#getCategoryPath(String, String) 4123 if (LOG.isDebugEnabled()) { 4124 LOG.debug(e.getLocalizedMessage(), e); 4125 } 4126 errorHandler.addError( 4127 value, 4128 Messages.get().getBundle(value.getLocale()).key(Messages.GUI_CATEGORY_CHECK_EMPTY_ERROR_0)); 4129 } catch (CmsException e) { 4130 // unexpected error 4131 if (LOG.isErrorEnabled()) { 4132 LOG.error(e.getLocalizedMessage(), e); 4133 } 4134 errorHandler.addError(value, e.getLocalizedMessage()); 4135 } 4136 return errorHandler; 4137 } 4138 4139 /** 4140 * Validates the given rules against the given value.<p> 4141 * 4142 * @param cms the current users OpenCms context 4143 * @param value the value to validate 4144 * @param errorHandler the error handler to use in case errors or warnings are detected 4145 * 4146 * @return if a broken link has been found 4147 */ 4148 protected boolean validateLink(CmsObject cms, I_CmsXmlContentValue value, CmsXmlContentErrorHandler errorHandler) { 4149 4150 // if there is a value of type file reference 4151 if ((value == null) || (!(value instanceof CmsXmlVfsFileValue) && !(value instanceof CmsXmlVarLinkValue))) { 4152 return false; 4153 } 4154 // if the value has a link (this will automatically fix, for instance, the path of moved resources) 4155 CmsLink link = null; 4156 if (value instanceof CmsXmlVfsFileValue) { 4157 link = ((CmsXmlVfsFileValue)value).getLink(cms); 4158 } else if (value instanceof CmsXmlVarLinkValue) { 4159 link = ((CmsXmlVarLinkValue)value).getLink(cms); 4160 } 4161 if ((link == null) || !link.isInternal()) { 4162 return false; 4163 } 4164 try { 4165 String sitePath = cms.getRequestContext().removeSiteRoot(link.getTarget()); 4166 4167 // check for links to static resources 4168 if (CmsStaticResourceHandler.isStaticResourceUri(sitePath)) { 4169 return false; 4170 } 4171 // validate the link for error 4172 CmsResource res = null; 4173 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(link.getTarget()); 4174 // the link target may be a root path for a resource in another site 4175 if (site != null) { 4176 CmsObject rootCms = OpenCms.initCmsObject(cms); 4177 rootCms.getRequestContext().setSiteRoot(""); 4178 res = rootCms.readResource(link.getTarget(), CmsResourceFilter.IGNORE_EXPIRATION); 4179 } else { 4180 res = cms.readResource(sitePath, CmsResourceFilter.IGNORE_EXPIRATION); 4181 } 4182 // check the time range 4183 if (res != null) { 4184 long time = System.currentTimeMillis(); 4185 if (!res.isReleased(time)) { 4186 if (errorHandler != null) { 4187 // generate warning message 4188 errorHandler.addWarning( 4189 value, 4190 Messages.get().getBundle(value.getLocale()).key( 4191 Messages.GUI_XMLCONTENT_CHECK_WARNING_NOT_RELEASED_0)); 4192 } 4193 return true; 4194 } else if (res.isExpired(time)) { 4195 if (errorHandler != null) { 4196 // generate warning message 4197 errorHandler.addWarning( 4198 value, 4199 Messages.get().getBundle(value.getLocale()).key( 4200 Messages.GUI_XMLCONTENT_CHECK_WARNING_EXPIRED_0)); 4201 } 4202 return true; 4203 } 4204 } 4205 } catch (CmsException e) { 4206 if (errorHandler != null) { 4207 // generate error message 4208 errorHandler.addError( 4209 value, 4210 Messages.get().getBundle(value.getLocale()).key(Messages.GUI_XMLCONTENT_CHECK_ERROR_0)); 4211 } 4212 return true; 4213 } 4214 return false; 4215 } 4216 4217 /** 4218 * Validates the given rules against the given value.<p> 4219 * 4220 * @param cms the current users OpenCms context 4221 * @param value the value to validate 4222 * @param errorHandler the error handler to use in case errors or warnings are detected 4223 * @param rules the rules to validate the value against 4224 * @param isWarning if true, this validation should be stored as a warning, otherwise as an error 4225 * 4226 * @return the updated error handler 4227 */ 4228 protected CmsXmlContentErrorHandler validateValue( 4229 CmsObject cms, 4230 I_CmsXmlContentValue value, 4231 CmsXmlContentErrorHandler errorHandler, 4232 Map<String, String> rules, 4233 boolean isWarning) { 4234 4235 if (validateLink(cms, value, errorHandler)) { 4236 return errorHandler; 4237 } 4238 4239 if (CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget() instanceof CmsDisplayWidget) { 4240 // display widgets should not be validated 4241 return errorHandler; 4242 } 4243 4244 String valueStr; 4245 try { 4246 valueStr = value.getStringValue(cms); 4247 } catch (Exception e) { 4248 // if the value can not be accessed it's useless to continue 4249 errorHandler.addError(value, e.getMessage()); 4250 return errorHandler; 4251 } 4252 4253 String regex = rules.get(value.getName()); 4254 if (regex == null) { 4255 // no customized rule, check default XML schema validation rules 4256 return validateValue(cms, value, valueStr, errorHandler, isWarning); 4257 } 4258 4259 boolean matchSign = true; 4260 if (regex.charAt(0) == '!') { 4261 // negate the pattern 4262 matchSign = false; 4263 regex = regex.substring(1); 4264 } 4265 4266 String stringToBeMatched = valueStr; 4267 if (stringToBeMatched == null) { 4268 // set match value to empty String to avoid exceptions in pattern matcher 4269 stringToBeMatched = ""; 4270 } 4271 4272 // use the custom validation pattern 4273 final boolean matches; 4274 try { 4275 matches = Pattern.matches(regex, stringToBeMatched); 4276 } catch (PatternSyntaxException | StackOverflowError e) { 4277 final String localizedMessage = (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : ""); 4278 final String ticket = String.valueOf(System.currentTimeMillis()); 4279 4280 Throwable trace = e; 4281 if (e instanceof StackOverflowError) { 4282 final String stackOverflowInfoMessage = "StackOverflowError thrown on pattern matching during xml" + 4283 " content validation. (Cause will be also logged in DEBUG level.)\n" + 4284 "Note 1.- Possible cause: The Java regex engine uses recursive method calls to implement" + 4285 " backtracking. When a repetition inside a regular expression contains multiple paths" + 4286 " (i.e. the body of the repetition contains an alternation (|), an optional element or another" + 4287 " repetition), trying to match the regular expression can cause a stack overflow on large inputs." + 4288 " This does not happen when using a possessive quantifier (such as *+ instead of *) or when using" + 4289 " a character class inside a repetition (e.g. [ab]* instead of (a|b)*).\n" + 4290 "Note 2.- On StackOverflowError, the size of the stacktraces could be limited by the JVM " + 4291 " and we could be missing information to identify the origin of the problem. To help in this" + 4292 " case, we create a new exception close to this origin. Alternatively, you can increase" + 4293 " the depth of the stack trace (for instance, '-XX:MaxJavaStackTraceDepth=1000000') to" + 4294 " identify it"; 4295 trace = LOG.isDebugEnabled() ? new Exception(stackOverflowInfoMessage, e) : new Exception(stackOverflowInfoMessage); 4296 errorHandler.addError(value, Messages.get().getBundle( 4297 value.getLocale()).key(Messages.GUI_EDITOR_XMLCONTENT_CANNOT_VALIDATE_ERROR_3, ticket, regex, stringToBeMatched)); 4298 } else { 4299 errorHandler.addError(value, Messages.get().getBundle( 4300 value.getLocale()).key(Messages.GUI_EDITOR_XMLCONTENT_INVALID_RULE_3, ticket, regex, localizedMessage)); 4301 } 4302 4303 LOG.warn("Ticket " + ticket + " - " + localizedMessage + "\n" 4304 + " Regex='" + (matchSign ? "" : "!") + regex + "'\n" 4305 + " Path='" + value.getPath() + "'\n" 4306 + " Input='" + stringToBeMatched + "'", trace); 4307 4308 return errorHandler; 4309 } 4310 if (matchSign != matches) { 4311 // generate the message 4312 String message = getValidationMessage(cms, value, regex, valueStr, matchSign, isWarning); 4313 if (isWarning) { 4314 errorHandler.addWarning(value, message); 4315 } else { 4316 errorHandler.addError(value, message); 4317 // if an error was found, the default XML schema validation is not applied 4318 return errorHandler; 4319 } 4320 } 4321 4322 // no error found, check default XML schema validation rules 4323 return validateValue(cms, value, valueStr, errorHandler, isWarning); 4324 } 4325 4326 /** 4327 * Checks the default XML schema validation rules.<p> 4328 * 4329 * These rules should only be tested if this is not a test for warnings.<p> 4330 * 4331 * @param cms the current users OpenCms context 4332 * @param value the value to validate 4333 * @param valueStr the string value of the given value 4334 * @param errorHandler the error handler to use in case errors or warnings are detected 4335 * @param isWarning if true, this validation should be stored as a warning, otherwise as an error 4336 * 4337 * @return the updated error handler 4338 */ 4339 protected CmsXmlContentErrorHandler validateValue( 4340 CmsObject cms, 4341 I_CmsXmlContentValue value, 4342 String valueStr, 4343 CmsXmlContentErrorHandler errorHandler, 4344 boolean isWarning) { 4345 4346 if (isWarning) { 4347 // default schema validation only applies to errors 4348 return errorHandler; 4349 } 4350 4351 String message = null; 4352 if (value instanceof I_CmsXmlValidateWithMessage) { 4353 CmsMessageContainer messageContainer = ((I_CmsXmlValidateWithMessage)value).validateWithMessage(valueStr); 4354 if (null != messageContainer) { 4355 message = messageContainer.key(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)); 4356 } 4357 } else { 4358 if (!value.validateValue(valueStr)) { 4359 // value is not valid, add an error to the handler 4360 message = getValidationMessage(cms, value, value.getTypeName(), valueStr, true, false); 4361 } 4362 } 4363 if (null != message) { 4364 errorHandler.addError(value, message); 4365 } 4366 4367 return errorHandler; 4368 } 4369 4370 /** 4371 * Writes the categories if a category widget is present.<p> 4372 * 4373 * @param cms the cms context 4374 * @param file the file 4375 * @param content the xml content to set the categories for 4376 * 4377 * @return the perhaps modified file 4378 * 4379 * @throws CmsException if something goes wrong 4380 */ 4381 protected CmsFile writeCategories(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 4382 4383 if (CmsWorkplace.isTemporaryFile(file)) { 4384 // ignore temporary file if the original file exists (not the case for direct edit: "new") 4385 if (CmsResource.isTemporaryFileName(file.getRootPath())) { 4386 String originalFileName = CmsResource.getFolderPath(file.getRootPath()) 4387 + CmsResource.getName(file.getRootPath()).substring(CmsResource.TEMP_FILE_PREFIX.length()); 4388 if (cms.existsResource(cms.getRequestContext().removeSiteRoot(originalFileName))) { 4389 // original file exists, ignore it 4390 return file; 4391 } 4392 } else { 4393 // file name does not start with temporary prefix, ignore the file 4394 return file; 4395 } 4396 } 4397 // check the presence of a category widget 4398 boolean hasCategoryWidget = hasCategoryWidget(); 4399 if (!hasCategoryWidget) { 4400 // nothing to do if no category widget is present 4401 return file; 4402 } 4403 boolean modified = false; 4404 // clone the cms object, and use the root site 4405 CmsObject tmpCms = OpenCms.initCmsObject(cms); 4406 tmpCms.getRequestContext().setSiteRoot(""); 4407 // read all siblings 4408 try { 4409 List<CmsResource> listsib = tmpCms.readSiblings(file.getRootPath(), CmsResourceFilter.ALL); 4410 for (int i = 0; i < listsib.size(); i++) { 4411 CmsResource resource = listsib.get(i); 4412 // get the default locale of the sibling 4413 List<Locale> locales = getLocalesForResource(tmpCms, resource.getRootPath()); 4414 Locale locale = locales.get(0); 4415 for (Locale l : locales) { 4416 if (content.hasLocale(l)) { 4417 locale = l; 4418 break; 4419 } 4420 } 4421 // remove all previously set categories 4422 boolean clearedCategories = false; 4423 // iterate over all values checking for the category widget 4424 CmsXmlContentWidgetVisitor widgetCollector = new CmsXmlContentWidgetVisitor(cms, locale); 4425 content.visitAllValuesWith(widgetCollector); 4426 Iterator<Map.Entry<String, I_CmsXmlContentValue>> itWidgets = widgetCollector.getValues().entrySet().iterator(); 4427 while (itWidgets.hasNext()) { 4428 Map.Entry<String, I_CmsXmlContentValue> entry = itWidgets.next(); 4429 String xpath = entry.getKey(); 4430 I_CmsWidget widget = widgetCollector.getWidgets().get(xpath); 4431 I_CmsXmlContentValue value = entry.getValue(); 4432 if (!(widget instanceof CmsCategoryWidget) 4433 || value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 4434 // ignore other values than categories 4435 continue; 4436 } 4437 if (!clearedCategories) { 4438 CmsCategoryService.getInstance().clearCategoriesForResource(tmpCms, resource.getRootPath()); 4439 clearedCategories = true; 4440 } 4441 String stringValue = value.getStringValue(tmpCms); 4442 if (CmsStringUtil.isEmptyOrWhitespaceOnly(stringValue)) { 4443 // skip empty values 4444 continue; 4445 } 4446 try { 4447 // add the file to the selected category 4448 String[] catRootPathes = stringValue.split(","); 4449 for (String catRootPath : catRootPathes) { 4450 CmsCategory cat = CmsCategoryService.getInstance().getCategory(tmpCms, catRootPath); 4451 CmsCategoryService.getInstance().addResourceToCategory( 4452 tmpCms, 4453 resource.getRootPath(), 4454 cat.getPath()); 4455 } 4456 } catch (CmsVfsResourceNotFoundException e) { 4457 // invalid category 4458 try { 4459 // try to remove invalid value 4460 content.removeValue(value.getName(), value.getLocale(), value.getIndex()); 4461 modified = true; 4462 } catch (CmsRuntimeException ex) { 4463 // in case minoccurs prevents removing the invalid value 4464 if (LOG.isDebugEnabled()) { 4465 LOG.debug(ex.getLocalizedMessage(), ex); 4466 } 4467 } 4468 } 4469 } 4470 } 4471 } catch (CmsException ex) { 4472 if (LOG.isErrorEnabled()) { 4473 LOG.error(ex.getLocalizedMessage(), ex); 4474 } 4475 } 4476 if (modified) { 4477 // when an invalid category has been removed 4478 file = content.correctXmlStructure(cms); 4479 content.setFile(file); 4480 } 4481 return file; 4482 } 4483 4484 /** 4485 * Creates a search field mapping for the given mapping element and the locale.<p> 4486 * 4487 * @param contentDefinition the content definition 4488 * @param element the mapping element configured in the schema 4489 * @param locale the locale 4490 * 4491 * @return the created search field mapping 4492 * 4493 * @throws CmsXmlException if the dynamic field class could not be found 4494 */ 4495 private I_CmsSearchFieldMapping createSearchFieldMapping( 4496 CmsXmlContentDefinition contentDefinition, 4497 Element element, 4498 Locale locale) 4499 throws CmsXmlException { 4500 4501 I_CmsSearchFieldMapping fieldMapping = null; 4502 String typeAsString = element.attributeValue(APPINFO_ATTR_TYPE); 4503 CmsSearchFieldMappingType type = CmsSearchFieldMappingType.valueOf(typeAsString); 4504 switch (type.getMode()) { 4505 case 0: // content 4506 case 3: // item 4507 // localized 4508 String param = locale.toString() + "|" + element.getStringValue(); 4509 fieldMapping = new CmsSearchFieldMapping(type, param); 4510 break; 4511 case 1: // property 4512 case 2: // property-search 4513 case 5: // attribute 4514 // not localized 4515 fieldMapping = new CmsSearchFieldMapping(type, element.getStringValue()); 4516 break; 4517 case 4: // dynamic 4518 String mappingClass = element.attributeValue(APPINFO_ATTR_CLASS); 4519 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mappingClass)) { 4520 try { 4521 fieldMapping = (I_CmsSearchFieldMapping)Class.forName(mappingClass).newInstance(); 4522 fieldMapping.setType(CmsSearchFieldMappingType.DYNAMIC); 4523 fieldMapping.setParam(element.getStringValue()); 4524 fieldMapping.setLocale(locale); 4525 } catch (Exception e) { 4526 throw new CmsXmlException( 4527 Messages.get().container( 4528 Messages.ERR_XML_SCHEMA_MAPPING_CLASS_NOT_EXIST_3, 4529 mappingClass, 4530 contentDefinition.getTypeName(), 4531 contentDefinition.getSchemaLocation())); 4532 } 4533 4534 } 4535 break; 4536 default: 4537 // NOOP 4538 } 4539 if (fieldMapping != null) { 4540 fieldMapping.setDefaultValue(element.attributeValue(APPINFO_ATTR_DEFAULT)); 4541 } 4542 return fieldMapping; 4543 } 4544 4545 /** 4546 * Gets the xpath mapped to a given target, if the mapping exists, and null otherwise. 4547 * 4548 * @param target the mapping target 4549 * @return the xpath mapped to the target 4550 */ 4551 private String getMappingSource(String target) { 4552 4553 for (Map.Entry<String, List<String>> entry : m_elementMappings.entrySet()) { 4554 if (entry.getValue().contains(target)) { 4555 return entry.getKey(); 4556 } 4557 } 4558 return null; 4559 } 4560 4561 /** 4562 * Utility method to return a path fragment.<p> 4563 * 4564 * @param pathElements the path elements 4565 * @param begin the begin index 4566 * 4567 * @return the path 4568 */ 4569 private String getSubPath(String[] pathElements, int begin) { 4570 4571 String result = ""; 4572 for (int i = begin; i < pathElements.length; i++) { 4573 result += pathElements[i] + "/"; 4574 } 4575 if (result.length() > 0) { 4576 result = result.substring(0, result.length() - 1); 4577 } 4578 return result; 4579 } 4580 4581 /** 4582 * Checks if any configured value type is an OpenCmsCategory. 4583 * 4584 * @return true if any configured value type is an OpenCmsCategory 4585 */ 4586 private boolean hasCategoryType() { 4587 4588 try { 4589 for (I_CmsXmlSchemaType typeEntry : m_contentDefinition.getTypeSequence()) { 4590 String typeName = typeEntry.getTypeName(); 4591 I_CmsXmlSchemaType type = OpenCms.getXmlContentTypeManager().getContentType(typeName); 4592 if (type instanceof CmsXmlCategoryValue) { 4593 return true; 4594 } 4595 } 4596 } catch (Exception e) { 4597 LOG.debug(e.getLocalizedMessage(), e); 4598 } 4599 return false; 4600 4601 } 4602 4603 /** 4604 * Checks whether a category widget is configured. 4605 * 4606 * @return true if a category widget is configured 4607 */ 4608 private boolean hasCategoryWidget() { 4609 4610 if (m_hasCategoryWidget == null) { 4611 boolean result = false; 4612 for (Map.Entry<String, String> widgetEntry : m_widgetNames.entrySet()) { 4613 String widgetName = widgetEntry.getValue(); 4614 I_CmsWidget widget = OpenCms.getXmlContentTypeManager().getWidget(widgetName); 4615 if ((widget != null) && (widget instanceof CmsCategoryWidget)) { 4616 result = true; 4617 break; 4618 } 4619 } 4620 result = result || hasCategoryType(); 4621 m_hasCategoryWidget = Boolean.valueOf(result); 4622 return result; 4623 } 4624 return m_hasCategoryWidget.booleanValue(); 4625 4626 } 4627 4628 /** 4629 * Initializes the geo-mapping configuration. 4630 * 4631 * @param element the configuration node 4632 */ 4633 private void initGeoMappingEntries(Element element) { 4634 4635 try { 4636 for (Element child : element.elements()) { 4637 EntryType type = EntryType.valueOf(child.getName()); 4638 String value = child.getText(); 4639 Entry entry = new Entry(type, value.trim()); 4640 m_geomappingEntries.add(entry); 4641 } 4642 } catch (Exception e) { 4643 LOG.error(e.getLocalizedMessage(), e); 4644 } 4645 } 4646 4647 /** 4648 * Initializes the message key fall back handler.<p> 4649 * 4650 * @param element the XML element node 4651 */ 4652 private void initMessageKeyHandler(Element element) { 4653 4654 String className = element.attributeValue(APPINFO_ATTR_CLASS); 4655 String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 4656 try { 4657 Object messageKeyHandler = Class.forName(className).getConstructor(String.class).newInstance(configuration); 4658 m_messageKeyHandler = (CmsMultiMessages.I_KeyFallbackHandler)messageKeyHandler; 4659 } catch (Exception e) { 4660 LOG.error(e.getLocalizedMessage(), e); 4661 } 4662 } 4663 4664 /** 4665 * Checks if the given mapping has the 'useDefault' flag set to true.<p> 4666 * 4667 * @param path the mapping path 4668 * @param mapping the mapping type 4669 * 4670 * @return true if 'useDefault' is enabled for this mapping 4671 */ 4672 private boolean isMappingUsingDefault(String path, String mapping) { 4673 4674 String key = path + ":" + mapping; 4675 return m_mappingsUsingDefault.contains(key); 4676 } 4677 4678 /** 4679 * Helper method which does most of the mapping resolution work.<p> 4680 * 4681 * @param cms the CMS context to use 4682 * @param content the content object 4683 * @param valuePath the xpath of the value 4684 * @param valueIsSimple true if this is a simple value 4685 * @param valueIndex the index of the value 4686 * @param valueLocale the locale of the value 4687 * @param originalStringValue the value as a string 4688 * 4689 * @throws CmsException if something goes wrong 4690 */ 4691 private void resolveMapping( 4692 CmsObject cms, 4693 CmsXmlContent content, 4694 String valuePath, 4695 boolean valueIsSimple, 4696 int valueIndex, 4697 Locale valueLocale, 4698 String originalStringValue) 4699 throws CmsException { 4700 4701 CmsObject rootCms = createRootCms(cms); 4702 // get the original VFS file from the content 4703 CmsFile file = content.getFile(); 4704 if (!valueIsSimple) { 4705 // no mappings for a nested schema are possible 4706 // note that the sub-elements of the nested schema ARE mapped by the node visitor, 4707 // it's just the nested schema value itself that does not support mapping 4708 return; 4709 } 4710 4711 List<String> mappings = getMappings(valuePath); 4712 if (mappings.size() == 0) { 4713 // nothing to do if we have no mappings at all 4714 return; 4715 } 4716 // create OpenCms user context initialized with "/" as site root to read all siblings 4717 // read all siblings of the file 4718 List<CmsResource> siblings = rootCms.readSiblings( 4719 content.getFile().getRootPath(), 4720 CmsResourceFilter.IGNORE_EXPIRATION); 4721 4722 Set<CmsResource> urlNameMappingResources = new HashSet<CmsResource>(); 4723 boolean mapToUrlName = false; 4724 urlNameMappingResources.add(content.getFile()); 4725 // since 7.0.2 multiple mappings are possible 4726 4727 // get the string value of the current node 4728 4729 CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(rootCms, content, valueLocale); 4730 resolver.setKeepEmptyMacros(true); 4731 String stringValue = resolver.resolveMacros(originalStringValue); 4732 CmsMappingResolutionContext mappingContext = (CmsMappingResolutionContext)(cms.getRequestContext().getAttribute( 4733 ATTR_MAPPING_RESOLUTION_CONTEXT)); 4734 4735 for (String mapping : mappings) { 4736 4737 if (CmsStringUtil.isNotEmpty(mapping)) { 4738 4739 // attribute mapping now does its own handling of siblings/locales in CmsMappingResolutionContext, 4740 // so we just save the mapped release/expiration dates for later, and we do this before the sibling/locale handling 4741 // logic in this method. 4742 if (mapping.startsWith(MAPTO_ATTRIBUTE)) { 4743 4744 // this is an attribute mapping 4745 String attribute = mapping.substring(MAPTO_ATTRIBUTE.length()); 4746 switch (ATTRIBUTES.indexOf(attribute)) { 4747 case 0: // date released 4748 long date = 0; 4749 try { 4750 date = Long.valueOf(stringValue).longValue(); 4751 } catch (NumberFormatException e) { 4752 // ignore, value can be a macro 4753 } 4754 if (date == 0) { 4755 date = CmsResource.DATE_RELEASED_DEFAULT; 4756 } 4757 mappingContext.putReleaseDate(valueLocale, date); 4758 break; 4759 case 1: // date expired 4760 date = 0; 4761 try { 4762 date = Long.valueOf(stringValue).longValue(); 4763 } catch (NumberFormatException e) { 4764 // ignore, value can be a macro 4765 } 4766 if (date == 0) { 4767 date = CmsResource.DATE_EXPIRED_DEFAULT; 4768 } 4769 mappingContext.putExpirationDate(valueLocale, date); 4770 break; 4771 default: 4772 // ignore invalid / other mappings 4773 } 4774 continue; // skip to next mapping 4775 } 4776 4777 // for multiple language mappings, we need to ensure 4778 // a) all siblings are handled 4779 // b) only the "right" locale is mapped to a sibling 4780 for (int i = (siblings.size() - 1); i >= 0; i--) { 4781 // get filename 4782 String filename = (siblings.get(i)).getRootPath(); 4783 if (mapping.startsWith(MAPTO_URLNAME)) { 4784 // should be written regardless of whether there is a sibling with the correct locale 4785 mapToUrlName = true; 4786 } 4787 4788 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 4789 if (!locale.equals(valueLocale)) { 4790 // only map property if the locale fits 4791 continue; 4792 } 4793 4794 // make sure the file is locked 4795 CmsLock lock = rootCms.getLock(filename); 4796 if (lock.isUnlocked()) { 4797 rootCms.lockResource(filename); 4798 } else if (!lock.isDirectlyOwnedInProjectBy(rootCms)) { 4799 rootCms.changeLock(filename); 4800 } 4801 4802 if (mapping.startsWith(MAPTO_PERMISSION) && (valueIndex == 0)) { 4803 4804 // map value to a permission 4805 // example of a mapping: mapto="permission:GROUP:+r+v|GROUP.ALL_OTHERS:|GROUP.Projectmanagers:+r+v+w+c" 4806 4807 // get permission(s) to set 4808 String permissionMappings = mapping.substring(MAPTO_PERMISSION.length()); 4809 String mainMapping = permissionMappings; 4810 Map<String, String> permissionsToSet = new HashMap<String, String>(); 4811 4812 // separate permission to set for element value from other permissions to set 4813 int sepIndex = permissionMappings.indexOf('|'); 4814 if (sepIndex != -1) { 4815 mainMapping = permissionMappings.substring(0, sepIndex); 4816 permissionMappings = permissionMappings.substring(sepIndex + 1); 4817 permissionsToSet = CmsStringUtil.splitAsMap(permissionMappings, "|", ":"); 4818 } 4819 4820 // determine principal type and permission string to set 4821 String principalType = I_CmsPrincipal.PRINCIPAL_GROUP; 4822 String permissionString = mainMapping; 4823 sepIndex = mainMapping.indexOf(':'); 4824 if (sepIndex != -1) { 4825 principalType = mainMapping.substring(0, sepIndex); 4826 permissionString = mainMapping.substring(sepIndex + 1); 4827 } 4828 if (permissionString.toLowerCase().indexOf('o') == -1) { 4829 permissionString += "+o"; 4830 } 4831 4832 // remove all existing permissions from the file 4833 List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false); 4834 for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) { 4835 CmsAccessControlEntry ace = j.next(); 4836 if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) { 4837 // remove the entry "All others", which has to be treated in a special way 4838 rootCms.rmacc( 4839 filename, 4840 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME, 4841 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString()); 4842 } else { 4843 // this is a group or user principal 4844 I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal()); 4845 if (principal.isGroup()) { 4846 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName()); 4847 } else if (principal.isUser()) { 4848 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName()); 4849 } 4850 } 4851 } 4852 4853 // set additional permissions that are defined in mapping 4854 for (Iterator<Map.Entry<String, String>> j = permissionsToSet.entrySet().iterator(); j.hasNext();) { 4855 Map.Entry<String, String> entry = j.next(); 4856 sepIndex = entry.getKey().indexOf('.'); 4857 if (sepIndex != -1) { 4858 String type = entry.getKey().substring(0, sepIndex); 4859 String name = entry.getKey().substring(sepIndex + 1); 4860 String permissions = entry.getValue(); 4861 if (permissions.toLowerCase().indexOf('o') == -1) { 4862 permissions += "+o"; 4863 } 4864 try { 4865 rootCms.chacc(filename, type, name, permissions); 4866 } catch (CmsException e) { 4867 // setting permission did not work 4868 LOG.error(e.getLocalizedMessage(), e); 4869 } 4870 } 4871 } 4872 4873 // set permission(s) using the element value(s) 4874 // the set with all selected principals 4875 TreeSet<String> allPrincipals = new TreeSet<String>(); 4876 String path = CmsXmlUtils.removeXpathIndex(valuePath); 4877 List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale); 4878 Iterator<I_CmsXmlContentValue> j = values.iterator(); 4879 while (j.hasNext()) { 4880 I_CmsXmlContentValue val = j.next(); 4881 String principalName = val.getStringValue(rootCms); 4882 // the prinicipal name can be a principal list 4883 List<String> principalNames = CmsStringUtil.splitAsList( 4884 principalName, 4885 PRINCIPAL_LIST_SEPARATOR); 4886 // iterate over the principals 4887 Iterator<String> iterPrincipals = principalNames.iterator(); 4888 while (iterPrincipals.hasNext()) { 4889 // get the next principal 4890 String principal = iterPrincipals.next(); 4891 allPrincipals.add(principal); 4892 } 4893 } 4894 // iterate over the set with all principals and set the permissions 4895 Iterator<String> iterAllPricinipals = allPrincipals.iterator(); 4896 while (iterAllPricinipals.hasNext()) { 4897 // get the next principal 4898 String principal = iterAllPricinipals.next(); 4899 rootCms.chacc(filename, principalType, principal, permissionString); 4900 } 4901 // special case: permissions are written only to one sibling, end loop 4902 i = 0; 4903 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST) && (valueIndex == 0)) { 4904 4905 boolean mapToShared; 4906 int prefixLength; 4907 // check which mapping is used (shared or individual) 4908 if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) { 4909 mapToShared = true; 4910 prefixLength = MAPTO_PROPERTY_LIST_SHARED.length(); 4911 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) { 4912 mapToShared = false; 4913 prefixLength = MAPTO_PROPERTY_LIST_INDIVIDUAL.length(); 4914 } else { 4915 mapToShared = false; 4916 prefixLength = MAPTO_PROPERTY_LIST.length(); 4917 } 4918 4919 // this is a property list mapping 4920 String property = mapping.substring(prefixLength); 4921 4922 String path = CmsXmlUtils.removeXpathIndex(valuePath); 4923 List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale); 4924 Iterator<I_CmsXmlContentValue> j = values.iterator(); 4925 StringBuffer result = new StringBuffer(values.size() * 64); 4926 while (j.hasNext()) { 4927 I_CmsXmlContentValue val = j.next(); 4928 result.append(val.getStringValue(rootCms)); 4929 if (j.hasNext()) { 4930 result.append(CmsProperty.VALUE_LIST_DELIMITER); 4931 } 4932 } 4933 4934 CmsProperty p; 4935 if (mapToShared) { 4936 // map to shared value 4937 p = new CmsProperty(property, null, result.toString()); 4938 } else { 4939 // map to individual value 4940 p = new CmsProperty(property, result.toString(), null); 4941 } 4942 // write the created list string value in the selected property 4943 rootCms.writePropertyObject(filename, p); 4944 if (mapToShared) { 4945 // special case: shared mappings must be written only to one sibling, end loop 4946 i = 0; 4947 } 4948 4949 } else if (mapping.startsWith(MAPTO_PROPERTY)) { 4950 4951 boolean mapToShared; 4952 int prefixLength; 4953 // check which mapping is used (shared or individual) 4954 if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) { 4955 mapToShared = true; 4956 prefixLength = MAPTO_PROPERTY_SHARED.length(); 4957 } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) { 4958 mapToShared = false; 4959 prefixLength = MAPTO_PROPERTY_INDIVIDUAL.length(); 4960 } else { 4961 mapToShared = false; 4962 prefixLength = MAPTO_PROPERTY.length(); 4963 } 4964 4965 // this is a property mapping 4966 String property = mapping.substring(prefixLength); 4967 4968 CmsProperty p; 4969 if (mapToShared) { 4970 // map to shared value 4971 p = new CmsProperty(property, null, stringValue); 4972 } else { 4973 // map to individual value 4974 p = new CmsProperty(property, stringValue, null); 4975 } 4976 // just store the string value in the selected property 4977 rootCms.writePropertyObject(filename, p); 4978 if (mapToShared) { 4979 // special case: shared mappings must be written only to one sibling, end loop 4980 i = 0; 4981 } 4982 } else if (mapping.startsWith(MAPTO_URLNAME)) { 4983 // we write the actual mappings later 4984 urlNameMappingResources.add(siblings.get(i)); 4985 } 4986 } 4987 } 4988 } 4989 if (mapToUrlName) { 4990 for (CmsResource resourceForUrlNameMapping : urlNameMappingResources) { 4991 if (!CmsResource.isTemporaryFileName(resourceForUrlNameMapping.getRootPath())) { 4992 String mappedName = stringValue; 4993 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(mappedName)) { 4994 mappedName = mappedName.trim(); 4995 mappingContext.addUrlNameMapping( 4996 mappedName, 4997 valueLocale, 4998 resourceForUrlNameMapping.getStructureId()); 4999 } 5000 } 5001 } 5002 } 5003 5004 // make sure the original is locked 5005 CmsLock lock = rootCms.getLock(file); 5006 if (lock.isUnlocked()) { 5007 rootCms.lockResource(file.getRootPath()); 5008 } else if (!lock.isExclusiveOwnedBy(rootCms.getRequestContext().getCurrentUser())) { 5009 rootCms.changeLock(file.getRootPath()); 5010 } 5011 } 5012 5013 /** 5014 * Parses a boolean from a string and returns a default value if the string couldn't be parsed.<p> 5015 * 5016 * @param text the text from which to get the boolean value 5017 * @param defaultValue the value to return if parsing fails 5018 * 5019 * @return the parsed boolean 5020 */ 5021 private boolean safeParseBoolean(String text, boolean defaultValue) { 5022 5023 if (text == null) { 5024 return defaultValue; 5025 } 5026 try { 5027 return Boolean.parseBoolean(text); 5028 } catch (Throwable t) { 5029 return defaultValue; 5030 } 5031 } 5032 5033}