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