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