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