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 /** version-transformation node name. */ 565 public static final String APPINFO_VERSION_TRANSFORMATION = "versiontransformation"; 566 567 /** Constant for the "preview" appinfo element name. */ 568 public static final String APPINFO_PREVIEW = "preview"; 569 570 /** Constant for the "propertybundle" appinfo element name. */ 571 public static final String APPINFO_PROPERTYBUNDLE = "propertybundle"; 572 573 /** Constant for the "relation" appinfo element name. */ 574 public static final String APPINFO_RELATION = "relation"; 575 576 /** Constant for the "relations" appinfo element name. */ 577 public static final String APPINFO_RELATIONS = "relations"; 578 579 /** Constant for the "resource" appinfo element name. */ 580 public static final String APPINFO_RESOURCE = "resource"; 581 582 /** Constant for the "resourcebundle" appinfo element name. */ 583 public static final String APPINFO_RESOURCEBUNDLE = "resourcebundle"; 584 585 /** Constant for the "resourcebundles" appinfo element name. */ 586 public static final String APPINFO_RESOURCEBUNDLES = "resourcebundles"; 587 588 /** Constant for the reverse-mapping-enabled appinfo element name. */ 589 public static final String APPINFO_REVERSE_MAPPING_ENABLED = "reverse-mapping-enabled"; 590 591 /** Constant for the "rule" appinfo element name. */ 592 public static final String APPINFO_RULE = "rule"; 593 594 /** The file where the default appinfo schema is located. */ 595 public static final String APPINFO_SCHEMA_FILE = "org/opencms/xml/content/DefaultAppinfo.xsd"; 596 597 /** The file where the default appinfo schema types are located. */ 598 public static final String APPINFO_SCHEMA_FILE_TYPES = "org/opencms/xml/content/DefaultAppinfoTypes.xsd"; 599 600 /** The XML system id for the default appinfo schema types. */ 601 public static final String APPINFO_SCHEMA_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX 602 + APPINFO_SCHEMA_FILE; 603 604 /** The XML system id for the default appinfo schema types. */ 605 public static final String APPINFO_SCHEMA_TYPES_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX 606 + APPINFO_SCHEMA_FILE_TYPES; 607 608 /** Constant for the "searchsetting" appinfo element name. */ 609 public static final String APPINFO_SEARCHSETTING = "searchsetting"; 610 611 /** Constant for the "searchsettings" appinfo element name. */ 612 public static final String APPINFO_SEARCHSETTINGS = "searchsettings"; 613 614 /** Constant for the "setting" appinfo element name. */ 615 public static final String APPINFO_SETTING = "setting"; 616 617 /** Constant for the "settings" appinfo element name. */ 618 public static final String APPINFO_SETTINGS = "settings"; 619 620 /** Constant for the "solrfield" appinfo element name. */ 621 public static final String APPINFO_SOLR_FIELD = "solrfield"; 622 623 /** Constant for the "synchronization" appinfo element name. */ 624 public static final String APPINFO_SYNCHRONIZATION = "synchronization"; 625 626 /** Constant for the "synchronizations" appinfo element name. */ 627 public static final String APPINFO_SYNCHRONIZATIONS = "synchronizations"; 628 629 /** Constant for the "tab" appinfo element name. */ 630 public static final String APPINFO_TAB = "tab"; 631 632 /** Constant for the "tabs" appinfo element name. */ 633 public static final String APPINFO_TABS = "tabs"; 634 635 /** Node name. */ 636 public static final String APPINFO_TEMPLATE = "template"; 637 638 /** Node name. */ 639 public static final String APPINFO_TEMPLATES = "templates"; 640 641 /** Constant for the "validationrule" appinfo element name. */ 642 public static final String APPINFO_VALIDATIONRULE = "validationrule"; 643 644 /** Constant for the "validationrules" appinfo element name. */ 645 public static final String APPINFO_VALIDATIONRULES = "validationrules"; 646 647 /** Constant for the "element" value of the appinfo attribute "addto". */ 648 public static final String APPINFO_VALUE_ADD_TO_CONTENT = "element"; 649 650 /** Constant for the "page" value of the appinfo attribute "addto". */ 651 public static final String APPINFO_VALUE_ADD_TO_PAGE = "page"; 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 configuration values for the element widgets (as defined in the annotations). */ 777 protected Map<String, String> m_configurationValues; 778 779 /** The CSS resources to include into the html-page head. */ 780 protected Set<String> m_cssHeadIncludes; 781 782 /** The default values for the elements (as defined in the annotations). */ 783 protected Map<String, String> m_defaultValues; 784 785 /** The element mappings (as defined in the annotations). */ 786 protected Map<String, List<String>> m_elementMappings; 787 788 /** The formatter configuration. */ 789 protected CmsFormatterConfiguration m_formatterConfiguration; 790 791 /** The list of formatters from the XSD. */ 792 protected List<CmsFormatterBean> m_formatters; 793 794 /** The configured geo-coordinate mapping configuration entries. */ 795 protected List<CmsGeoMappingConfiguration.Entry> m_geomappingEntries = new ArrayList<>(); 796 797 /** Relation actions. */ 798 protected Map<String, InvalidRelationAction> m_invalidRelationActions = new HashMap<>(); 799 800 /** The java-script resources to include into the html-page head. */ 801 protected Set<String> m_jsHeadIncludes; 802 803 /** The resource bundle name to be used for localization of this content handler. */ 804 protected List<String> m_messageBundleNames; 805 806 /** The folder containing the model file(s) for the content. */ 807 protected String m_modelFolder; 808 809 /** The preview location (as defined in the annotations). */ 810 protected String m_previewLocation; 811 812 /** Name of the field used for geo-coordinate mapping. */ 813 protected String m_primaryGeomappingField; 814 815 /** The relation check rules. */ 816 protected Map<String, Boolean> m_relationChecks; 817 818 /** The relation check rules. */ 819 protected Map<String, CmsRelationType> m_relations; 820 821 /** The Solr field configurations. */ 822 protected Map<String, CmsSearchField> m_searchFields; 823 824 /** The Solr field configurations added to the container pages contents are on. */ 825 protected Map<String, CmsSearchField> m_searchFieldsPage; 826 827 /** The search settings. */ 828 protected Map<String, SearchContentType> m_searchSettings; 829 830 /** String template group for the simple search setting expansions. */ 831 protected StringTemplateGroup m_searchTemplateGroup; 832 833 /** The configured settings for the formatters (as defined in the annotations). */ 834 protected Map<String, CmsXmlContentProperty> m_settings; 835 836 /** Path to XSL transform in VFS to use for version transformation. */ 837 protected String m_versionTransformation; 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 /** Change handler configurations. */ 864 private List<CmsChangeHandlerConfig> m_changeHandlerConfigs = new ArrayList<>(); 865 866 /** The container page only flag, indicating if this XML content should be indexed on container pages only. */ 867 private boolean m_containerPageOnly; 868 869 /** The content definition for which this content handler is configured. */ 870 private CmsXmlContentDefinition m_contentDefinition; 871 872 /** The default complex widget class name. */ 873 private String m_defaultWidget; 874 875 /** The default complex widget configuration. */ 876 private String m_defaultWidgetConfig; 877 878 /** The default complex widget for this type. */ 879 private I_CmsComplexWidget m_defaultWidgetInstance; 880 881 /** The elements to display in ncompact view. */ 882 private HashMap<String, DisplayType> m_displayTypes; 883 884 /** An optional edit handler. */ 885 private I_CmsEditHandler m_editHandler; 886 887 /** The editor change handlers. */ 888 private List<I_CmsXmlContentEditorChangeHandler> m_editorChangeHandlers; 889 890 /** The descriptions for the fields. */ 891 private Map<String, String> m_fieldDescriptions = new HashMap<>(); 892 893 /** The nice names for the fields. */ 894 private Map<String, String> m_fieldNiceNames = new HashMap<>(); 895 896 /** Cached boolean indicating whether the content has category widgets. */ 897 private volatile Boolean m_hasCategoryWidget; 898 899 /** The JSON renderer settings. */ 900 private JsonRendererSettings m_jsonRendererSettings; 901 902 /** A set of keys identifying the mappings which should use default values if the corresponding values are not set in the XML content. */ 903 private Set<String> m_mappingsUsingDefault = new HashSet<String>(); 904 905 /** Message key fallback handler for the editor. */ 906 private CmsMultiMessages.I_KeyFallbackHandler m_messageKeyHandler = new CmsMultiMessages.I_KeyFallbackHandler() { 907 908 public Optional<String> getFallbackKey(String key) { 909 910 return Optional.absent(); 911 } 912 }; 913 914 /** The nested formatter elements. */ 915 private Set<String> m_nestedFormatterElements; 916 917 /** The paths of values for which no macros should be resolved when getting the default value. */ 918 private Set<String> m_nonMacroResolvableDefaults = new HashSet<String>(); 919 920 /** The parameters. */ 921 private CmsParameterConfiguration m_parameters = new CmsParameterConfiguration(); 922 923 /** Option to disable reverse mapping for this content type. */ 924 private boolean m_reverseMappingEnabled = true; 925 926 /** The visibility configurations by element path. */ 927 private Map<String, VisibilityConfiguration> m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>(); 928 929 /** The map of widget names by path. */ 930 private Map<String, String> m_widgetNames = new HashMap<>(); 931 932 /** The cached map of combined synchronization information. */ 933 protected LinkedHashMap<String, SynchronizationMode> m_combinedSynchronizations; 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 return m_validationErrorMessages.get(elementName); 1768 } 1769 1770 /** 1771 * Gets the validation warning message configured in the schema for the element. 1772 * 1773 * @param elementName the name of the element 1774 * @return the validation message 1775 */ 1776 public String getValidationWarning(String elementName) { 1777 return m_validationWarningMessages.get(elementName); 1778 } 1779 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(CmsObject cms, Locale locale, String elementName, boolean isWarning, boolean keyOnly) { 1794 String rawValue = (isWarning ? m_validationWarningMessages : m_validationErrorMessages).get(elementName); 1795 if (rawValue == null) { 1796 return null; 1797 } 1798 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages( 1799 getMessages(locale)); 1800 if (keyOnly) { 1801 resolver = new CmsKeyDummyMacroResolver(resolver); 1802 } 1803 String resolved = resolver.resolveMacros(rawValue); 1804 if (keyOnly) { 1805 return CmsKeyDummyMacroResolver.getKey(resolved); 1806 } else { 1807 return resolved; 1808 } 1809 } 1810 1811 /** 1812 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getVersionTransformation() 1813 */ 1814 public String getVersionTransformation() { 1815 1816 return m_versionTransformation; 1817 } 1818 1819 /** 1820 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.file.CmsObject, java.lang.String) 1821 */ 1822 public I_CmsWidget getWidget(CmsObject cms, String path) { 1823 1824 String widgetName = m_widgetNames.get(path); 1825 if (widgetName == null) { 1826 return null; 1827 } 1828 1829 // First resolve macros, then try resulting string as widget alias, finally try interpreting it as a class name 1830 if (cms != null) { 1831 CmsMacroResolver resolver = new CmsMacroResolver(); 1832 resolver.setCmsObject(cms); 1833 widgetName = resolver.resolveMacros(widgetName); 1834 } 1835 I_CmsWidget result = null; 1836 result = OpenCms.getXmlContentTypeManager().getWidget(widgetName); 1837 if (result != null) { 1838 return result.newInstance(); 1839 } 1840 if (CmsStringUtil.isValidJavaClassName(widgetName)) { 1841 try { 1842 Class<?> cls = Class.forName(widgetName, false, getClass().getClassLoader()); 1843 if (I_CmsWidget.class.isAssignableFrom(cls)) { 1844 return (I_CmsWidget)(cls.newInstance()); 1845 } 1846 } catch (Exception e) { 1847 LOG.warn(e.getLocalizedMessage(), e); 1848 return null; 1849 } 1850 } 1851 return null; 1852 1853 } 1854 1855 /** 1856 * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.xml.types.I_CmsXmlSchemaType) 1857 */ 1858 @Deprecated 1859 public I_CmsWidget getWidget(I_CmsXmlSchemaType value) { 1860 1861 // try the specific widget settings first 1862 I_CmsWidget result = getWidget(null, value.getName()); 1863 if (result == null) { 1864 // use default widget mappings 1865 result = OpenCms.getXmlContentTypeManager().getWidgetDefault(value.getTypeName()); 1866 } else { 1867 result = result.newInstance(); 1868 } 1869 if (result != null) { 1870 // set the configuration value for this widget 1871 String configuration = getConfiguration(value); 1872 if (configuration == null) { 1873 // no individual configuration defined, try to get global default configuration 1874 configuration = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(result); 1875 } 1876 result.setConfiguration(configuration); 1877 } 1878 return result; 1879 } 1880 1881 /** 1882 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasModifiableFormatters() 1883 */ 1884 public boolean hasModifiableFormatters() { 1885 1886 return (m_formatters != null) && (m_formatters.size() > 0); 1887 } 1888 1889 /** 1890 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasNestedFormatters() 1891 */ 1892 public boolean hasNestedFormatters() { 1893 1894 return !m_nestedFormatterElements.isEmpty(); 1895 } 1896 1897 /** 1898 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasSynchronizedElements() 1899 */ 1900 public boolean hasSynchronizedElements() { 1901 1902 return !m_synchronizations.isEmpty(); 1903 } 1904 1905 /** 1906 * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasVisibilityHandlers() 1907 */ 1908 public boolean hasVisibilityHandlers() { 1909 1910 return (m_visibilityConfigurations != null) && !m_visibilityConfigurations.isEmpty(); 1911 } 1912 1913 /** 1914 * @see org.opencms.xml.content.I_CmsXmlContentHandler#initialize(org.dom4j.Element, org.opencms.xml.CmsXmlContentDefinition) 1915 */ 1916 public synchronized void initialize(Element appInfoElement, CmsXmlContentDefinition contentDefinition) 1917 throws CmsXmlException { 1918 1919 if (appInfoElement != null) { 1920 // validate the appinfo element XML content with the default appinfo handler schema 1921 validateAppinfoElement(appInfoElement); 1922 1923 // re-initialize the local variables 1924 init(); 1925 1926 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(appInfoElement); 1927 while (i.hasNext()) { 1928 // iterate all elements in the appinfo node 1929 Element element = i.next(); 1930 String nodeName = element.getName(); 1931 if (nodeName.equals(APPINFO_MAPPINGS)) { 1932 initMappings(element, contentDefinition); 1933 } else if (nodeName.equals(APPINFO_LAYOUTS)) { 1934 initLayouts(element, contentDefinition); 1935 } else if (nodeName.equals(APPINFO_VALIDATIONRULES)) { 1936 initValidationRules(element, contentDefinition); 1937 } else if (nodeName.equals(APPINFO_RELATIONS)) { 1938 initRelations(element, contentDefinition); 1939 } else if (nodeName.equals(APPINFO_DEFAULTS)) { 1940 initDefaultValues(element, contentDefinition); 1941 } else if (nodeName.equals(APPINFO_MODELFOLDER)) { 1942 initModelFolder(element, contentDefinition); 1943 } else if (nodeName.equals(APPINFO_PREVIEW)) { 1944 initPreview(element, contentDefinition); 1945 } else if (nodeName.equals(APPINFO_RESOURCEBUNDLE)) { 1946 initResourceBundle(element, contentDefinition, true); 1947 } else if (nodeName.equals(APPINFO_RESOURCEBUNDLES)) { 1948 initResourceBundle(element, contentDefinition, false); 1949 } else if (nodeName.equals(APPINFO_SEARCHSETTINGS)) { 1950 initSearchSettings(element, contentDefinition); 1951 } else if (nodeName.equals(APPINFO_TABS)) { 1952 initTabs(element, contentDefinition); 1953 } else if (nodeName.equals(APPINFO_FORMATTERS)) { 1954 initFormatters(element, contentDefinition); 1955 } else if (nodeName.equals(APPINFO_HEAD_INCLUDES)) { 1956 initHeadIncludes(element, contentDefinition); 1957 } else if (nodeName.equals(APPINFO_SETTINGS)) { 1958 initSettings(element, contentDefinition); 1959 } else if (nodeName.equals(APPINFO_EDIT_HANDLER)) { 1960 initEditHandler(element); 1961 } else if (nodeName.equals(APPINFO_NESTED_FORMATTERS)) { 1962 initNestedFormatters(element, contentDefinition); 1963 } else if (nodeName.equals(APPINFO_TEMPLATES)) { 1964 initTemplates(element, contentDefinition); 1965 } else if (nodeName.equals(APPINFO_DEFAULTWIDGET)) { 1966 initDefaultWidget(element); 1967 } else if (nodeName.equals(APPINFO_VISIBILITIES)) { 1968 initVisibilities(element, contentDefinition); 1969 } else if (nodeName.equals(APPINFO_SYNCHRONIZATIONS)) { 1970 initSynchronizations(element, contentDefinition); 1971 } else if (nodeName.equals(APPINFO_EDITOR_CHANGE_HANDLERS)) { 1972 initEditorChangeHandlers(element); 1973 } else if (nodeName.equals(APPINFO_MESSAGEKEYHANDLER)) { 1974 initMessageKeyHandler(element); 1975 } else if (nodeName.equals(APPINFO_PARAMETERS)) { 1976 initParameters(element); 1977 } else if (nodeName.equals(APPINFO_FIELD_SETTINGS)) { 1978 initFields(element, contentDefinition); 1979 } else if (nodeName.equals(APPINFO_JSON_RENDERER)) { 1980 initJsonRenderer(element); 1981 } else if (nodeName.equals(APPINFO_REVERSE_MAPPING_ENABLED)) { 1982 m_reverseMappingEnabled = Boolean.parseBoolean(element.getTextTrim()); 1983 } else if (nodeName.equals(APPINFO_GEOMAPPING)) { 1984 initGeoMappingEntries(element); 1985 } else if (nodeName.equals(APPINFO_VERSION_TRANSFORMATION)) { 1986 m_versionTransformation = element.getTextTrim(); 1987 } 1988 } 1989 } 1990 m_contentDefinition = contentDefinition; 1991 addGeoMappingField(); 1992 1993 // at the end, add default check rules for optional file references 1994 addDefaultCheckRules(contentDefinition, null, null); 1995 } 1996 1997 /** 1998 * @see org.opencms.xml.content.I_CmsXmlContentHandler#invalidateBrokenLinks(CmsObject, CmsXmlContent) 1999 */ 2000 public void invalidateBrokenLinks(CmsObject cms, CmsXmlContent document) { 2001 2002 if ((cms == null) || (cms.getRequestContext().getRequestTime() == CmsResource.DATE_RELEASED_EXPIRED_IGNORE)) { 2003 // do not check if the request comes the editor 2004 return; 2005 } 2006 boolean needReinitialization = false; 2007 // iterate the locales 2008 Iterator<Locale> itLocales = document.getLocales().iterator(); 2009 while (itLocales.hasNext()) { 2010 Locale locale = itLocales.next(); 2011 List<String> removedNodes = new ArrayList<String>(); 2012 Map<String, I_CmsXmlContentValue> valuesToRemove = Maps.newHashMap(); 2013 // iterate the values 2014 Iterator<I_CmsXmlContentValue> itValues = document.getValues(locale).iterator(); 2015 while (itValues.hasNext()) { 2016 I_CmsXmlContentValue value = itValues.next(); 2017 InvalidRelationAction invalidRelationAction = getInvalidRelationActionForValue(value); 2018 String path = value.getPath(); 2019 // check if this value has already been deleted by parent rules 2020 boolean alreadyRemoved = false; 2021 Iterator<String> itRemNodes = removedNodes.iterator(); 2022 while (itRemNodes.hasNext()) { 2023 String remNode = itRemNodes.next(); 2024 if (path.startsWith(remNode)) { 2025 alreadyRemoved = true; 2026 break; 2027 } 2028 } 2029 // only continue if not already removed and if a rule match 2030 if (alreadyRemoved 2031 || ((m_relationChecks.get(path) == null) 2032 && (invalidRelationAction == null) 2033 && (m_relationChecks.get(CmsXmlUtils.removeXpath(path)) == null))) { 2034 continue; 2035 } 2036 2037 // check rule matched 2038 if (LOG.isDebugEnabled()) { 2039 LOG.debug(Messages.get().getBundle().key(Messages.LOG_XMLCONTENT_CHECK_RULE_MATCH_1, path)); 2040 } 2041 if (validateLink(cms, value, null)) { 2042 // invalid link 2043 if (LOG.isDebugEnabled()) { 2044 LOG.debug( 2045 Messages.get().getBundle().key( 2046 Messages.LOG_XMLCONTENT_CHECK_WARNING_2, 2047 path, 2048 value.getStringValue(cms))); 2049 } 2050 // find the node to remove 2051 String parentPath = path; 2052 boolean firstIteration = true; 2053 while (isInvalidateParent(parentPath) 2054 || (firstIteration && (invalidRelationAction == InvalidRelationAction.removeParent))) { 2055 firstIteration = false; 2056 // check parent 2057 parentPath = CmsXmlUtils.removeLastXpathElement(parentPath); 2058 // log info 2059 if (LOG.isDebugEnabled()) { 2060 LOG.debug( 2061 Messages.get().getBundle().key( 2062 Messages.LOG_XMLCONTENT_CHECK_PARENT_2, 2063 path, 2064 parentPath)); 2065 } 2066 } 2067 value = document.getValue(parentPath, locale); 2068 // Doing the actual DOM modifications here would make the bookmarks for this locale invalid, 2069 // so we delay it until later because we need the bookmarks for document.getValue() in the next loop iterations 2070 valuesToRemove.put(parentPath, value); 2071 // mark node as deleted 2072 removedNodes.add(parentPath); 2073 } 2074 } 2075 for (I_CmsXmlContentValue valueToRemove : valuesToRemove.values()) { 2076 // detach the value node from the XML document 2077 valueToRemove.getElement().detach(); 2078 needReinitialization = true; 2079 } 2080 } 2081 if (needReinitialization) { 2082 document.m_hasInvalidatedBrokenLinks = true; 2083 // re-initialize the XML content 2084 document.initDocument(); 2085 } 2086 } 2087 2088 /** 2089 * Returns true if the Acacia editor is disabled for this type.<p> 2090 * 2091 * @return true if the acacia editor is disabled 2092 */ 2093 public boolean isAcaciaEditorDisabled() { 2094 2095 return !m_useAcacia; 2096 } 2097 2098 /** 2099 * @see org.opencms.xml.content.I_CmsXmlContentHandler#isContainerPageOnly() 2100 */ 2101 public boolean isContainerPageOnly() { 2102 2103 return m_containerPageOnly; 2104 } 2105 2106 /** 2107 * Returns the content field visibilty.<p> 2108 * 2109 * This implementation will be used as default if no other <link>org.opencms.xml.content.I_CmsXmlContentVisibilityHandler</link> is configured.<p> 2110 * 2111 * Only users that are member in one of the specified groups will be allowed to view and edit the given content field.<p> 2112 * The parameter should contain a '|' separated list of group names.<p> 2113 * 2114 * @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) 2115 */ 2116 public boolean isValueVisible( 2117 CmsObject cms, 2118 I_CmsXmlSchemaType value, 2119 String elementName, 2120 String params, 2121 CmsResource resource, 2122 Locale contentLocale) { 2123 2124 CmsUser user = cms.getRequestContext().getCurrentUser(); 2125 boolean result = false; 2126 2127 try { 2128 List<CmsRole> roles = OpenCms.getRoleManager().getRolesOfUser(cms, user.getName(), "", true, false, true); 2129 List<CmsGroup> groups = cms.getGroupsOfUser(user.getName(), false); 2130 CmsMacroResolver resolver = new CmsMacroResolver(); 2131 resolver.setCmsObject(cms); 2132 Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2133 resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale)); 2134 params = resolver.resolveMacros(params); 2135 2136 if ("visible".equals(params.trim())) { 2137 return true; 2138 } 2139 String[] allowedPrincipals = params.split("\\|"); 2140 List<String> groupNames = new ArrayList<String>(); 2141 List<String> roleNames = new ArrayList<String>(); 2142 2143 for (CmsGroup group : groups) { 2144 groupNames.add(group.getName()); 2145 } 2146 for (CmsRole role : roles) { 2147 roleNames.add(role.getRoleName()); 2148 } 2149 for (String principal : allowedPrincipals) { 2150 if (CmsRole.hasPrefix(principal)) { 2151 // prefixed as a role 2152 principal = CmsRole.removePrefix(principal); 2153 if (roleNames.contains(principal)) { 2154 result = true; 2155 break; 2156 } 2157 } else { 2158 // otherwise we always assume this is a group, will work if prefixed or not 2159 principal = CmsGroup.removePrefix(principal); 2160 if (groupNames.contains(principal)) { 2161 result = true; 2162 break; 2163 } 2164 } 2165 } 2166 } catch (CmsException e) { 2167 LOG.error(e.getLocalizedMessage(), e); 2168 } 2169 2170 return result; 2171 } 2172 2173 /** 2174 * @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) 2175 */ 2176 public boolean isVisible( 2177 CmsObject cms, 2178 I_CmsXmlSchemaType contentValue, 2179 String valuePath, 2180 CmsResource resource, 2181 Locale contentLocale) { 2182 2183 if (contentValue instanceof CmsXmlAccessRestrictionValue) { 2184 CmsAccessRestrictionInfo restrictionInfo = CmsAccessRestrictionInfo.getRestrictionInfo( 2185 cms, 2186 m_contentDefinition); 2187 if (restrictionInfo == null) { 2188 return false; 2189 } 2190 } 2191 2192 if (hasVisibilityHandlers() && m_visibilityConfigurations.containsKey(valuePath)) { 2193 VisibilityConfiguration config = m_visibilityConfigurations.get(valuePath); 2194 return config.getHandler().isValueVisible( 2195 cms, 2196 contentValue, 2197 valuePath, 2198 config.getParams(), 2199 resource, 2200 contentLocale); 2201 } 2202 return true; 2203 2204 } 2205 2206 /** 2207 * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForUse(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent) 2208 */ 2209 public CmsXmlContent prepareForUse(CmsObject cms, CmsXmlContent content) { 2210 2211 // NOOP, just return the unmodified content 2212 return content; 2213 } 2214 2215 /** 2216 * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForWrite(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.file.CmsFile) 2217 */ 2218 public CmsFile prepareForWrite(CmsObject cms, CmsXmlContent content, CmsFile file) throws CmsException { 2219 2220 if (!content.isAutoCorrectionEnabled()) { 2221 // check if the XML should be corrected automatically (if not already set) 2222 Object attribute = cms.getRequestContext().getAttribute(CmsXmlContent.AUTO_CORRECTION_ATTRIBUTE); 2223 // set the auto correction mode as required 2224 boolean autoCorrectionEnabled = (attribute != null) && ((Boolean)attribute).booleanValue(); 2225 content.setAutoCorrectionEnabled(autoCorrectionEnabled); 2226 } 2227 // validate the XML structure before writing the file if required 2228 if (!content.isAutoCorrectionEnabled()) { 2229 // an exception will be thrown if the structure is invalid 2230 content.validateXmlStructure(new CmsXmlEntityResolver(cms)); 2231 } 2232 // read the content-conversion property 2233 String contentConversion = CmsHtmlConverter.getConversionSettings(cms, file); 2234 if (CmsStringUtil.isEmptyOrWhitespaceOnly(contentConversion)) { 2235 // enable pretty printing and XHTML conversion of XML content html fields by default 2236 contentConversion = CmsHtmlConverter.PARAM_XHTML; 2237 } 2238 content.setConversion(contentConversion); 2239 // correct the HTML structure 2240 file = content.correctXmlStructure(cms); 2241 content.setFile(file); 2242 2243 // check if any field has a configured attribute mapping 2244 boolean hasAttributeMappings = m_elementMappings.values().stream().flatMap(List::stream).filter( 2245 mapping -> mapping.startsWith(MAPTO_ATTRIBUTE)).findAny().isPresent(); 2246 2247 // resolve the file mappings 2248 CmsMappingResolutionContext mappingContext = new CmsMappingResolutionContext(content, hasAttributeMappings); 2249 mappingContext.setCmsObject(cms); 2250 // pass the mapping context as a request context attribute to preserve interface compatibility 2251 cms.getRequestContext().setAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT, mappingContext); 2252 content.resolveMappings(cms); 2253 // ensure all property or permission mappings of deleted optional values are removed 2254 removeEmptyMappings(cms, file, content); 2255 resolveDefaultMappings(cms, file, content); 2256 cms.getRequestContext().removeAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT); 2257 mappingContext.finalizeMappings(); 2258 // write categories (if there is a category widget present) 2259 file = writeCategories(cms, file, content); 2260 // return the result 2261 return file; 2262 } 2263 2264 /** 2265 * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue) 2266 */ 2267 public void resolveMapping(CmsObject cms, CmsXmlContent content, I_CmsXmlContentValue value) throws CmsException { 2268 2269 if (content.getFile() == null) { 2270 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0)); 2271 } 2272 2273 // get the mappings for the element name 2274 boolean valueIsSimple = value.isSimpleType(); 2275 String valuePath = value.getPath(); 2276 int valueIndex = value.getIndex(); 2277 Locale valueLocale = value.getLocale(); 2278 CmsObject rootCms1 = createRootCms(cms); 2279 String originalStringValue = null; 2280 if (valueIsSimple) { 2281 originalStringValue = value.getStringValue(rootCms1); 2282 } 2283 resolveMapping(cms, content, valuePath, valueIsSimple, valueIndex, valueLocale, originalStringValue); 2284 } 2285 2286 /** 2287 * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveValidation(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler) 2288 */ 2289 public CmsXmlContentErrorHandler resolveValidation( 2290 CmsObject cms, 2291 I_CmsXmlContentValue value, 2292 CmsXmlContentErrorHandler errorHandler) { 2293 2294 if (errorHandler == null) { 2295 // init a new error handler if required 2296 errorHandler = new CmsXmlContentErrorHandler(); 2297 } 2298 2299 if (!value.isSimpleType()) { 2300 // no validation for a nested schema is possible 2301 // note that the sub-elements of the nested schema ARE validated by the node visitor, 2302 // it's just the nested schema value itself that does not support validation 2303 return errorHandler; 2304 } 2305 2306 // validate the error rules 2307 errorHandler = validateValue(cms, value, errorHandler, m_validationErrorRules, false); 2308 // validate the warning rules 2309 errorHandler = validateValue(cms, value, errorHandler, m_validationWarningRules, true); 2310 // validate categories 2311 errorHandler = validateCategories(cms, value, errorHandler); 2312 // return the result 2313 return errorHandler; 2314 } 2315 2316 /** 2317 * Adds a check rule for a specified element.<p> 2318 * 2319 * @param contentDefinition the XML content definition this XML content handler belongs to 2320 * @param elementName the element name to add the rule to 2321 * @param invalidate <code>false</code>, to disable link check / 2322 * <code>true</code> or <code>node</code>, to invalidate just the single node if the link is broken / 2323 * <code>parent</code>, if this rule will invalidate the whole parent node in nested content 2324 * @param type the relation type 2325 * 2326 * @throws CmsXmlException in case an unknown element name is used 2327 */ 2328 protected void addCheckRule( 2329 CmsXmlContentDefinition contentDefinition, 2330 String elementName, 2331 String invalidate, 2332 String type) 2333 throws CmsXmlException { 2334 2335 I_CmsXmlSchemaType schemaType = contentDefinition.getSchemaType(elementName); 2336 if (schemaType == null) { 2337 // no element with the given name 2338 throw new CmsXmlException( 2339 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_ELEM_1, elementName)); 2340 } 2341 if (!CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName()) 2342 && !CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) { 2343 // element is not a OpenCmsVfsFile 2344 throw new CmsXmlException( 2345 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_TYPE_1, elementName)); 2346 } 2347 2348 // cache the check rule data 2349 Boolean invalidateParent = null; 2350 if ((invalidate == null) 2351 || invalidate.equalsIgnoreCase(Boolean.TRUE.toString()) 2352 || invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_NODE)) { 2353 invalidateParent = Boolean.FALSE; 2354 } else if (invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_PARENT)) { 2355 invalidateParent = Boolean.TRUE; 2356 } 2357 if (invalidateParent != null) { 2358 m_relationChecks.put(elementName, invalidateParent); 2359 } 2360 CmsRelationType relationType = (type == null ? CmsRelationType.XML_WEAK : CmsRelationType.valueOfXml(type)); 2361 m_relations.put(elementName, relationType); 2362 2363 if (invalidateParent != null) { 2364 // check the whole xpath hierarchy 2365 String path = elementName; 2366 while (CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)) { 2367 if (!isInvalidateParent(path)) { 2368 // if invalidate type = node, then the node needs to be optional 2369 if (contentDefinition.getSchemaType(path).getMinOccurs() > 0) { 2370 // element is not optional 2371 throw new CmsXmlException( 2372 Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_OPTIONAL_1, path)); 2373 } 2374 // no need to further check 2375 break; 2376 } else if (!CmsXmlUtils.isDeepXpath(path)) { 2377 // if invalidate type = parent, then the node needs to be nested 2378 // document root can not be invalidated 2379 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_EMPTY_DOC_0)); 2380 } 2381 path = CmsXmlUtils.removeLastXpathElement(path); 2382 } 2383 } 2384 } 2385 2386 /** 2387 * Adds a configuration value for an element widget.<p> 2388 * 2389 * @param contentDefinition the XML content definition this XML content handler belongs to 2390 * @param elementName the element name 2391 * @param configurationValue the configuration value to use 2392 * 2393 * @throws CmsXmlException in case an unknown element name is used 2394 */ 2395 protected void addConfiguration( 2396 CmsXmlContentDefinition contentDefinition, 2397 String elementName, 2398 String configurationValue) 2399 throws CmsXmlException { 2400 2401 if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) { 2402 throw new CmsXmlException( 2403 Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName)); 2404 } 2405 2406 m_configurationValues.put(elementName, configurationValue); 2407 } 2408 2409 /** 2410 * Adds a default value for an element.<p> 2411 * 2412 * @param contentDefinition the XML content definition this XML content handler belongs to 2413 * @param elementName the element name to map 2414 * @param defaultValue the default value to use 2415 * @param resolveMacrosValue the value of the 'resolveMacros' attribute 2416 * 2417 * @throws CmsXmlException in case an unknown element name is used 2418 */ 2419 protected void addDefault( 2420 CmsXmlContentDefinition contentDefinition, 2421 String elementName, 2422 String defaultValue, 2423 String resolveMacrosValue) 2424 throws CmsXmlException { 2425 2426 if (contentDefinition.getSchemaType(elementName) == null) { 2427 throw new CmsXmlException( 2428 org.opencms.xml.types.Messages.get().container( 2429 Messages.ERR_XMLCONTENT_INVALID_ELEM_DEFAULT_1, 2430 elementName)); 2431 } 2432 // store mappings as xpath to allow better control about what is mapped 2433 String xpath = CmsXmlUtils.createXpath(elementName, 1); 2434 m_defaultValues.put(xpath, defaultValue); 2435 2436 // macros are resolved by default 2437 if ((resolveMacrosValue != null) && !Boolean.parseBoolean(resolveMacrosValue)) { 2438 m_nonMacroResolvableDefaults.add(xpath); 2439 } 2440 } 2441 2442 /** 2443 * Adds all needed default check rules recursively for the given schema type.<p> 2444 * 2445 * @param rootContentDefinition the root content definition 2446 * @param schemaType the schema type to check 2447 * @param elementPath the current element path 2448 * 2449 * @throws CmsXmlException if something goes wrong 2450 */ 2451 protected void addDefaultCheckRules( 2452 CmsXmlContentDefinition rootContentDefinition, 2453 I_CmsXmlSchemaType schemaType, 2454 String elementPath) 2455 throws CmsXmlException { 2456 2457 if ((schemaType != null) && schemaType.isSimpleType()) { 2458 if ((schemaType.getMinOccurs() == 0) 2459 && (CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName()) 2460 || CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) 2461 && !m_relationChecks.containsKey(elementPath) 2462 && !m_relations.containsKey(elementPath)) { 2463 // add default check rule for the element 2464 addCheckRule(rootContentDefinition, elementPath, null, null); 2465 } 2466 } else { 2467 // recursion required 2468 CmsXmlContentDefinition nestedContentDefinition = rootContentDefinition; 2469 if (schemaType != null) { 2470 CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition)schemaType; 2471 nestedContentDefinition = nestedDefinition.getNestedContentDefinition(); 2472 } 2473 Iterator<String> itElems = nestedContentDefinition.getSchemaTypes().iterator(); 2474 while (itElems.hasNext()) { 2475 String element = itElems.next(); 2476 String path = (schemaType != null) ? CmsXmlUtils.concatXpath(elementPath, element) : element; 2477 I_CmsXmlSchemaType nestedSchema = nestedContentDefinition.getSchemaType(element); 2478 if ((schemaType == null) || !nestedSchema.equals(schemaType)) { 2479 addDefaultCheckRules(rootContentDefinition, nestedSchema, path); 2480 } 2481 } 2482 } 2483 } 2484 2485 /** 2486 * Adds the given element to the compact view set.<p> 2487 * 2488 * @param contentDefinition the XML content definition this XML content handler belongs to 2489 * @param elementName the element name 2490 * @param displayType the display type to use for the element widget 2491 * 2492 * @throws CmsXmlException in case an unknown element name is used 2493 */ 2494 protected void addDisplayType( 2495 CmsXmlContentDefinition contentDefinition, 2496 String elementName, 2497 DisplayType displayType) 2498 throws CmsXmlException { 2499 2500 if (contentDefinition.getSchemaType(elementName) == null) { 2501 throw new CmsXmlException( 2502 Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName)); 2503 } 2504 m_displayTypes.put(elementName, displayType); 2505 } 2506 2507 /** 2508 * Finally adds the field used for geo-coordinate mapping by combining the configuration 2509 * from the geomapping section and the field settings. 2510 */ 2511 protected void addGeoMappingField() { 2512 2513 CmsGeoMappingConfiguration mappingConfig = getGeoMappingConfiguration(); 2514 if (mappingConfig != null) { 2515 CmsSolrField field = new CmsSolrField( 2516 GEOMAPPING_FIELD, 2517 Collections.emptyList(), 2518 CmsLocaleManager.getDefaultLocale(), 2519 "0.000000,0.000000"); 2520 I_CmsSearchFieldMapping mapping = new CmsGeoCoordinateFieldMapping(getGeoMappingConfiguration()); 2521 field.addMapping(mapping); 2522 m_searchFields.put("__geocoord__", field); 2523 } 2524 } 2525 2526 /** 2527 * Adds an element mapping.<p> 2528 * 2529 * @param contentDefinition the XML content definition this XML content handler belongs to 2530 * @param elementName the element name to map 2531 * @param mapping the mapping to use 2532 * @param useDefault the 'useDefault' attribute 2533 * 2534 * @throws CmsXmlException in case an unknown element name is used 2535 */ 2536 protected void addMapping( 2537 CmsXmlContentDefinition contentDefinition, 2538 String elementName, 2539 String mapping, 2540 String useDefault) 2541 throws CmsXmlException { 2542 2543 if (contentDefinition.getSchemaType(elementName) == null) { 2544 throw new CmsXmlException( 2545 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName)); 2546 } 2547 2548 // store mappings as xpath to allow better control about what is mapped 2549 String xpath = CmsXmlUtils.createXpath(elementName, 1); 2550 // since 7.0.2 multiple mappings are possible, so the mappings are stored in an array 2551 List<String> values = m_elementMappings.get(xpath); 2552 if (values == null) { 2553 // there should not really be THAT much multiple mappings per value... 2554 values = new ArrayList<String>(4); 2555 m_elementMappings.put(xpath, values); 2556 } 2557 if (Boolean.parseBoolean(useDefault)) { 2558 m_mappingsUsingDefault.add(xpath + ":" + mapping); 2559 } 2560 values.add(mapping); 2561 if (mapping.startsWith(MAPTO_PROPERTY) && mapping.endsWith(":" + CmsPropertyDefinition.PROPERTY_TITLE)) { 2562 // this is a title mapping 2563 m_titleMappings.add(xpath); 2564 } 2565 } 2566 2567 /** 2568 * Adds a nested formatter element.<p> 2569 * 2570 * @param elementName the element name 2571 * @param contentDefinition the content definition 2572 * 2573 * @throws CmsXmlException in case something goes wrong 2574 */ 2575 protected void addNestedFormatter(String elementName, CmsXmlContentDefinition contentDefinition) 2576 throws CmsXmlException { 2577 2578 if (contentDefinition.getSchemaType(elementName) == null) { 2579 throw new CmsXmlException( 2580 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName)); 2581 } 2582 m_nestedFormatterElements.add(elementName); 2583 } 2584 2585 /** 2586 * Adds a Solr field for an element.<p> 2587 * 2588 * @param contentDefinition the XML content definition this XML content handler belongs to 2589 * @param field the Solr field 2590 */ 2591 @Deprecated 2592 protected void addSearchField(CmsXmlContentDefinition contentDefinition, CmsSearchField field) { 2593 2594 addSearchField(contentDefinition, field, I_CmsXmlContentHandler.MappingType.ELEMENT); 2595 } 2596 2597 /** 2598 * Adds a Solr field for an element.<p> 2599 * 2600 * @param contentDefinition the XML content definition this XML content handler belongs to 2601 * @param field the Solr field 2602 * @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 2603 */ 2604 protected void addSearchField( 2605 CmsXmlContentDefinition contentDefinition, 2606 CmsSearchField field, 2607 I_CmsXmlContentHandler.MappingType type) { 2608 2609 Locale locale = null; 2610 if (field instanceof CmsSolrField) { 2611 locale = ((CmsSolrField)field).getLocale(); 2612 } 2613 String key = CmsXmlUtils.concatXpath(locale != null ? locale.toString() : null, field.getName()); 2614 switch (type) { 2615 case PAGE: 2616 m_searchFieldsPage.put(key, field); 2617 break; 2618 case ELEMENT: 2619 default: 2620 m_searchFields.put(key, field); 2621 break; 2622 } 2623 } 2624 2625 /** 2626 * Adds a search setting for an element.<p> 2627 * 2628 * @param contentDefinition the XML content definition this XML content handler belongs to 2629 * @param elementName the element name to map 2630 * @param value the search setting value to store 2631 * 2632 * @throws CmsXmlException in case an unknown element name is used 2633 */ 2634 protected void addSearchSetting( 2635 CmsXmlContentDefinition contentDefinition, 2636 String elementName, 2637 SearchContentType value) 2638 throws CmsXmlException { 2639 2640 if (contentDefinition.getSchemaType(elementName) == null) { 2641 throw new CmsXmlException( 2642 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_SEARCHSETTINGS_1, elementName)); 2643 } 2644 // store the search exclusion as defined 2645 m_searchSettings.put(elementName, value); 2646 } 2647 2648 /** 2649 * Adds search settings as defined by 'simple' syntax in fields.<p> 2650 * 2651 * @param contentDef the content definition 2652 * @param name the element name 2653 * @param value the search setting value 2654 * @throws CmsXmlException if something goes wrong 2655 */ 2656 protected void addSimpleSearchSetting(CmsXmlContentDefinition contentDef, String name, String value) 2657 throws CmsXmlException { 2658 2659 SearchContentType searchContentType = SearchContentType.fromString(value); 2660 if (null != searchContentType) { 2661 addSearchSetting(contentDef, name, searchContentType); 2662 } else { 2663 if ("geocoords".equals(value) || "listgeocoords".equals(value)) { 2664 m_primaryGeomappingField = name; 2665 m_searchSettings.put(CmsXmlUtils.removeXpath(name), I_CmsXmlContentValue.SearchContentType.FALSE); 2666 } else { 2667 StringTemplate template = m_searchTemplateGroup.getInstanceOf(value); 2668 if ((template != null) && (template.getFormalArgument("name") != null)) { 2669 template.setAttribute("name", CmsEncoder.escapeXml(name)); 2670 String xml = template.toString(); 2671 try { 2672 Document doc = DocumentHelper.parseText(xml); 2673 initSearchSettings(doc.getRootElement(), contentDef); 2674 } catch (DocumentException e) { 2675 LOG.error(e.getLocalizedMessage(), e); 2676 } 2677 } 2678 } 2679 } 2680 } 2681 2682 /** 2683 * Adds a validation rule for a specified element.<p> 2684 * 2685 * @param contentDefinition the XML content definition this XML content handler belongs to 2686 * @param elementName the element name to add the rule to 2687 * @param regex the validation rule regular expression 2688 * @param message the message in case validation fails (may be null) 2689 * @param isWarning if true, this rule is used for warnings, otherwise it's an error 2690 * 2691 * @throws CmsXmlException in case an unknown element name is used 2692 */ 2693 protected void addValidationRule( 2694 CmsXmlContentDefinition contentDefinition, 2695 String elementName, 2696 String regex, 2697 String message, 2698 boolean isWarning) 2699 throws CmsXmlException { 2700 2701 if (contentDefinition.getSchemaType(elementName) == null) { 2702 throw new CmsXmlException( 2703 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_VALIDATION_1, elementName)); 2704 } 2705 2706 if (isWarning) { 2707 m_validationWarningRules.put(elementName, regex); 2708 if (message != null) { 2709 m_validationWarningMessages.put(elementName, message); 2710 } 2711 } else { 2712 m_validationErrorRules.put(elementName, regex); 2713 if (message != null) { 2714 m_validationErrorMessages.put(elementName, message); 2715 } 2716 } 2717 } 2718 2719 /** 2720 * Adds a GUI widget for a specified element.<p> 2721 * 2722 * @param contentDefinition the XML content definition this XML content handler belongs to 2723 * @param elementName the element name to map 2724 * @param name the widget to use as GUI for the element (registered alias or class name) 2725 * 2726 * @throws CmsXmlException in case an unknown element name is used 2727 */ 2728 protected void addWidget(CmsXmlContentDefinition contentDefinition, String elementName, String name) 2729 throws CmsXmlException { 2730 2731 if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) { 2732 throw new CmsXmlException( 2733 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_LAYOUTWIDGET_1, elementName)); 2734 } 2735 2736 if (name.indexOf(I_CmsMacroResolver.MACRO_DELIMITER) == -1) { 2737 // we can only validate this if we don't have macros 2738 if (OpenCms.getXmlContentTypeManager().getWidget(name) == null) { 2739 if (CmsStringUtil.isValidJavaClassName(name)) { 2740 try { 2741 Class<?> cls = Class.forName(name, false, getClass().getClassLoader()); 2742 if (!I_CmsWidget.class.isAssignableFrom(cls) 2743 && !I_CmsComplexWidget.class.isAssignableFrom(cls)) { 2744 throw new CmsXmlException( 2745 Messages.get().container( 2746 Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3, 2747 name, 2748 elementName, 2749 contentDefinition.getSchemaLocation())); 2750 2751 } 2752 } catch (Exception e) { 2753 throw new CmsXmlException( 2754 Messages.get().container( 2755 Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3, 2756 name, 2757 elementName, 2758 contentDefinition.getSchemaLocation()), 2759 e); 2760 } 2761 } 2762 } 2763 2764 } 2765 m_widgetNames.put(elementName, name); 2766 } 2767 2768 /** 2769 * Helper method to create a visibility configuration.<p> 2770 * 2771 * @param className the visibility handler class name 2772 * @param params the parameters for the visibility 2773 * 2774 * @return the visibility configuration 2775 */ 2776 protected VisibilityConfiguration createVisibilityConfiguration(String className, String params) { 2777 2778 I_CmsXmlContentVisibilityHandler handler = this; 2779 if (className != null) { 2780 try { 2781 handler = (I_CmsXmlContentVisibilityHandler)(Class.forName(className).newInstance()); 2782 } catch (Exception e) { 2783 LOG.error(e.getLocalizedMessage(), e); 2784 } 2785 } 2786 VisibilityConfiguration result = new VisibilityConfiguration(handler, params); 2787 return result; 2788 } 2789 2790 /** 2791 * Returns information about the availability mapping for the given availability attribute. 2792 * 2793 * @param attr the availability attribute 2794 * @return the information about the mapping 2795 */ 2796 protected MappingInfo getAttributeMapping(AttributeType attr) { 2797 2798 String target = null; 2799 String source = null; 2800 switch (attr) { 2801 case expiration: 2802 target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATEEXPIRED; 2803 break; 2804 2805 case release: 2806 target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATERELEASED; 2807 break; 2808 2809 default: 2810 break; 2811 } 2812 if (target != null) { 2813 source = getMappingSource(target); 2814 } 2815 2816 return new MappingInfo(source, target); 2817 } 2818 2819 /** 2820 * Returns the configured default locales for the content of the given resource.<p> 2821 * 2822 * @param cms the cms context 2823 * @param resource the resource path to get the default locales for 2824 * 2825 * @return the default locales of the resource 2826 */ 2827 protected List<Locale> getLocalesForResource(CmsObject cms, String resource) { 2828 2829 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(cms, resource); 2830 if ((locales == null) || locales.isEmpty()) { 2831 locales = OpenCms.getLocaleManager().getAvailableLocales(); 2832 } 2833 return locales; 2834 } 2835 2836 /** 2837 * Creates editor change handler instances for all nested fields that have configured them in their field settings 2838 * 2839 * @return editor change handlers for all nested fields for which they are configured 2840 */ 2841 protected List<I_CmsXmlContentEditorChangeHandler> getNestedEditorChangeHandlers() { 2842 2843 Multimap<String, CmsChangeHandlerConfig> configMap = ArrayListMultimap.create(); 2844 collectNestedChangeHandlerConfigs(m_contentDefinition, "", configMap); 2845 List<I_CmsXmlContentEditorChangeHandler> result = new ArrayList<>(); 2846 for (String key : configMap.keySet()) { 2847 for (CmsChangeHandlerConfig handlerConfig : configMap.get(key)) { 2848 String path = CmsStringUtil.joinPaths(key, handlerConfig.getField()); 2849 path = CmsFileUtil.removeLeadingSeparator(path); 2850 String scope = normalizeChangeHandlerScope(path); 2851 java.util.Optional<I_CmsXmlContentEditorChangeHandler> optHandler = handlerConfig.newHandler(scope); 2852 if (optHandler.isPresent()) { 2853 result.add(optHandler.get()); 2854 } 2855 } 2856 } 2857 List<I_CmsXmlContentEditorChangeHandler> nestedHandlers = result; 2858 return nestedHandlers; 2859 } 2860 2861 /** 2862 * Returns the category reference path for the given value.<p> 2863 * 2864 * @param cms the cms context 2865 * @param value the xml content value 2866 * 2867 * @return the category reference path for the given value 2868 */ 2869 protected String getReferencePath(CmsObject cms, I_CmsXmlContentValue value) { 2870 2871 // get the original file instead of the temp file 2872 CmsFile file = value.getDocument().getFile(); 2873 String resourceName = cms.getSitePath(file); 2874 if (CmsWorkplace.isTemporaryFile(file)) { 2875 StringBuffer result = new StringBuffer(resourceName.length() + 2); 2876 result.append(CmsResource.getFolderPath(resourceName)); 2877 result.append(CmsResource.getName(resourceName).substring(1)); 2878 resourceName = result.toString(); 2879 } 2880 try { 2881 List<CmsResource> listsib = cms.readSiblings(resourceName, CmsResourceFilter.ALL); 2882 for (int i = 0; i < listsib.size(); i++) { 2883 CmsResource resource = listsib.get(i); 2884 // get the default locale of the resource and set the categories 2885 List<Locale> locales = getLocalesForResource(cms, cms.getSitePath(resource)); 2886 for (Locale l : locales) { 2887 if (value.getLocale().equals(l)) { 2888 return cms.getSitePath(resource); 2889 } 2890 } 2891 } 2892 } catch (CmsVfsResourceNotFoundException e) { 2893 // may hapen if editing a new resource 2894 if (LOG.isDebugEnabled()) { 2895 LOG.debug(e.getLocalizedMessage(), e); 2896 } 2897 } catch (CmsException e) { 2898 if (LOG.isErrorEnabled()) { 2899 LOG.error(e.getLocalizedMessage(), e); 2900 } 2901 } 2902 // if the locale can not be found, just take the current file 2903 return cms.getSitePath(file); 2904 } 2905 2906 /** 2907 * Returns the validation message to be displayed if a certain rule was violated.<p> 2908 * 2909 * @param cms the current users OpenCms context 2910 * @param value the value to validate 2911 * @param regex the rule that was violated 2912 * @param valueStr the string value of the given value 2913 * @param matchResult if false, the rule was negated 2914 * @param isWarning if true, this validation indicate a warning, otherwise an error 2915 * 2916 * @return the validation message to be displayed 2917 */ 2918 protected String getValidationMessage( 2919 CmsObject cms, 2920 I_CmsXmlContentValue value, 2921 String regex, 2922 String valueStr, 2923 boolean matchResult, 2924 boolean isWarning) { 2925 2926 String message = null; 2927 if (isWarning) { 2928 message = m_validationWarningMessages.get(value.getName()); 2929 } else { 2930 message = m_validationErrorMessages.get(value.getName()); 2931 } 2932 2933 if (message == null) { 2934 if (isWarning) { 2935 message = MESSAGE_VALIDATION_DEFAULT_WARNING; 2936 } else { 2937 message = MESSAGE_VALIDATION_DEFAULT_ERROR; 2938 } 2939 } 2940 2941 // create additional macro values 2942 Map<String, String> additionalValues = new HashMap<String, String>(); 2943 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_VALUE, valueStr); 2944 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_REGEX, ((!matchResult) ? "!" : "") + regex); 2945 additionalValues.put(CmsMacroResolver.KEY_VALIDATION_PATH, value.getPath()); 2946 2947 CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages( 2948 getMessages(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms))).setAdditionalMacros(additionalValues); 2949 2950 return resolver.resolveMacros(message); 2951 } 2952 2953 /** 2954 * Called when this content handler is initialized.<p> 2955 */ 2956 protected void init() { 2957 2958 m_elementMappings = new HashMap<String, List<String>>(); 2959 m_validationErrorRules = new HashMap<String, String>(); 2960 m_validationErrorMessages = new HashMap<String, String>(); 2961 m_validationWarningRules = new HashMap<String, String>(); 2962 m_validationWarningMessages = new HashMap<String, String>(); 2963 m_defaultValues = new HashMap<String, String>(); 2964 m_configurationValues = new HashMap<String, String>(); 2965 m_searchSettings = new HashMap<String, SearchContentType>(); 2966 m_relations = new HashMap<String, CmsRelationType>(); 2967 m_relationChecks = new HashMap<String, Boolean>(); 2968 m_previewLocation = null; 2969 m_modelFolder = null; 2970 m_tabs = new ArrayList<CmsXmlContentTab>(); 2971 m_cssHeadIncludes = new LinkedHashSet<String>(); 2972 m_jsHeadIncludes = new LinkedHashSet<String>(); 2973 m_settings = new LinkedHashMap<String, CmsXmlContentProperty>(); 2974 m_titleMappings = new ArrayList<String>(2); 2975 m_formatters = new ArrayList<CmsFormatterBean>(); 2976 m_searchFields = new HashMap<String, CmsSearchField>(); 2977 m_searchFieldsPage = new HashMap<String, CmsSearchField>(); 2978 m_allowedTemplates = new CmsDefaultSet<String>(); 2979 m_allowedTemplates.setDefaultMembership(true); 2980 m_displayTypes = new HashMap<String, DisplayType>(); 2981 m_editorChangeHandlers = new ArrayList<I_CmsXmlContentEditorChangeHandler>(); 2982 m_nestedFormatterElements = new HashSet<String>(); 2983 try ( 2984 InputStream stream = CmsDefaultXmlContentHandler.class.getResourceAsStream("simple-searchsetting-configs.st")) { 2985 m_searchTemplateGroup = CmsStringUtil.readStringTemplateGroup(stream); 2986 } catch (IOException e) { 2987 LOG.error(e.getLocalizedMessage(), e); 2988 } 2989 } 2990 2991 /** 2992 * Initializes the default values for this content handler.<p> 2993 * 2994 * Using the default values from the appinfo node, it's possible to have more 2995 * sophisticated logic for generating the defaults then just using the XML schema "default" 2996 * attribute.<p> 2997 * 2998 * @param root the "defaults" element from the appinfo node of the XML content definition 2999 * @param contentDefinition the content definition the default values belong to 3000 * @throws CmsXmlException if something goes wrong 3001 */ 3002 protected void initDefaultValues(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3003 3004 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_DEFAULT); 3005 while (i.hasNext()) { 3006 // iterate all "default" elements in the "defaults" node 3007 Element element = i.next(); 3008 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3009 String defaultValue = element.attributeValue(APPINFO_ATTR_VALUE); 3010 String resolveMacrosValue = element.attributeValue(APPINFO_ATTR_RESOLVE_MACROS); 3011 if ((elementName != null) && (defaultValue != null)) { 3012 // add a default value mapping for the element 3013 addDefault(contentDefinition, elementName, defaultValue, resolveMacrosValue); 3014 } 3015 } 3016 } 3017 3018 /** 3019 * Initializes the default complex widget.<p> 3020 * 3021 * @param element the element in which the default complex widget is configured 3022 */ 3023 protected void initDefaultWidget(Element element) { 3024 3025 m_defaultWidget = element.attributeValue(APPINFO_ATTR_WIDGET); 3026 m_defaultWidgetConfig = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 3027 try { 3028 m_defaultWidgetInstance = (I_CmsComplexWidget)(Class.forName(m_defaultWidget).newInstance()); 3029 } catch (Exception e) { 3030 LOG.error(e.getLocalizedMessage(), e); 3031 } 3032 } 3033 3034 /** 3035 * Initializes the edit handler.<p> 3036 * 3037 * @param handlerElement the edit handler element 3038 */ 3039 protected void initEditHandler(Element handlerElement) { 3040 3041 String editHandlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS); 3042 Map<String, String> params = Maps.newHashMap(); 3043 Element paramsElement = handlerElement.element(APPINFO_PARAMETERS); 3044 if (paramsElement != null) { 3045 for (Element paramElement : paramsElement.elements(APPINFO_PARAM)) { 3046 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 3047 String value = paramElement.getText(); 3048 params.put(name, value); 3049 } 3050 } 3051 try { 3052 m_editHandler = (I_CmsEditHandler)Class.forName(editHandlerClass).newInstance(); 3053 m_editHandler.setParameters(params); 3054 } catch (Exception e) { 3055 LOG.error(e.getMessage(), e); 3056 } 3057 } 3058 3059 /** 3060 * Initializes the editor change handlers.<p> 3061 * 3062 * @param element the editorchangehandlers node of the app info 3063 */ 3064 protected void initEditorChangeHandlers(Element element) { 3065 3066 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_EDITOR_CHANGE_HANDLER); 3067 while (i.hasNext()) { 3068 // iterate all "default" elements in the "defaults" node 3069 Element handlerElement = i.next(); 3070 String handlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS); 3071 String configuration = handlerElement.attributeValue(APPINFO_ATTR_CONFIGURATION); 3072 String scope = handlerElement.attributeValue(APPINFO_ATTR_SCOPE); 3073 try { 3074 I_CmsXmlContentEditorChangeHandler handler = (I_CmsXmlContentEditorChangeHandler)Class.forName( 3075 handlerClass).newInstance(); 3076 handler.setConfiguration(configuration); 3077 handler.setScope(scope); 3078 m_editorChangeHandlers.add(handler); 3079 } catch (Exception e) { 3080 LOG.error(e.getLocalizedMessage(), e); 3081 } 3082 } 3083 } 3084 3085 /** 3086 * Processes a single field definition.<p> 3087 * 3088 * @param elem the parent element 3089 * @param contentDef the content definition 3090 * 3091 * @throws CmsXmlException if something goes wrong 3092 */ 3093 protected void initField(Element elem, CmsXmlContentDefinition contentDef) throws CmsXmlException { 3094 3095 String nameVal = elem.elementText(CmsConfigurationReader.N_PROPERTY_NAME); 3096 if (nameVal == null) { 3097 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_BAD_FIELD_NAME_1, nameVal)); 3098 } 3099 final String name = nameVal.trim(); 3100 3101 String ruleRegex = elem.elementText(CmsConfigurationReader.N_RULE_REGEX); 3102 String ruleType = elem.elementText(CmsConfigurationReader.N_RULE_TYPE); 3103 String error = elem.elementText(CmsConfigurationReader.N_ERROR); 3104 if (error == null) { 3105 error = ""; 3106 } 3107 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(ruleRegex)) { 3108 addValidationRule(contentDef, name, ruleRegex, error, "warning".equalsIgnoreCase(ruleType)); 3109 } 3110 3111 String defaultValue = elem.elementText(CmsConfigurationReader.N_DEFAULT); 3112 String defaultResolveMacros = elem.elementTextTrim(FieldSettingElems.DefaultResolveMacros.name()); 3113 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(defaultValue)) { 3114 addDefault(contentDef, name, defaultValue, defaultResolveMacros); 3115 } 3116 3117 String widget = elem.elementText(CmsConfigurationReader.N_WIDGET); 3118 String widgetConfig = elem.elementText(CmsConfigurationReader.N_WIDGET_CONFIG); 3119 if (widget != null) { 3120 addWidget(contentDef, name, widget); 3121 } 3122 if (widgetConfig != null) { 3123 widgetConfig = widgetConfig.trim(); 3124 addConfiguration(contentDef, name, widgetConfig); 3125 } 3126 3127 String niceName = elem.elementText(CmsConfigurationReader.N_DISPLAY_NAME); 3128 if (niceName != null) { 3129 m_fieldNiceNames.put(name, niceName); 3130 } 3131 String description = elem.elementText(CmsConfigurationReader.N_DESCRIPTION); 3132 if (description != null) { 3133 m_fieldDescriptions.put(name, description); 3134 } 3135 for (Element mappingElem : elem.elements(FieldSettingElems.Mapping.name())) { 3136 String mapTo = mappingElem.elementText(FieldSettingElems.MapTo.name()); 3137 String useDefault = mappingElem.elementText(FieldSettingElems.UseDefault.name()); 3138 if (mapTo != null) { 3139 addMapping(contentDef, name, mapTo, useDefault); 3140 } 3141 } 3142 String display = elem.elementTextTrim(FieldSettingElems.Display.name()); 3143 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(display)) { 3144 try { 3145 addDisplayType(contentDef, name, DisplayType.valueOf(display)); 3146 } catch (Exception e) { 3147 LOG.error(e.getLocalizedMessage(), e); 3148 } 3149 } 3150 String synchronization = elem.elementTextTrim(FieldSettingElems.Synchronization.name()); 3151 if (synchronization != null) { 3152 if ("strong".equals(synchronization)) { 3153 m_synchronizations.put(name, SynchronizationMode.strong); 3154 } else if (Boolean.parseBoolean(synchronization)) { 3155 m_synchronizations.put(name, SynchronizationMode.standard); 3156 } else { 3157 // we use a distinct value rather than just leaving it empty because we want to be able to override the synchronization 3158 // definition in a nested schema with the one in the top-level schema 3159 m_synchronizations.put(name, SynchronizationMode.none); 3160 } 3161 } 3162 3163 for (Element relElem : elem.elements(FieldSettingElems.Relation.name())) { 3164 String type = relElem.elementTextTrim(FieldSettingElems.Type.name()); 3165 String invalidate = relElem.elementTextTrim(FieldSettingElems.Invalidate.name()); 3166 if (type != null) { 3167 type = type.toLowerCase(); 3168 } 3169 if (invalidate != null) { 3170 invalidate = invalidate.toLowerCase(); 3171 } 3172 addCheckRule(contentDef, name, invalidate, type); 3173 } 3174 3175 for (Element visElem : elem.elements(FieldSettingElems.Visibility.name())) { 3176 String params = visElem.getText(); 3177 VisibilityConfiguration visConfig = createVisibilityConfiguration(null, params); 3178 m_visibilityConfigurations.put(name, visConfig); 3179 } 3180 3181 for (Element visElem : elem.elements(FieldSettingElems.FieldVisibility.name())) { 3182 String className = visElem.elementTextTrim(FieldSettingElems.Class.name()); 3183 String params = visElem.elementTextTrim(FieldSettingElems.Params.name()); 3184 VisibilityConfiguration visConfig = createVisibilityConfiguration(className, params); 3185 m_visibilityConfigurations.put(name, visConfig); 3186 } 3187 3188 String nestedFormatter = elem.elementTextTrim(FieldSettingElems.NestedFormatter.name()); 3189 if (Boolean.parseBoolean(nestedFormatter)) { 3190 m_nestedFormatterElements.add(name); 3191 } 3192 3193 String search = elem.elementTextTrim(FieldSettingElems.Search.name()); 3194 if (search != null) { 3195 addSimpleSearchSetting(contentDef, name, search); 3196 } 3197 3198 String ifInvalidRelationStr = elem.elementTextTrim(FieldSettingElems.IfInvalidRelation.name()); 3199 if (CmsStringUtil.isEmptyOrWhitespaceOnly(ifInvalidRelationStr)) { 3200 ifInvalidRelationStr = null; 3201 } 3202 if (ifInvalidRelationStr != null) { 3203 if (name.contains("[") || name.contains("/")) { 3204 LOG.error("Only simple field names allowed for the IfInvalidRelation field setting."); 3205 } else { 3206 try { 3207 InvalidRelationAction ifInvalidRelation = InvalidRelationAction.valueOf(ifInvalidRelationStr); 3208 m_invalidRelationActions.put(name, ifInvalidRelation); 3209 } catch (Exception e) { 3210 LOG.error(e.getLocalizedMessage(), e); 3211 } 3212 } 3213 3214 } 3215 3216 for (Element changeHandlerElem : elem.elements(N_CHANGEHANDLER)) { 3217 String config = changeHandlerElem.attributeValue(A_CONFIGURATION); 3218 String className = changeHandlerElem.getText().trim(); 3219 CmsChangeHandlerConfig entry = new CmsChangeHandlerConfig(name, className, config); 3220 m_changeHandlerConfigs.add(entry); 3221 3222 } 3223 } 3224 3225 /** 3226 * Processes all field declarations in the schema.<p> 3227 * 3228 * @param parent the parent element 3229 * @param contentDef the content definition 3230 * 3231 * @throws CmsXmlException if something goes wrong 3232 */ 3233 protected void initFields(Element parent, CmsXmlContentDefinition contentDef) throws CmsXmlException { 3234 3235 for (Element fieldElem : parent.elements(N_SETTING)) { 3236 initField(fieldElem, contentDef); 3237 } 3238 } 3239 3240 /** 3241 * Initializes the formatters for this content handler.<p> 3242 * 3243 * @param root the "formatters" element from the appinfo node of the XML content definition 3244 * @param contentDefinition the content definition the formatters belong to 3245 */ 3246 protected void initFormatters(Element root, CmsXmlContentDefinition contentDefinition) { 3247 3248 // reading the include resources common for all formatters 3249 Iterator<Element> itFormatter = CmsXmlGenericWrapper.elementIterator(root, APPINFO_FORMATTER); 3250 while (itFormatter.hasNext()) { 3251 // iterate all "formatter" elements in the "formatters" node 3252 Element element = itFormatter.next(); 3253 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3254 if (CmsStringUtil.isEmptyOrWhitespaceOnly(type)) { 3255 // if not set use "*" as default for type 3256 type = CmsFormatterBean.WILDCARD_TYPE; 3257 } 3258 String jspRootPath = element.attributeValue(APPINFO_ATTR_URI); 3259 String minWidthStr = element.attributeValue(APPINFO_ATTR_MINWIDTH); 3260 String maxWidthStr = element.attributeValue(APPINFO_ATTR_MAXWIDTH); 3261 String preview = element.attributeValue(APPINFO_ATTR_PREVIEW); 3262 String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT); 3263 m_formatters.add( 3264 new CmsFormatterBean( 3265 type, 3266 jspRootPath, 3267 minWidthStr, 3268 maxWidthStr, 3269 preview, 3270 searchContent, 3271 contentDefinition.getSchemaLocation())); 3272 } 3273 } 3274 3275 /** 3276 * Initializes the head includes for this content handler.<p> 3277 * 3278 * @param root the "headincludes" element from the appinfo node of the XML content definition 3279 * @param contentDefinition the content definition the head-includes belong to 3280 */ 3281 protected void initHeadIncludes(Element root, CmsXmlContentDefinition contentDefinition) { 3282 3283 Iterator<Element> itInclude = CmsXmlGenericWrapper.elementIterator(root, APPINFO_HEAD_INCLUDE); 3284 while (itInclude.hasNext()) { 3285 Element element = itInclude.next(); 3286 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3287 String uri = element.attributeValue(APPINFO_ATTR_URI); 3288 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(uri)) { 3289 if (ATTRIBUTE_INCLUDE_TYPE_CSS.equals(type)) { 3290 m_cssHeadIncludes.add(uri); 3291 } else if (ATTRIBUTE_INCLUDE_TYPE_JAVASCRIPT.equals(type)) { 3292 m_jsHeadIncludes.add(uri); 3293 } 3294 } 3295 } 3296 } 3297 3298 /** 3299 * Reads the JSON renderer settings. 3300 * 3301 * @param element the configuration XML element 3302 */ 3303 protected void initJsonRenderer(Element element) { 3304 3305 String cls = element.attributeValue(APPINFO_ATTR_CLASS); 3306 Map<String, String> params = new HashMap<>(); 3307 for (Element paramElement : element.elements(APPINFO_PARAM)) { 3308 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 3309 String value = paramElement.getText(); 3310 params.put(name, value); 3311 } 3312 m_jsonRendererSettings = new JsonRendererSettings(cls, params); 3313 3314 } 3315 3316 /** 3317 * Initializes the layout for this content handler.<p> 3318 * 3319 * Unless otherwise instructed, the editor uses one specific GUI widget for each 3320 * XML value schema type. For example, for a {@link org.opencms.xml.types.CmsXmlStringValue} 3321 * the default widget is the {@link org.opencms.widgets.CmsInputWidget}. 3322 * However, certain values can also use more then one widget, for example you may 3323 * also use a {@link org.opencms.widgets.CmsCheckboxWidget} for a String value, 3324 * and as a result the Strings possible values would be either <code>"false"</code> or <code>"true"</code>, 3325 * but nevertheless be a String.<p> 3326 * 3327 * The widget to use can further be controlled using the <code>widget</code> attribute. 3328 * You can specify either a valid widget alias such as <code>StringWidget</code>, 3329 * or the name of a Java class that implements <code>{@link I_CmsWidget}</code>.<p> 3330 * 3331 * Configuration options to the widget can be passed using the <code>configuration</code> 3332 * attribute. You can specify any String as configuration. This String is then passed 3333 * to the widget during initialization. It's up to the individual widget implementation 3334 * to interpret this configuration String.<p> 3335 * 3336 * @param root the "layouts" element from the appinfo node of the XML content definition 3337 * @param contentDefinition the content definition the layout belongs to 3338 * 3339 * @throws CmsXmlException if something goes wrong 3340 */ 3341 protected void initLayouts(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3342 3343 m_useAcacia = safeParseBoolean(root.attributeValue(ATTR_USE_ACACIA), true); 3344 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_LAYOUT); 3345 while (i.hasNext()) { 3346 // iterate all "layout" elements in the "layouts" node 3347 Element element = i.next(); 3348 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3349 String widgetClassOrAlias = element.attributeValue(APPINFO_ATTR_WIDGET); 3350 String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 3351 String displayStr = element.attributeValue(APPINFO_ATTR_DISPLAY); 3352 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(displayStr) && (elementName != null)) { 3353 addDisplayType(contentDefinition, elementName, DisplayType.valueOf(displayStr)); 3354 } 3355 if ((elementName != null) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(widgetClassOrAlias)) { 3356 // add a widget mapping for the element 3357 addWidget(contentDefinition, elementName, widgetClassOrAlias); 3358 if (configuration != null) { 3359 addConfiguration(contentDefinition, elementName, configuration); 3360 } 3361 } 3362 } 3363 } 3364 3365 /** 3366 * Initializes the element mappings for this content handler.<p> 3367 * 3368 * Element mappings allow storing values from the XML content in other locations. 3369 * For example, if you have an element called "Title", it's likely a good idea to 3370 * store the value of this element also in the "Title" property of a XML content resource.<p> 3371 * 3372 * @param root the "mappings" element from the appinfo node of the XML content definition 3373 * @param contentDefinition the content definition the mappings belong to 3374 * @throws CmsXmlException if something goes wrong 3375 */ 3376 protected void initMappings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3377 3378 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_MAPPING); 3379 while (i.hasNext()) { 3380 // iterate all "mapping" elements in the "mappings" node 3381 Element element = i.next(); 3382 // this is a mapping node 3383 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3384 String maptoName = element.attributeValue(APPINFO_ATTR_MAPTO); 3385 String useDefault = element.attributeValue(APPINFO_ATTR_USE_DEFAULT); 3386 if ((elementName != null) && (maptoName != null)) { 3387 // add the element mapping 3388 addMapping(contentDefinition, elementName, maptoName, useDefault); 3389 } 3390 } 3391 } 3392 3393 /** 3394 * Initializes the folder containing the model file(s) for this content handler.<p> 3395 * 3396 * @param root the "modelfolder" element from the appinfo node of the XML content definition 3397 * @param contentDefinition the content definition the model folder belongs to 3398 * @throws CmsXmlException if something goes wrong 3399 */ 3400 protected void initModelFolder(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3401 3402 String master = root.attributeValue(APPINFO_ATTR_URI); 3403 if (master == null) { 3404 throw new CmsXmlException( 3405 Messages.get().container( 3406 Messages.ERR_XMLCONTENT_MISSING_MODELFOLDER_URI_2, 3407 root.getName(), 3408 contentDefinition.getSchemaLocation())); 3409 } 3410 m_modelFolder = master; 3411 } 3412 3413 /** 3414 * Initializes the nested formatter fields.<p> 3415 * 3416 * @param element the formatters element 3417 * @param contentDefinition the content definition 3418 * 3419 * @throws CmsXmlException in case something goes wron 3420 */ 3421 protected void initNestedFormatters(Element element, CmsXmlContentDefinition contentDefinition) 3422 throws CmsXmlException { 3423 3424 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_NESTED_FORMATTER); 3425 while (i.hasNext()) { 3426 // iterate all "default" elements in the "defaults" node 3427 Element handlerElement = i.next(); 3428 String formatterElement = handlerElement.attributeValue(APPINFO_ATTR_ELEMENT); 3429 addNestedFormatter(formatterElement, contentDefinition); 3430 } 3431 } 3432 3433 /** 3434 * Initializes the parameters from the schema.<p> 3435 * 3436 * @param root the parameter root element 3437 */ 3438 protected void initParameters(Element root) { 3439 3440 m_parameters.clear(); 3441 for (Element paramElement : root.elements(APPINFO_PARAM)) { 3442 String name = paramElement.attributeValue(APPINFO_ATTR_NAME); 3443 String value = paramElement.getText(); 3444 m_parameters.put(name, value); 3445 } 3446 3447 } 3448 3449 /** 3450 * Initializes the preview location for this content handler.<p> 3451 * 3452 * @param root the "preview" element from the appinfo node of the XML content definition 3453 * @param contentDefinition the content definition the validation rules belong to 3454 * @throws CmsXmlException if something goes wrong 3455 */ 3456 protected void initPreview(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3457 3458 String preview = root.attributeValue(APPINFO_ATTR_URI); 3459 if (preview == null) { 3460 throw new CmsXmlException( 3461 Messages.get().container( 3462 Messages.ERR_XMLCONTENT_MISSING_PREVIEW_URI_2, 3463 root.getName(), 3464 contentDefinition.getSchemaLocation())); 3465 } 3466 m_previewLocation = preview; 3467 } 3468 3469 /** 3470 * Initializes the relation configuration for this content handler.<p> 3471 * 3472 * OpenCms performs link checks for all OPTIONAL links defined in XML content values of type 3473 * OpenCmsVfsFile. However, for most projects in the real world a more fine-grained control 3474 * over the link check process is required. For these cases, individual relation behavior can 3475 * be defined for the appinfo node.<p> 3476 * 3477 * Additional here can be defined an optional type for the relations, for instance.<p> 3478 * 3479 * @param root the "relations" element from the appinfo node of the XML content definition 3480 * @param contentDefinition the content definition the check rules belong to 3481 * 3482 * @throws CmsXmlException if something goes wrong 3483 */ 3484 protected void initRelations(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3485 3486 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_RELATION); 3487 while (i.hasNext()) { 3488 // iterate all "checkrule" elements in the "checkrule" node 3489 Element element = i.next(); 3490 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3491 String invalidate = element.attributeValue(APPINFO_ATTR_INVALIDATE); 3492 if (invalidate != null) { 3493 invalidate = invalidate.toUpperCase(); 3494 } 3495 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3496 if (type != null) { 3497 type = type.toLowerCase(); 3498 } 3499 if (elementName != null) { 3500 // add a check rule for the element 3501 addCheckRule(contentDefinition, elementName, invalidate, type); 3502 } 3503 } 3504 } 3505 3506 /** 3507 * Initializes the resource bundle to use for localized messages in this content handler.<p> 3508 * 3509 * @param root the "resourcebundle" element from the appinfo node of the XML content definition 3510 * @param contentDefinition the content definition the validation rules belong to 3511 * @param single if <code>true</code> we process the classic sinle line entry, otherwise it's the multiple line setting 3512 * 3513 * @throws CmsXmlException if something goes wrong 3514 */ 3515 protected void initResourceBundle(Element root, CmsXmlContentDefinition contentDefinition, boolean single) 3516 throws CmsXmlException { 3517 3518 if (m_messageBundleNames == null) { 3519 // it's uncommon to have more then one bundle so just initialize an array length of 2 3520 m_messageBundleNames = new ArrayList<String>(2); 3521 } 3522 3523 if (single) { 3524 // single "resourcebundle" node 3525 3526 String messageBundleName = root.attributeValue(APPINFO_ATTR_NAME); 3527 if (messageBundleName == null) { 3528 throw new CmsXmlException( 3529 Messages.get().container( 3530 Messages.ERR_XMLCONTENT_MISSING_RESOURCE_BUNDLE_NAME_2, 3531 root.getName(), 3532 contentDefinition.getSchemaLocation())); 3533 } 3534 if (!m_messageBundleNames.contains(messageBundleName)) { 3535 // avoid duplicates 3536 m_messageBundleNames.add(messageBundleName); 3537 } 3538 // clear the cached resource bundles for this bundle 3539 CmsResourceBundleLoader.flushBundleCache(messageBundleName, false); 3540 3541 } else { 3542 // multiple "resourcebundles" node 3543 3544 // get an iterator for all "propertybundle" subnodes 3545 Iterator<Element> propertybundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_PROPERTYBUNDLE); 3546 while (propertybundles.hasNext()) { 3547 // iterate all "propertybundle" elements in the "resourcebundle" node 3548 Element propBundle = propertybundles.next(); 3549 String propertyBundleName = propBundle.attributeValue(APPINFO_ATTR_NAME); 3550 if (!m_messageBundleNames.contains(propertyBundleName)) { 3551 // avoid duplicates 3552 m_messageBundleNames.add(propertyBundleName); 3553 } 3554 // clear the cached resource bundles for this bundle 3555 CmsResourceBundleLoader.flushBundleCache(propertyBundleName, false); 3556 } 3557 3558 // get an iterator for all "xmlbundle" subnodes 3559 Iterator<Element> xmlbundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_XMLBUNDLE); 3560 while (xmlbundles.hasNext()) { 3561 Element xmlbundle = xmlbundles.next(); 3562 String xmlBundleName = xmlbundle.attributeValue(APPINFO_ATTR_NAME); 3563 // cache the bundle from the XML 3564 if (!m_messageBundleNames.contains(xmlBundleName)) { 3565 // avoid duplicates 3566 m_messageBundleNames.add(xmlBundleName); 3567 } 3568 // clear the cached resource bundles for this bundle 3569 CmsResourceBundleLoader.flushBundleCache(xmlBundleName, true); 3570 Iterator<Element> bundles = CmsXmlGenericWrapper.elementIterator(xmlbundle, APPINFO_BUNDLE); 3571 while (bundles.hasNext()) { 3572 // iterate all "bundle" elements in the "xmlbundle" node 3573 Element bundle = bundles.next(); 3574 String localeStr = bundle.attributeValue(APPINFO_ATTR_LOCALE); 3575 Locale locale; 3576 if (CmsStringUtil.isEmptyOrWhitespaceOnly(localeStr)) { 3577 // no locale set, so use no locale 3578 locale = null; 3579 } else { 3580 // use provided locale 3581 locale = CmsLocaleManager.getLocale(localeStr); 3582 } 3583 boolean isDefaultLocaleAndNotNull = (locale != null) 3584 && locale.equals(CmsLocaleManager.getDefaultLocale()); 3585 3586 CmsListResourceBundle xmlBundle = null; 3587 3588 Iterator<Element> resources = CmsXmlGenericWrapper.elementIterator(bundle, APPINFO_RESOURCE); 3589 while (resources.hasNext()) { 3590 // now collect all resource bundle keys 3591 Element resource = resources.next(); 3592 String key = resource.attributeValue(APPINFO_ATTR_KEY); 3593 String value = resource.attributeValue(APPINFO_ATTR_VALUE); 3594 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) { 3595 // read from inside XML tag if value attribute is not set 3596 value = resource.getTextTrim(); 3597 } 3598 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(key) 3599 && CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 3600 if (xmlBundle == null) { 3601 // use lazy initilaizing of the bundle 3602 xmlBundle = new CmsListResourceBundle(); 3603 } 3604 xmlBundle.addMessage(key.trim(), value.trim()); 3605 } 3606 } 3607 if (xmlBundle != null) { 3608 CmsResourceBundleLoader.addBundleToCache(xmlBundleName, locale, xmlBundle); 3609 if (isDefaultLocaleAndNotNull) { 3610 CmsResourceBundleLoader.addBundleToCache(xmlBundleName, null, xmlBundle); 3611 } 3612 } 3613 } 3614 } 3615 } 3616 } 3617 3618 /** 3619 * Initializes the search exclusions values for this content handler.<p> 3620 * 3621 * For the full text search, the value of all elements in one locale of the XML content are combined 3622 * to one big text, which is referred to as the "content" in the context of the full text search. 3623 * With this option, it is possible to hide certain elements from this "content" that does not make sense 3624 * to include in the full text search.<p> 3625 * 3626 * @param root the "searchsettings" element from the appinfo node of the XML content definition 3627 * @param contentDefinition the content definition the default values belong to 3628 * 3629 * @throws CmsXmlException if something goes wrong 3630 */ 3631 protected void initSearchSettings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3632 3633 String containerPageOnly = root.attributeValue(APPINFO_ATTR_CONTAINER_PAGE_ONLY); 3634 if (!CmsStringUtil.isEmpty(containerPageOnly)) { 3635 m_containerPageOnly = Boolean.valueOf(containerPageOnly).booleanValue(); 3636 } 3637 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SEARCHSETTING); 3638 while (i.hasNext()) { 3639 Element element = i.next(); 3640 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3641 String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT); 3642 SearchContentType searchContentType = SearchContentType.fromString(searchContent); 3643 if (elementName != null) { 3644 addSearchSetting(contentDefinition, elementName, searchContentType); 3645 } 3646 Iterator<Element> it = CmsXmlGenericWrapper.elementIterator(element, APPINFO_SOLR_FIELD); 3647 Element solrElement; 3648 while (it.hasNext()) { 3649 solrElement = it.next(); 3650 3651 String localeNames = solrElement.attributeValue(APPINFO_ATTR_LOCALE); 3652 boolean localized = true; 3653 if ((localeNames != null) 3654 && (localeNames.equals("none") || localeNames.equals("null") || localeNames.trim().equals(""))) { 3655 localized = false; 3656 } 3657 List<Locale> locales = null; 3658 if (localized) { 3659 locales = OpenCms.getLocaleManager().getAvailableLocales(localeNames); 3660 if (localized && ((locales == null) || locales.isEmpty())) { 3661 locales = OpenCms.getLocaleManager().getAvailableLocales(); 3662 } else if (locales.isEmpty()) { 3663 locales.add(CmsLocaleManager.getDefaultLocale()); 3664 } 3665 } else { 3666 locales = Collections.singletonList(null); 3667 } 3668 for (Locale locale : locales) { 3669 String targetField = solrElement.attributeValue(APPINFO_ATTR_TARGET_FIELD); 3670 if (localized) { 3671 targetField = targetField + "_" + locale.toString(); 3672 } 3673 String sourceField = solrElement.attributeValue(APPINFO_ATTR_SOURCE_FIELD); 3674 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(sourceField)) { 3675 int lastUnderScore = sourceField.lastIndexOf("_"); 3676 if (lastUnderScore > 0) { 3677 sourceField = sourceField.substring(lastUnderScore); 3678 } 3679 targetField += sourceField; 3680 } 3681 3682 String copyFieldNames = solrElement.attributeValue(APPINFO_ATTR_COPY_FIELDS, ""); 3683 List<String> copyFields = CmsStringUtil.splitAsList(copyFieldNames, ','); 3684 String defaultValue = solrElement.attributeValue(APPINFO_ATTR_DEFAULT); 3685 CmsSolrField field = new CmsSolrField(targetField, copyFields, locale, defaultValue); 3686 3687 // create the field mappings for this element 3688 Iterator<Element> ite = CmsXmlGenericWrapper.elementIterator(solrElement, APPINFO_ATTR_MAPPING); 3689 while (ite.hasNext()) { 3690 Element mappingElement = ite.next(); 3691 field.addMapping( 3692 createSearchFieldMapping(contentDefinition, mappingElement, locale, elementName)); 3693 } 3694 3695 // if no mapping was defined yet, create a mapping for the element itself 3696 if ((field.getMappings() == null) || field.getMappings().isEmpty()) { 3697 CmsSearchFieldMapping map = new CmsSearchFieldMapping( 3698 CmsSearchFieldMappingType.ITEM, 3699 elementName); 3700 if (localized) { 3701 map.setLocale(locale); 3702 } 3703 field.addMapping(map); 3704 } 3705 Set<I_CmsXmlContentHandler.MappingType> mappingTypes = parseSearchMappingTypes(solrElement); 3706 for (I_CmsXmlContentHandler.MappingType type : mappingTypes) { 3707 addSearchField(contentDefinition, field, type); 3708 } 3709 } 3710 } 3711 } 3712 } 3713 3714 /** 3715 * Initializes the element settings for this content handler.<p> 3716 * 3717 * @param root the "settings" element from the appinfo node of the XML content definition 3718 * @param contentDefinition the content definition the element settings belong to 3719 */ 3720 protected void initSettings(Element root, CmsXmlContentDefinition contentDefinition) { 3721 3722 Iterator<Element> itProperties = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SETTING); 3723 while (itProperties.hasNext()) { 3724 Element element = itProperties.next(); 3725 CmsXmlContentProperty setting = new CmsXmlContentProperty( 3726 element.attributeValue(APPINFO_ATTR_NAME), 3727 element.attributeValue(APPINFO_ATTR_TYPE), 3728 element.attributeValue(APPINFO_ATTR_WIDGET), 3729 element.attributeValue(APPINFO_ATTR_WIDGET_CONFIG), 3730 element.attributeValue(APPINFO_ATTR_RULE_REGEX), 3731 element.attributeValue(APPINFO_ATTR_RULE_TYPE), 3732 element.attributeValue(APPINFO_ATTR_DEFAULT), 3733 element.attributeValue(APPINFO_ATTR_NICE_NAME), 3734 element.attributeValue(APPINFO_ATTR_DESCRIPTION), 3735 element.attributeValue(APPINFO_ATTR_ERROR), 3736 element.attributeValue(APPINFO_ATTR_PREFERFOLDER)); 3737 String name = setting.getName(); 3738 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(name)) { 3739 m_settings.put(name, setting); 3740 } 3741 } 3742 } 3743 3744 /** 3745 * Initializes the locale synchronizations elements.<p> 3746 * 3747 * @param root the synchronizations element of the content schema appinfo. 3748 * @param contentDefinition the content definition 3749 */ 3750 protected void initSynchronizations(Element root, CmsXmlContentDefinition contentDefinition) { 3751 3752 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_SYNCHRONIZATION)); 3753 for (Element element : elements) { 3754 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3755 // 'strong' not supported in the old notation 3756 m_synchronizations.put(elementName, SynchronizationMode.standard); 3757 } 3758 } 3759 3760 /** 3761 * Initializes the tabs for this content handler.<p> 3762 * 3763 * @param root the "tabs" element from the appinfo node of the XML content definition 3764 * @param contentDefinition the content definition the tabs belong to 3765 */ 3766 protected void initTabs(Element root, CmsXmlContentDefinition contentDefinition) { 3767 3768 if (Boolean.valueOf(root.attributeValue(APPINFO_ATTR_USEALL, CmsStringUtil.FALSE)).booleanValue()) { 3769 // all first level elements should be treated as tabs 3770 Iterator<I_CmsXmlSchemaType> i = contentDefinition.getTypeSequence().iterator(); 3771 while (i.hasNext()) { 3772 // get the type 3773 I_CmsXmlSchemaType type = i.next(); 3774 m_tabs.add(new CmsXmlContentTab(type.getName())); 3775 } 3776 } else { 3777 // manual definition of tabs 3778 Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_TAB); 3779 while (i.hasNext()) { 3780 // iterate all "tab" elements in the "tabs" node 3781 Element element = i.next(); 3782 // this is a tab node 3783 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3784 String collapseValue = element.attributeValue(APPINFO_ATTR_COLLAPSE, CmsStringUtil.TRUE); 3785 Node descriptionNode = element.selectSingleNode(APPINFO_ATTR_DESCRIPTION + "/text()"); 3786 String description = null; 3787 if (descriptionNode != null) { 3788 description = descriptionNode.getText(); 3789 } else { 3790 description = element.attributeValue(APPINFO_ATTR_DESCRIPTION); 3791 } 3792 3793 String tabName = element.attributeValue(APPINFO_ATTR_NAME, elementName); 3794 if (elementName != null) { 3795 // add the element tab 3796 m_tabs.add( 3797 new CmsXmlContentTab( 3798 elementName, 3799 Boolean.valueOf(collapseValue).booleanValue(), 3800 tabName, 3801 description)); 3802 } 3803 } 3804 // check if first element has been defined as tab 3805 I_CmsXmlSchemaType type = contentDefinition.getTypeSequence().get(0); 3806 CmsXmlContentTab tab = new CmsXmlContentTab(type.getName()); 3807 if (!m_tabs.contains(tab)) { 3808 m_tabs.add(0, tab); 3809 } 3810 } 3811 } 3812 3813 /** 3814 * Initializes the forbidden template contexts.<p> 3815 * 3816 * @param root the root XML element 3817 * @param contentDefinition the content definition 3818 */ 3819 protected void initTemplates(Element root, CmsXmlContentDefinition contentDefinition) { 3820 3821 String strEnabledByDefault = root.attributeValue(ATTR_ENABLED_BY_DEFAULT); 3822 m_allowedTemplates.setDefaultMembership(safeParseBoolean(strEnabledByDefault, true)); 3823 List<Node> elements = root.selectNodes(APPINFO_TEMPLATE); 3824 for (Node elem : elements) { 3825 boolean enabled = safeParseBoolean(((Element)elem).attributeValue(ATTR_ENABLED), true); 3826 String templateName = elem.getText().trim(); 3827 m_allowedTemplates.setContains(templateName, enabled); 3828 } 3829 m_allowedTemplates.freeze(); 3830 } 3831 3832 /** 3833 * Initializes the validation rules this content handler.<p> 3834 * 3835 * OpenCms always performs XML schema validation for all XML contents. However, 3836 * for most projects in the real world a more fine-grained control over the validation process is 3837 * required. For these cases, individual validation rules can be defined for the appinfo node.<p> 3838 * 3839 * @param root the "validationrules" element from the appinfo node of the XML content definition 3840 * @param contentDefinition the content definition the validation rules belong to 3841 * 3842 * @throws CmsXmlException if something goes wrong 3843 */ 3844 protected void initValidationRules(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException { 3845 3846 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_RULE)); 3847 elements.addAll(CmsXmlGenericWrapper.elements(root, APPINFO_VALIDATIONRULE)); 3848 Iterator<Element> i = elements.iterator(); 3849 while (i.hasNext()) { 3850 // iterate all "rule" or "validationrule" elements in the "validationrules" node 3851 Element element = i.next(); 3852 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3853 String regex = element.attributeValue(APPINFO_ATTR_REGEX); 3854 String type = element.attributeValue(APPINFO_ATTR_TYPE); 3855 if (type != null) { 3856 type = type.toLowerCase(); 3857 } 3858 String message = element.attributeValue(APPINFO_ATTR_MESSAGE); 3859 if ((elementName != null) && (regex != null)) { 3860 // add a validation rule for the element 3861 addValidationRule( 3862 contentDefinition, 3863 elementName, 3864 regex, 3865 message, 3866 APPINFO_ATTR_TYPE_WARNING.equals(type)); 3867 } 3868 } 3869 } 3870 3871 /** 3872 * Initializes the content visibility settings.<p> 3873 * 3874 * @param root the visibilities appinfo element 3875 * @param contentDefinition the content definition 3876 */ 3877 protected void initVisibilities(Element root, CmsXmlContentDefinition contentDefinition) { 3878 3879 m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>(); 3880 String mainHandlerClassName = root.attributeValue(APPINFO_ATTR_CLASS); 3881 // using self as the default visibility handler implementation 3882 I_CmsXmlContentVisibilityHandler mainHandler = this; 3883 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mainHandlerClassName)) { 3884 try { 3885 // in case there is a main handler configured, try to instanciate it 3886 Class<?> handlerClass = Class.forName(mainHandlerClassName); 3887 mainHandler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance(); 3888 } catch (Exception e) { 3889 LOG.error(e.getLocalizedMessage(), e); 3890 } 3891 } 3892 List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_VISIBILITY)); 3893 for (Element element : elements) { 3894 try { 3895 String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT); 3896 String handlerClassName = element.attributeValue(APPINFO_ATTR_CLASS); 3897 String params = element.attributeValue(APPINFO_ATTR_PARAMS); 3898 I_CmsXmlContentVisibilityHandler handler = null; 3899 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(handlerClassName)) { 3900 3901 Class<?> handlerClass = Class.forName(handlerClassName); 3902 handler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance(); 3903 } else { 3904 handler = mainHandler; 3905 } 3906 m_visibilityConfigurations.put(elementName, new VisibilityConfiguration(handler, params)); 3907 3908 } catch (Exception e) { 3909 LOG.error(e.getLocalizedMessage(), e); 3910 } 3911 } 3912 } 3913 3914 /** 3915 * Returns the is-invalidate-parent flag for the given xpath.<p> 3916 * 3917 * @param xpath the path to get the check rule for 3918 * 3919 * @return the configured is-invalidate-parent flag for the given xpath 3920 */ 3921 protected boolean isInvalidateParent(String xpath) { 3922 3923 if (!CmsXmlUtils.isDeepXpath(xpath)) { 3924 return false; 3925 } 3926 Boolean isInvalidateParent = null; 3927 // look up the default from the configured mappings 3928 isInvalidateParent = m_relationChecks.get(xpath); 3929 if (isInvalidateParent == null) { 3930 // no value found, try default xpath 3931 String path = CmsXmlUtils.removeXpath(xpath); 3932 // look up the default value again without indexes 3933 isInvalidateParent = m_relationChecks.get(path); 3934 } 3935 if (isInvalidateParent == null) { 3936 return false; 3937 } 3938 return isInvalidateParent.booleanValue(); 3939 } 3940 3941 /** 3942 * Returns the localized resource string for a given message key according to the configured resource bundle 3943 * of this content handler.<p> 3944 * 3945 * If the key was not found in the configured bundle, or no bundle is configured for this 3946 * content handler, the return value is 3947 * <code>"??? " + keyName + " ???"</code>.<p> 3948 * 3949 * @param keyName the key for the desired string 3950 * @param locale the locale to get the key from 3951 * 3952 * @return the resource string for the given key 3953 * 3954 * @see CmsMessages#formatUnknownKey(String) 3955 * @see CmsMessages#isUnknownKey(String) 3956 */ 3957 protected String key(String keyName, Locale locale) { 3958 3959 CmsMessages messages = getMessages(locale); 3960 if (messages != null) { 3961 return messages.key(keyName); 3962 } 3963 return CmsMessages.formatUnknownKey(keyName); 3964 } 3965 3966 /** 3967 * @param solrElement the XML node of the <solrfield> node 3968 * @return parsed values of the attribute "addto" 3969 */ 3970 protected Set<MappingType> parseSearchMappingTypes(Element solrElement) { 3971 3972 Set<MappingType> result = new HashSet<MappingType>(); 3973 String mappingTypes = solrElement.attributeValue(APPINFO_ATTR_ADD_TO); 3974 if (mappingTypes != null) { 3975 String[] types = mappingTypes.split(","); 3976 for (int i = 0; i < types.length; i++) { 3977 String type = types[i].trim(); 3978 if (APPINFO_VALUE_ADD_TO_PAGE.equals(type)) { 3979 result.add(MappingType.PAGE); 3980 } else if (APPINFO_VALUE_ADD_TO_CONTENT.equals(type)) { 3981 result.add(MappingType.ELEMENT); 3982 } 3983 } 3984 } else { 3985 // for backwards compatibility 3986 result.add(MappingType.ELEMENT); 3987 } 3988 3989 return result; 3990 } 3991 3992 /** 3993 * Removes property values on resources for non-existing, optional elements.<p> 3994 * 3995 * @param cms the current users OpenCms context 3996 * @param file the file which is currently being prepared for writing 3997 * @param content the XML content to remove the property values for 3998 * @throws CmsException in case of read/write errors accessing the OpenCms VFS 3999 */ 4000 protected void removeEmptyMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 4001 4002 List<CmsResource> siblings = null; 4003 CmsObject rootCms = null; 4004 4005 Iterator<Map.Entry<String, List<String>>> allMappings = m_elementMappings.entrySet().iterator(); 4006 while (allMappings.hasNext()) { 4007 Map.Entry<String, List<String>> e = allMappings.next(); 4008 String path = e.getKey(); 4009 List<String> mappings = e.getValue(); 4010 if (mappings == null) { 4011 // nothing to do if we have no mappings at all 4012 continue; 4013 } 4014 if ((siblings == null) || (rootCms == null)) { 4015 // create OpenCms user context initialized with "/" as site root to read all siblings 4016 rootCms = OpenCms.initCmsObject(cms); 4017 rootCms.getRequestContext().setSiteRoot("/"); 4018 siblings = rootCms.readSiblings(content.getFile().getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION); 4019 } 4020 for (int v = mappings.size() - 1; v >= 0; v--) { 4021 String mapping = mappings.get(v); 4022 4023 if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) { 4024 for (int i = 0; i < siblings.size(); i++) { 4025 4026 // get siblings filename and locale 4027 String filename = siblings.get(i).getRootPath(); 4028 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 4029 4030 if (!content.hasLocale(locale)) { 4031 // only remove property if the locale fits 4032 continue; 4033 } 4034 if (content.hasValue(path, locale)) { 4035 // value is available, property must be kept 4036 continue; 4037 } 4038 4039 if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) { 4040 4041 String property; 4042 boolean shared = false; 4043 if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) { 4044 property = mapping.substring(MAPTO_PROPERTY_LIST_INDIVIDUAL.length()); 4045 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) { 4046 property = mapping.substring(MAPTO_PROPERTY_LIST_SHARED.length()); 4047 shared = true; 4048 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST)) { 4049 property = mapping.substring(MAPTO_PROPERTY_LIST.length()); 4050 } else if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) { 4051 property = mapping.substring(MAPTO_PROPERTY_SHARED.length()); 4052 shared = true; 4053 } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) { 4054 property = mapping.substring(MAPTO_PROPERTY_INDIVIDUAL.length()); 4055 } else { 4056 property = mapping.substring(MAPTO_PROPERTY.length()); 4057 } 4058 rootCms.writePropertyObject( 4059 filename, 4060 new CmsProperty( 4061 property, 4062 CmsProperty.DELETE_VALUE, 4063 shared ? CmsProperty.DELETE_VALUE : null)); 4064 } 4065 } 4066 } else if (mapping.startsWith(MAPTO_PERMISSION)) { 4067 for (int i = 0; i < siblings.size(); i++) { 4068 4069 // get siblings filename and locale 4070 String filename = siblings.get(i).getRootPath(); 4071 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 4072 4073 if (!content.hasLocale(locale)) { 4074 // only remove property if the locale fits 4075 continue; 4076 } 4077 if (content.hasValue(path, locale)) { 4078 // value is available, property must be kept 4079 continue; 4080 } 4081 // remove all existing permissions from the file 4082 List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false); 4083 for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) { 4084 CmsAccessControlEntry ace = j.next(); 4085 if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) { 4086 // remove the entry "All others", which has to be treated in a special way 4087 rootCms.rmacc( 4088 filename, 4089 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME, 4090 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString()); 4091 } else { 4092 // this is a group or user principal 4093 I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal()); 4094 if (principal.isGroup()) { 4095 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName()); 4096 } else if (principal.isUser()) { 4097 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName()); 4098 } 4099 } 4100 } 4101 } 4102 } 4103 } 4104 } 4105 } 4106 4107 /** 4108 * Resolves those mappings for which no content value exists and useDefault is set to true.<p> 4109 * 4110 * @param cms the CMS context to use 4111 * @param file the content file 4112 * @param content the content object 4113 * 4114 * @throws CmsException if something goes wrong 4115 */ 4116 protected void resolveDefaultMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 4117 4118 for (Map.Entry<String, List<String>> e : m_elementMappings.entrySet()) { 4119 String path = e.getKey(); 4120 List<String> mappings = e.getValue(); 4121 if (mappings == null) { 4122 // nothing to do if we have no mappings at all 4123 continue; 4124 } 4125 for (int v = mappings.size() - 1; v >= 0; v--) { 4126 String mapping = mappings.get(v); 4127 if (!isMappingUsingDefault(path, mapping)) { 4128 continue; 4129 } 4130 for (Locale locale : content.getLocales()) { 4131 if (content.hasValue(path, locale)) { 4132 continue; 4133 } else { 4134 String defaultValue = getDefault(cms, file, null, path, locale); 4135 if (defaultValue != null) { 4136 resolveMapping(cms, content, path, true, 0, locale, defaultValue); 4137 } 4138 } 4139 } 4140 4141 } 4142 } 4143 } 4144 4145 /** 4146 * Validates if the given <code>appinfo</code> element node from the XML content definition schema 4147 * is valid according the the capabilities of this content handler.<p> 4148 * 4149 * @param appinfoElement the <code>appinfo</code> element node to validate 4150 * 4151 * @throws CmsXmlException in case the element validation fails 4152 */ 4153 protected void validateAppinfoElement(Element appinfoElement) throws CmsXmlException { 4154 4155 // create a document to validate 4156 Document doc = DocumentHelper.createDocument(); 4157 Element root = doc.addElement(APPINFO_APPINFO); 4158 // attach the default appinfo schema 4159 root.add(I_CmsXmlSchemaType.XSI_NAMESPACE); 4160 root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, APPINFO_SCHEMA_SYSTEM_ID); 4161 // append the content from the appinfo node in the content definition 4162 root.appendContent(appinfoElement); 4163 // now validate the document with the default appinfo schema 4164 CmsXmlUtils.validateXmlStructure(doc, CmsEncoder.ENCODING_UTF_8, new CmsXmlEntityResolver(null)); 4165 } 4166 4167 /** 4168 * The errorHandler parameter is optional, if <code>null</code> is given a new error handler 4169 * instance must be created.<p> 4170 * 4171 * @param cms the current OpenCms user context 4172 * @param value the value to resolve the validation rules for 4173 * @param errorHandler (optional) an error handler instance that contains previous error or warnings 4174 * 4175 * @return an error handler that contains all errors and warnings currently found 4176 */ 4177 protected CmsXmlContentErrorHandler validateCategories( 4178 CmsObject cms, 4179 I_CmsXmlContentValue value, 4180 CmsXmlContentErrorHandler errorHandler) { 4181 4182 if (!value.isSimpleType()) { 4183 // do not validate complex types 4184 return errorHandler; 4185 } 4186 I_CmsWidget widget = null; 4187 4188 widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget(); 4189 if (!(widget instanceof CmsCategoryWidget)) { 4190 // do not validate widget that are not category widgets 4191 return errorHandler; 4192 } 4193 String stringValue = value.getStringValue(cms); 4194 if (stringValue.isEmpty()) { 4195 return errorHandler; 4196 } 4197 try { 4198 String[] values = stringValue.split(","); 4199 for (int i = 0; i < values.length; i++) { 4200 String val = values[i]; 4201 String catPath = CmsCategoryService.getInstance().getCategory(cms, val).getPath(); 4202 String refPath = getReferencePath(cms, value); 4203 CmsCategoryService.getInstance().readCategory(cms, catPath, refPath); 4204 if (((CmsCategoryWidget)widget).isOnlyLeafs()) { 4205 if (!CmsCategoryService.getInstance().readCategories(cms, catPath, false, refPath).isEmpty()) { 4206 errorHandler.addError( 4207 value, 4208 Messages.get().getBundle(value.getLocale()).key( 4209 Messages.GUI_CATEGORY_CHECK_NOLEAF_ERROR_0)); 4210 } 4211 } 4212 } 4213 } catch (CmsDataAccessException e) { 4214 // expected error in case of empty/invalid value 4215 // see CmsCategory#getCategoryPath(String, String) 4216 if (LOG.isDebugEnabled()) { 4217 LOG.debug(e.getLocalizedMessage(), e); 4218 } 4219 errorHandler.addError( 4220 value, 4221 Messages.get().getBundle(value.getLocale()).key(Messages.GUI_CATEGORY_CHECK_EMPTY_ERROR_0)); 4222 } catch (CmsException e) { 4223 // unexpected error 4224 if (LOG.isErrorEnabled()) { 4225 LOG.error(e.getLocalizedMessage(), e); 4226 } 4227 errorHandler.addError(value, e.getLocalizedMessage()); 4228 } 4229 return errorHandler; 4230 } 4231 4232 /** 4233 * Validates the given rules against the given value.<p> 4234 * 4235 * @param cms the current users OpenCms context 4236 * @param value the value to validate 4237 * @param errorHandler the error handler to use in case errors or warnings are detected 4238 * 4239 * @return if a broken link has been found 4240 */ 4241 protected boolean validateLink(CmsObject cms, I_CmsXmlContentValue value, CmsXmlContentErrorHandler errorHandler) { 4242 4243 // if there is a value of type file reference 4244 if ((value == null) || (!(value instanceof CmsXmlVfsFileValue) && !(value instanceof CmsXmlVarLinkValue))) { 4245 return false; 4246 } 4247 // if the value has a link (this will automatically fix, for instance, the path of moved resources) 4248 CmsLink link = null; 4249 if (value instanceof CmsXmlVfsFileValue) { 4250 link = ((CmsXmlVfsFileValue)value).getLink(cms); 4251 } else if (value instanceof CmsXmlVarLinkValue) { 4252 link = ((CmsXmlVarLinkValue)value).getLink(cms); 4253 } 4254 if ((link == null) || !link.isInternal()) { 4255 return false; 4256 } 4257 try { 4258 String sitePath = cms.getRequestContext().removeSiteRoot(link.getTarget()); 4259 4260 // check for links to static resources 4261 if (CmsStaticResourceHandler.isStaticResourceUri(sitePath)) { 4262 return false; 4263 } 4264 // validate the link for error 4265 CmsResource res = null; 4266 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(link.getTarget()); 4267 // the link target may be a root path for a resource in another site 4268 if (site != null) { 4269 CmsObject rootCms = OpenCms.initCmsObject(cms); 4270 rootCms.getRequestContext().setSiteRoot(""); 4271 res = rootCms.readResource(link.getTarget(), CmsResourceFilter.IGNORE_EXPIRATION); 4272 } else { 4273 res = cms.readResource(sitePath, CmsResourceFilter.IGNORE_EXPIRATION); 4274 } 4275 // check the time range 4276 if (res != null) { 4277 long time = System.currentTimeMillis(); 4278 if (!res.isReleased(time)) { 4279 if (errorHandler != null) { 4280 // generate warning message 4281 errorHandler.addWarning( 4282 value, 4283 Messages.get().getBundle(value.getLocale()).key( 4284 Messages.GUI_XMLCONTENT_CHECK_WARNING_NOT_RELEASED_0)); 4285 } 4286 return true; 4287 } else if (res.isExpired(time)) { 4288 if (errorHandler != null) { 4289 // generate warning message 4290 errorHandler.addWarning( 4291 value, 4292 Messages.get().getBundle(value.getLocale()).key( 4293 Messages.GUI_XMLCONTENT_CHECK_WARNING_EXPIRED_0)); 4294 } 4295 return true; 4296 } 4297 } 4298 } catch (CmsException e) { 4299 if (errorHandler != null) { 4300 // generate error message 4301 errorHandler.addError( 4302 value, 4303 Messages.get().getBundle(value.getLocale()).key(Messages.GUI_XMLCONTENT_CHECK_ERROR_0)); 4304 } 4305 return true; 4306 } 4307 return false; 4308 } 4309 4310 /** 4311 * Validates the given rules against the given value.<p> 4312 * 4313 * @param cms the current users OpenCms context 4314 * @param value the value to validate 4315 * @param errorHandler the error handler to use in case errors or warnings are detected 4316 * @param rules the rules to validate the value against 4317 * @param isWarning if true, this validation should be stored as a warning, otherwise as an error 4318 * 4319 * @return the updated error handler 4320 */ 4321 protected CmsXmlContentErrorHandler validateValue( 4322 CmsObject cms, 4323 I_CmsXmlContentValue value, 4324 CmsXmlContentErrorHandler errorHandler, 4325 Map<String, String> rules, 4326 boolean isWarning) { 4327 4328 if (validateLink(cms, value, errorHandler)) { 4329 return errorHandler; 4330 } 4331 4332 if (CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget() instanceof CmsDisplayWidget) { 4333 // display widgets should not be validated 4334 return errorHandler; 4335 } 4336 4337 String valueStr; 4338 try { 4339 valueStr = value.getStringValue(cms); 4340 } catch (Exception e) { 4341 // if the value can not be accessed it's useless to continue 4342 errorHandler.addError(value, e.getMessage()); 4343 return errorHandler; 4344 } 4345 4346 String regex = rules.get(value.getName()); 4347 if (regex == null) { 4348 // no customized rule, check default XML schema validation rules 4349 return validateValue(cms, value, valueStr, errorHandler, isWarning); 4350 } 4351 4352 boolean matchSign = true; 4353 if (regex.charAt(0) == '!') { 4354 // negate the pattern 4355 matchSign = false; 4356 regex = regex.substring(1); 4357 } 4358 4359 String stringToBeMatched = valueStr; 4360 if (stringToBeMatched == null) { 4361 // set match value to empty String to avoid exceptions in pattern matcher 4362 stringToBeMatched = ""; 4363 } 4364 4365 // use the custom validation pattern 4366 final boolean matches; 4367 try { 4368 matches = Pattern.matches(regex, stringToBeMatched); 4369 } catch (PatternSyntaxException | StackOverflowError e) { 4370 final String localizedMessage = (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : ""); 4371 final String ticket = String.valueOf(System.currentTimeMillis()); 4372 4373 Throwable trace = e; 4374 if (e instanceof StackOverflowError) { 4375 final String stackOverflowInfoMessage = "StackOverflowError thrown on pattern matching during xml" 4376 + " content validation. (Cause will be also logged in DEBUG level.)\n" 4377 + "Note 1.- Possible cause: The Java regex engine uses recursive method calls to implement" 4378 + " backtracking. When a repetition inside a regular expression contains multiple paths" 4379 + " (i.e. the body of the repetition contains an alternation (|), an optional element or another" 4380 + " repetition), trying to match the regular expression can cause a stack overflow on large inputs." 4381 + " This does not happen when using a possessive quantifier (such as *+ instead of *) or when using" 4382 + " a character class inside a repetition (e.g. [ab]* instead of (a|b)*).\n" 4383 + "Note 2.- On StackOverflowError, the size of the stacktraces could be limited by the JVM " 4384 + " and we could be missing information to identify the origin of the problem. To help in this" 4385 + " case, we create a new exception close to this origin. Alternatively, you can increase" 4386 + " the depth of the stack trace (for instance, '-XX:MaxJavaStackTraceDepth=1000000') to" 4387 + " identify it"; 4388 trace = LOG.isDebugEnabled() 4389 ? new Exception(stackOverflowInfoMessage, e) 4390 : new Exception(stackOverflowInfoMessage); 4391 errorHandler.addError( 4392 value, 4393 Messages.get().getBundle(value.getLocale()).key( 4394 Messages.GUI_EDITOR_XMLCONTENT_CANNOT_VALIDATE_ERROR_3, 4395 ticket, 4396 regex, 4397 stringToBeMatched)); 4398 } else { 4399 errorHandler.addError( 4400 value, 4401 Messages.get().getBundle(value.getLocale()).key( 4402 Messages.GUI_EDITOR_XMLCONTENT_INVALID_RULE_3, 4403 ticket, 4404 regex, 4405 localizedMessage)); 4406 } 4407 4408 LOG.warn( 4409 "Ticket " 4410 + ticket 4411 + " - " 4412 + localizedMessage 4413 + "\n" 4414 + " Regex='" 4415 + (matchSign ? "" : "!") 4416 + regex 4417 + "'\n" 4418 + " Path='" 4419 + value.getPath() 4420 + "'\n" 4421 + " Input='" 4422 + stringToBeMatched 4423 + "'", 4424 trace); 4425 4426 return errorHandler; 4427 } 4428 if (matchSign != matches) { 4429 // generate the message 4430 String message = getValidationMessage(cms, value, regex, valueStr, matchSign, isWarning); 4431 if (isWarning) { 4432 errorHandler.addWarning(value, message); 4433 } else { 4434 errorHandler.addError(value, message); 4435 // if an error was found, the default XML schema validation is not applied 4436 return errorHandler; 4437 } 4438 } 4439 4440 // no error found, check default XML schema validation rules 4441 return validateValue(cms, value, valueStr, errorHandler, isWarning); 4442 } 4443 4444 /** 4445 * Checks the default XML schema validation rules.<p> 4446 * 4447 * These rules should only be tested if this is not a test for warnings.<p> 4448 * 4449 * @param cms the current users OpenCms context 4450 * @param value the value to validate 4451 * @param valueStr the string value of the given value 4452 * @param errorHandler the error handler to use in case errors or warnings are detected 4453 * @param isWarning if true, this validation should be stored as a warning, otherwise as an error 4454 * 4455 * @return the updated error handler 4456 */ 4457 protected CmsXmlContentErrorHandler validateValue( 4458 CmsObject cms, 4459 I_CmsXmlContentValue value, 4460 String valueStr, 4461 CmsXmlContentErrorHandler errorHandler, 4462 boolean isWarning) { 4463 4464 if (isWarning) { 4465 // default schema validation only applies to errors 4466 return errorHandler; 4467 } 4468 4469 String message = null; 4470 if (value instanceof I_CmsXmlValidateWithMessage) { 4471 CmsMessageContainer messageContainer = ((I_CmsXmlValidateWithMessage)value).validateWithMessage(valueStr); 4472 if (null != messageContainer) { 4473 message = messageContainer.key(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)); 4474 } 4475 } else { 4476 if (!value.validateValue(valueStr)) { 4477 // value is not valid, add an error to the handler 4478 message = getValidationMessage(cms, value, value.getTypeName(), valueStr, true, false); 4479 } 4480 } 4481 if (null != message) { 4482 errorHandler.addError(value, message); 4483 } 4484 4485 return errorHandler; 4486 } 4487 4488 /** 4489 * Writes the categories if a category widget is present.<p> 4490 * 4491 * @param cms the cms context 4492 * @param file the file 4493 * @param content the xml content to set the categories for 4494 * 4495 * @return the perhaps modified file 4496 * 4497 * @throws CmsException if something goes wrong 4498 */ 4499 protected CmsFile writeCategories(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException { 4500 4501 if (CmsWorkplace.isTemporaryFile(file)) { 4502 // ignore temporary file if the original file exists (not the case for direct edit: "new") 4503 if (CmsResource.isTemporaryFileName(file.getRootPath())) { 4504 String originalFileName = CmsResource.getFolderPath(file.getRootPath()) 4505 + CmsResource.getName(file.getRootPath()).substring(CmsResource.TEMP_FILE_PREFIX.length()); 4506 if (cms.existsResource(cms.getRequestContext().removeSiteRoot(originalFileName))) { 4507 // original file exists, ignore it 4508 return file; 4509 } 4510 } else { 4511 // file name does not start with temporary prefix, ignore the file 4512 return file; 4513 } 4514 } 4515 // check the presence of a category widget 4516 boolean hasCategoryWidget = hasCategoryWidget(); 4517 if (!hasCategoryWidget) { 4518 // nothing to do if no category widget is present 4519 return file; 4520 } 4521 boolean modified = false; 4522 // clone the cms object, and use the root site 4523 CmsObject tmpCms = OpenCms.initCmsObject(cms); 4524 tmpCms.getRequestContext().setSiteRoot(""); 4525 // read all siblings 4526 try { 4527 List<CmsResource> listsib = tmpCms.readSiblings(file.getRootPath(), CmsResourceFilter.ALL); 4528 for (int i = 0; i < listsib.size(); i++) { 4529 CmsResource resource = listsib.get(i); 4530 // get the default locale of the sibling 4531 List<Locale> locales = getLocalesForResource(tmpCms, resource.getRootPath()); 4532 Locale locale = locales.get(0); 4533 for (Locale l : locales) { 4534 if (content.hasLocale(l)) { 4535 locale = l; 4536 break; 4537 } 4538 } 4539 // remove all previously set categories 4540 boolean clearedCategories = false; 4541 // iterate over all values checking for the category widget 4542 CmsXmlContentWidgetVisitor widgetCollector = new CmsXmlContentWidgetVisitor(cms, locale); 4543 content.visitAllValuesWith(widgetCollector); 4544 Iterator<Map.Entry<String, I_CmsXmlContentValue>> itWidgets = widgetCollector.getValues().entrySet().iterator(); 4545 while (itWidgets.hasNext()) { 4546 Map.Entry<String, I_CmsXmlContentValue> entry = itWidgets.next(); 4547 String xpath = entry.getKey(); 4548 I_CmsWidget widget = widgetCollector.getWidgets().get(xpath); 4549 I_CmsXmlContentValue value = entry.getValue(); 4550 if (!(widget instanceof CmsCategoryWidget) 4551 || value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 4552 // ignore other values than categories 4553 continue; 4554 } 4555 if (!clearedCategories) { 4556 CmsCategoryService.getInstance().clearCategoriesForResource(tmpCms, resource.getRootPath()); 4557 clearedCategories = true; 4558 } 4559 String stringValue = value.getStringValue(tmpCms); 4560 if (CmsStringUtil.isEmptyOrWhitespaceOnly(stringValue)) { 4561 // skip empty values 4562 continue; 4563 } 4564 try { 4565 // add the file to the selected category 4566 String[] catRootPathes = stringValue.split(","); 4567 for (String catRootPath : catRootPathes) { 4568 CmsCategory cat = CmsCategoryService.getInstance().getCategory(tmpCms, catRootPath); 4569 CmsCategoryService.getInstance().addResourceToCategory( 4570 tmpCms, 4571 resource.getRootPath(), 4572 cat.getPath()); 4573 } 4574 } catch (CmsVfsResourceNotFoundException e) { 4575 // invalid category 4576 try { 4577 // try to remove invalid value 4578 content.removeValue(value.getName(), value.getLocale(), value.getIndex()); 4579 modified = true; 4580 } catch (CmsRuntimeException ex) { 4581 // in case minoccurs prevents removing the invalid value 4582 if (LOG.isDebugEnabled()) { 4583 LOG.debug(ex.getLocalizedMessage(), ex); 4584 } 4585 } 4586 } 4587 } 4588 } 4589 } catch (CmsException ex) { 4590 if (LOG.isErrorEnabled()) { 4591 LOG.error(ex.getLocalizedMessage(), ex); 4592 } 4593 } 4594 if (modified) { 4595 // when an invalid category has been removed 4596 file = content.correctXmlStructure(cms); 4597 content.setFile(file); 4598 } 4599 return file; 4600 } 4601 4602 /** 4603 * Helper method to combine synchronizations from a content definition and its nested content definitions. 4604 * 4605 * @param contentDefinition the content definition to start with 4606 * @param path the the path to this content definition 4607 * @param combinedSynchronizations the map in which the combined synchronizations should be stored 4608 */ 4609 private void combineSynchronizations( 4610 CmsXmlContentDefinition contentDefinition, 4611 String path, 4612 LinkedHashMap<String, SynchronizationMode> combinedSynchronizations) { 4613 4614 // put the synchronization definitions from nested contents in the map before the definitions from the current content definition, 4615 // so the latter can override the former 4616 4617 for (String name : contentDefinition.getSchemaTypes()) { 4618 I_CmsXmlSchemaType type = contentDefinition.getSchemaType(name); 4619 if (type instanceof CmsXmlNestedContentDefinition) { 4620 CmsXmlContentDefinition nestedDef = ((CmsXmlNestedContentDefinition)type).getNestedContentDefinition(); 4621 String subPath = "".equals(path) ? name : path + "/" + name; 4622 combineSynchronizations(nestedDef, subPath, combinedSynchronizations); 4623 } 4624 } 4625 CmsSynchronizationSpec synchs = contentDefinition.getContentHandler().getSynchronizations(false); 4626 for (Map.Entry<String, SynchronizationMode> entry : synchs.asMap().entrySet()) { 4627 String subPath = "".equals(path) ? entry.getKey() : path + "/" + entry.getKey(); 4628 combinedSynchronizations.put(subPath, entry.getValue()); 4629 } 4630 } 4631 4632 /** 4633 * Creates a search field mapping for the given mapping element and the locale.<p> 4634 * 4635 * @param contentDefinition the content definition 4636 * @param element the mapping element configured in the schema 4637 * @param locale the locale 4638 * 4639 * @return the created search field mapping 4640 * 4641 * @throws CmsXmlException if the dynamic field class could not be found 4642 */ 4643 private I_CmsSearchFieldMapping createSearchFieldMapping( 4644 CmsXmlContentDefinition contentDefinition, 4645 Element element, 4646 Locale locale, 4647 String defaultParamValue) 4648 throws CmsXmlException { 4649 4650 I_CmsSearchFieldMapping fieldMapping = null; 4651 String typeAsString = element.attributeValue(APPINFO_ATTR_TYPE); 4652 CmsSearchFieldMappingType type = CmsSearchFieldMappingType.valueOf(typeAsString); 4653 if (type == null) { 4654 throw new CmsXmlException( 4655 Messages.get().container( 4656 Messages.ERR_XML_SCHEMA_MAPPING_TYPE_NOT_EXIST_3, 4657 typeAsString, 4658 contentDefinition.getTypeName(), 4659 contentDefinition.getSchemaLocation())); 4660 } 4661 String mappingClass = element.attributeValue(APPINFO_ATTR_CLASS); 4662 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mappingClass)) { 4663 try { 4664 fieldMapping = (I_CmsSearchFieldMapping)Class.forName(mappingClass).newInstance(); 4665 } catch (Exception e) { 4666 throw new CmsXmlException( 4667 Messages.get().container( 4668 Messages.ERR_XML_SCHEMA_MAPPING_CLASS_NOT_EXIST_3, 4669 mappingClass, 4670 contentDefinition.getTypeName(), 4671 contentDefinition.getSchemaLocation())); 4672 } 4673 } else { 4674 fieldMapping = new CmsSearchFieldMapping(); 4675 } 4676 fieldMapping.setType(type); 4677 String paramValue = element.getStringValue(); 4678 if ((paramValue == null) || paramValue.isEmpty()) { 4679 paramValue = defaultParamValue; 4680 } 4681 fieldMapping.setParam(paramValue); 4682 fieldMapping.setLocale(locale); 4683 fieldMapping.setDefaultValue(element.attributeValue(APPINFO_ATTR_DEFAULT)); 4684 return fieldMapping; 4685 } 4686 4687 /** 4688 * Gets the xpath mapped to a given target, if the mapping exists, and null otherwise. 4689 * 4690 * @param target the mapping target 4691 * @return the xpath mapped to the target 4692 */ 4693 private String getMappingSource(String target) { 4694 4695 for (Map.Entry<String, List<String>> entry : m_elementMappings.entrySet()) { 4696 if (entry.getValue().contains(target)) { 4697 return entry.getKey(); 4698 } 4699 } 4700 return null; 4701 } 4702 4703 /** 4704 * Utility method to return a path fragment.<p> 4705 * 4706 * @param pathElements the path elements 4707 * @param begin the begin index 4708 * 4709 * @return the path 4710 */ 4711 private String getSubPath(String[] pathElements, int begin) { 4712 4713 String result = ""; 4714 for (int i = begin; i < pathElements.length; i++) { 4715 result += pathElements[i] + "/"; 4716 } 4717 if (result.length() > 0) { 4718 result = result.substring(0, result.length() - 1); 4719 } 4720 return result; 4721 } 4722 4723 /** 4724 * Checks if any configured value type is an OpenCmsCategory. 4725 * 4726 * @return true if any configured value type is an OpenCmsCategory 4727 */ 4728 private boolean hasCategoryType() { 4729 4730 try { 4731 for (I_CmsXmlSchemaType typeEntry : m_contentDefinition.getTypeSequence()) { 4732 String typeName = typeEntry.getTypeName(); 4733 I_CmsXmlSchemaType type = OpenCms.getXmlContentTypeManager().getContentType(typeName); 4734 if (type instanceof CmsXmlCategoryValue) { 4735 return true; 4736 } 4737 } 4738 } catch (Exception e) { 4739 LOG.debug(e.getLocalizedMessage(), e); 4740 } 4741 return false; 4742 4743 } 4744 4745 /** 4746 * Checks whether a category widget is configured. 4747 * 4748 * @return true if a category widget is configured 4749 */ 4750 private boolean hasCategoryWidget() { 4751 4752 if (m_hasCategoryWidget == null) { 4753 boolean result = false; 4754 for (Map.Entry<String, String> widgetEntry : m_widgetNames.entrySet()) { 4755 String widgetName = widgetEntry.getValue(); 4756 I_CmsWidget widget = OpenCms.getXmlContentTypeManager().getWidget(widgetName); 4757 if ((widget != null) && (widget instanceof CmsCategoryWidget)) { 4758 result = true; 4759 break; 4760 } 4761 } 4762 result = result || hasCategoryType(); 4763 m_hasCategoryWidget = Boolean.valueOf(result); 4764 return result; 4765 } 4766 return m_hasCategoryWidget.booleanValue(); 4767 4768 } 4769 4770 /** 4771 * Initializes the geo-mapping configuration. 4772 * 4773 * @param element the configuration node 4774 */ 4775 private void initGeoMappingEntries(Element element) { 4776 4777 try { 4778 for (Element child : element.elements()) { 4779 EntryType type = EntryType.valueOf(child.getName()); 4780 String value = child.getText(); 4781 Entry entry = new Entry(type, value.trim()); 4782 m_geomappingEntries.add(entry); 4783 } 4784 } catch (Exception e) { 4785 LOG.error(e.getLocalizedMessage(), e); 4786 } 4787 } 4788 4789 /** 4790 * Initializes the message key fall back handler.<p> 4791 * 4792 * @param element the XML element node 4793 */ 4794 private void initMessageKeyHandler(Element element) { 4795 4796 String className = element.attributeValue(APPINFO_ATTR_CLASS); 4797 String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION); 4798 try { 4799 Object messageKeyHandler = Class.forName(className).getConstructor(String.class).newInstance(configuration); 4800 m_messageKeyHandler = (CmsMultiMessages.I_KeyFallbackHandler)messageKeyHandler; 4801 } catch (Exception e) { 4802 LOG.error(e.getLocalizedMessage(), e); 4803 } 4804 } 4805 4806 /** 4807 * Checks if the given mapping has the 'useDefault' flag set to true.<p> 4808 * 4809 * @param path the mapping path 4810 * @param mapping the mapping type 4811 * 4812 * @return true if 'useDefault' is enabled for this mapping 4813 */ 4814 private boolean isMappingUsingDefault(String path, String mapping) { 4815 4816 String key = path + ":" + mapping; 4817 return m_mappingsUsingDefault.contains(key); 4818 } 4819 4820 /** 4821 * Helper method which does most of the mapping resolution work.<p> 4822 * 4823 * @param cms the CMS context to use 4824 * @param content the content object 4825 * @param valuePath the xpath of the value 4826 * @param valueIsSimple true if this is a simple value 4827 * @param valueIndex the index of the value 4828 * @param valueLocale the locale of the value 4829 * @param originalStringValue the value as a string 4830 * 4831 * @throws CmsException if something goes wrong 4832 */ 4833 private void resolveMapping( 4834 CmsObject cms, 4835 CmsXmlContent content, 4836 String valuePath, 4837 boolean valueIsSimple, 4838 int valueIndex, 4839 Locale valueLocale, 4840 String originalStringValue) 4841 throws CmsException { 4842 4843 CmsObject rootCms = createRootCms(cms); 4844 // get the original VFS file from the content 4845 CmsFile file = content.getFile(); 4846 if (!valueIsSimple) { 4847 // no mappings for a nested schema are possible 4848 // note that the sub-elements of the nested schema ARE mapped by the node visitor, 4849 // it's just the nested schema value itself that does not support mapping 4850 return; 4851 } 4852 4853 List<String> mappings = getMappings(valuePath); 4854 if (mappings.size() == 0) { 4855 // nothing to do if we have no mappings at all 4856 return; 4857 } 4858 // create OpenCms user context initialized with "/" as site root to read all siblings 4859 // read all siblings of the file 4860 List<CmsResource> siblings = rootCms.readSiblings( 4861 content.getFile().getRootPath(), 4862 CmsResourceFilter.IGNORE_EXPIRATION); 4863 4864 Set<CmsResource> urlNameMappingResources = new HashSet<CmsResource>(); 4865 boolean mapToUrlName = false; 4866 urlNameMappingResources.add(content.getFile()); 4867 // since 7.0.2 multiple mappings are possible 4868 4869 // get the string value of the current node 4870 4871 CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(rootCms, content, valueLocale); 4872 resolver.setKeepEmptyMacros(true); 4873 String stringValue = resolver.resolveMacros(originalStringValue); 4874 CmsMappingResolutionContext mappingContext = (CmsMappingResolutionContext)(cms.getRequestContext().getAttribute( 4875 ATTR_MAPPING_RESOLUTION_CONTEXT)); 4876 4877 for (String mapping : mappings) { 4878 4879 if (CmsStringUtil.isNotEmpty(mapping)) { 4880 4881 // attribute mapping now does its own handling of siblings/locales in CmsMappingResolutionContext, 4882 // so we just save the mapped release/expiration dates for later, and we do this before the sibling/locale handling 4883 // logic in this method. 4884 if (mapping.startsWith(MAPTO_ATTRIBUTE)) { 4885 4886 // this is an attribute mapping 4887 String attribute = mapping.substring(MAPTO_ATTRIBUTE.length()); 4888 switch (ATTRIBUTES.indexOf(attribute)) { 4889 case 0: // date released 4890 long date = 0; 4891 try { 4892 date = Long.valueOf(stringValue).longValue(); 4893 } catch (NumberFormatException e) { 4894 // ignore, value can be a macro 4895 } 4896 if (date == 0) { 4897 date = CmsResource.DATE_RELEASED_DEFAULT; 4898 } 4899 mappingContext.putReleaseDate(valueLocale, date); 4900 break; 4901 case 1: // date expired 4902 date = 0; 4903 try { 4904 date = Long.valueOf(stringValue).longValue(); 4905 } catch (NumberFormatException e) { 4906 // ignore, value can be a macro 4907 } 4908 if (date == 0) { 4909 date = CmsResource.DATE_EXPIRED_DEFAULT; 4910 } 4911 mappingContext.putExpirationDate(valueLocale, date); 4912 break; 4913 default: 4914 // ignore invalid / other mappings 4915 } 4916 continue; // skip to next mapping 4917 } 4918 4919 // for multiple language mappings, we need to ensure 4920 // a) all siblings are handled 4921 // b) only the "right" locale is mapped to a sibling 4922 for (int i = (siblings.size() - 1); i >= 0; i--) { 4923 // get filename 4924 String filename = (siblings.get(i)).getRootPath(); 4925 if (mapping.startsWith(MAPTO_URLNAME)) { 4926 // should be written regardless of whether there is a sibling with the correct locale 4927 mapToUrlName = true; 4928 } 4929 4930 Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); 4931 if (!locale.equals(valueLocale)) { 4932 // only map property if the locale fits 4933 continue; 4934 } 4935 4936 // make sure the file is locked 4937 CmsLock lock = rootCms.getLock(filename); 4938 if (lock.isUnlocked()) { 4939 rootCms.lockResource(filename); 4940 } else if (!lock.isDirectlyOwnedInProjectBy(rootCms)) { 4941 rootCms.changeLock(filename); 4942 } 4943 4944 if (mapping.startsWith(MAPTO_PERMISSION) && (valueIndex == 0)) { 4945 4946 // map value to a permission 4947 // example of a mapping: mapto="permission:GROUP:+r+v|GROUP.ALL_OTHERS:|GROUP.Projectmanagers:+r+v+w+c" 4948 4949 // get permission(s) to set 4950 String permissionMappings = mapping.substring(MAPTO_PERMISSION.length()); 4951 String mainMapping = permissionMappings; 4952 Map<String, String> permissionsToSet = new HashMap<String, String>(); 4953 4954 // separate permission to set for element value from other permissions to set 4955 int sepIndex = permissionMappings.indexOf('|'); 4956 if (sepIndex != -1) { 4957 mainMapping = permissionMappings.substring(0, sepIndex); 4958 permissionMappings = permissionMappings.substring(sepIndex + 1); 4959 permissionsToSet = CmsStringUtil.splitAsMap(permissionMappings, "|", ":"); 4960 } 4961 4962 // determine principal type and permission string to set 4963 String principalType = I_CmsPrincipal.PRINCIPAL_GROUP; 4964 String permissionString = mainMapping; 4965 sepIndex = mainMapping.indexOf(':'); 4966 if (sepIndex != -1) { 4967 principalType = mainMapping.substring(0, sepIndex); 4968 permissionString = mainMapping.substring(sepIndex + 1); 4969 } 4970 if (permissionString.toLowerCase().indexOf('o') == -1) { 4971 permissionString += "+o"; 4972 } 4973 4974 // remove all existing permissions from the file 4975 List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false); 4976 for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) { 4977 CmsAccessControlEntry ace = j.next(); 4978 if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) { 4979 // remove the entry "All others", which has to be treated in a special way 4980 rootCms.rmacc( 4981 filename, 4982 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME, 4983 CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString()); 4984 } else { 4985 // this is a group or user principal 4986 I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal()); 4987 if (principal.isGroup()) { 4988 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName()); 4989 } else if (principal.isUser()) { 4990 rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName()); 4991 } 4992 } 4993 } 4994 4995 // set additional permissions that are defined in mapping 4996 for (Iterator<Map.Entry<String, String>> j = permissionsToSet.entrySet().iterator(); j.hasNext();) { 4997 Map.Entry<String, String> entry = j.next(); 4998 sepIndex = entry.getKey().indexOf('.'); 4999 if (sepIndex != -1) { 5000 String type = entry.getKey().substring(0, sepIndex); 5001 String name = entry.getKey().substring(sepIndex + 1); 5002 String permissions = entry.getValue(); 5003 if (permissions.toLowerCase().indexOf('o') == -1) { 5004 permissions += "+o"; 5005 } 5006 try { 5007 rootCms.chacc(filename, type, name, permissions); 5008 } catch (CmsException e) { 5009 // setting permission did not work 5010 LOG.error(e.getLocalizedMessage(), e); 5011 } 5012 } 5013 } 5014 5015 // set permission(s) using the element value(s) 5016 // the set with all selected principals 5017 TreeSet<String> allPrincipals = new TreeSet<String>(); 5018 String path = CmsXmlUtils.removeXpathIndex(valuePath); 5019 List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale); 5020 Iterator<I_CmsXmlContentValue> j = values.iterator(); 5021 while (j.hasNext()) { 5022 I_CmsXmlContentValue val = j.next(); 5023 String principalName = val.getStringValue(rootCms); 5024 // the prinicipal name can be a principal list 5025 List<String> principalNames = CmsStringUtil.splitAsList( 5026 principalName, 5027 PRINCIPAL_LIST_SEPARATOR); 5028 // iterate over the principals 5029 Iterator<String> iterPrincipals = principalNames.iterator(); 5030 while (iterPrincipals.hasNext()) { 5031 // get the next principal 5032 String principal = iterPrincipals.next(); 5033 allPrincipals.add(principal); 5034 } 5035 } 5036 // iterate over the set with all principals and set the permissions 5037 Iterator<String> iterAllPricinipals = allPrincipals.iterator(); 5038 while (iterAllPricinipals.hasNext()) { 5039 // get the next principal 5040 String principal = iterAllPricinipals.next(); 5041 rootCms.chacc(filename, principalType, principal, permissionString); 5042 } 5043 // special case: permissions are written only to one sibling, end loop 5044 i = 0; 5045 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST) && (valueIndex == 0)) { 5046 5047 boolean mapToShared; 5048 int prefixLength; 5049 // check which mapping is used (shared or individual) 5050 if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) { 5051 mapToShared = true; 5052 prefixLength = MAPTO_PROPERTY_LIST_SHARED.length(); 5053 } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) { 5054 mapToShared = false; 5055 prefixLength = MAPTO_PROPERTY_LIST_INDIVIDUAL.length(); 5056 } else { 5057 mapToShared = false; 5058 prefixLength = MAPTO_PROPERTY_LIST.length(); 5059 } 5060 5061 // this is a property list mapping 5062 String property = mapping.substring(prefixLength); 5063 5064 String path = CmsXmlUtils.removeXpathIndex(valuePath); 5065 List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale); 5066 Iterator<I_CmsXmlContentValue> j = values.iterator(); 5067 StringBuffer result = new StringBuffer(values.size() * 64); 5068 while (j.hasNext()) { 5069 I_CmsXmlContentValue val = j.next(); 5070 result.append(val.getStringValue(rootCms)); 5071 if (j.hasNext()) { 5072 result.append(CmsProperty.VALUE_LIST_DELIMITER); 5073 } 5074 } 5075 5076 CmsProperty p; 5077 if (mapToShared) { 5078 // map to shared value 5079 p = new CmsProperty(property, null, result.toString()); 5080 } else { 5081 // map to individual value 5082 p = new CmsProperty(property, result.toString(), null); 5083 } 5084 // write the created list string value in the selected property 5085 rootCms.writePropertyObject(filename, p); 5086 if (mapToShared) { 5087 // special case: shared mappings must be written only to one sibling, end loop 5088 i = 0; 5089 } 5090 5091 } else if (mapping.startsWith(MAPTO_PROPERTY)) { 5092 5093 boolean mapToShared; 5094 int prefixLength; 5095 // check which mapping is used (shared or individual) 5096 if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) { 5097 mapToShared = true; 5098 prefixLength = MAPTO_PROPERTY_SHARED.length(); 5099 } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) { 5100 mapToShared = false; 5101 prefixLength = MAPTO_PROPERTY_INDIVIDUAL.length(); 5102 } else { 5103 mapToShared = false; 5104 prefixLength = MAPTO_PROPERTY.length(); 5105 } 5106 5107 // this is a property mapping 5108 String property = mapping.substring(prefixLength); 5109 5110 CmsProperty p; 5111 if (mapToShared) { 5112 // map to shared value 5113 p = new CmsProperty(property, null, stringValue); 5114 } else { 5115 // map to individual value 5116 p = new CmsProperty(property, stringValue, null); 5117 } 5118 // just store the string value in the selected property 5119 rootCms.writePropertyObject(filename, p); 5120 if (mapToShared) { 5121 // special case: shared mappings must be written only to one sibling, end loop 5122 i = 0; 5123 } 5124 } else if (mapping.startsWith(MAPTO_URLNAME)) { 5125 // we write the actual mappings later 5126 urlNameMappingResources.add(siblings.get(i)); 5127 } 5128 } 5129 } 5130 } 5131 if (mapToUrlName) { 5132 for (CmsResource resourceForUrlNameMapping : urlNameMappingResources) { 5133 if (!CmsResource.isTemporaryFileName(resourceForUrlNameMapping.getRootPath())) { 5134 String mappedName = stringValue; 5135 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(mappedName)) { 5136 mappedName = mappedName.trim(); 5137 mappingContext.addUrlNameMapping( 5138 mappedName, 5139 valueLocale, 5140 resourceForUrlNameMapping.getStructureId()); 5141 } 5142 } 5143 } 5144 } 5145 5146 // make sure the original is locked 5147 CmsLock lock = rootCms.getLock(file); 5148 if (lock.isUnlocked()) { 5149 rootCms.lockResource(file.getRootPath()); 5150 } else if (!lock.isExclusiveOwnedBy(rootCms.getRequestContext().getCurrentUser())) { 5151 rootCms.changeLock(file.getRootPath()); 5152 } 5153 } 5154 5155 /** 5156 * Parses a boolean from a string and returns a default value if the string couldn't be parsed.<p> 5157 * 5158 * @param text the text from which to get the boolean value 5159 * @param defaultValue the value to return if parsing fails 5160 * 5161 * @return the parsed boolean 5162 */ 5163 private boolean safeParseBoolean(String text, boolean defaultValue) { 5164 5165 if (text == null) { 5166 return defaultValue; 5167 } 5168 try { 5169 return Boolean.parseBoolean(text); 5170 } catch (Throwable t) { 5171 return defaultValue; 5172 } 5173 } 5174 5175}