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