001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.configuration;
029
030import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReference;
031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta;
032import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet;
033import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
034import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementDeleteMode;
035import org.opencms.ade.detailpage.CmsDetailPageInfo;
036import org.opencms.ade.galleries.CmsAddContentRestriction;
037import org.opencms.file.CmsFile;
038import org.opencms.file.CmsObject;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.file.types.I_CmsResourceType;
042import org.opencms.gwt.CmsIconUtil;
043import org.opencms.i18n.CmsLocaleManager;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.CmsRuntimeException;
047import org.opencms.main.OpenCms;
048import org.opencms.module.CmsModule;
049import org.opencms.relations.CmsLink;
050import org.opencms.util.CmsFileUtil;
051import org.opencms.util.CmsStringUtil;
052import org.opencms.util.CmsUUID;
053import org.opencms.xml.containerpage.CmsFormatterBean;
054import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler;
055import org.opencms.xml.containerpage.I_CmsFormatterBean;
056import org.opencms.xml.content.CmsXmlContent;
057import org.opencms.xml.content.CmsXmlContentFactory;
058import org.opencms.xml.content.CmsXmlContentProperty;
059import org.opencms.xml.content.CmsXmlContentProperty.Visibility;
060import org.opencms.xml.content.CmsXmlContentRootLocation;
061import org.opencms.xml.content.I_CmsXmlContentLocation;
062import org.opencms.xml.content.I_CmsXmlContentValueLocation;
063import org.opencms.xml.types.CmsXmlVarLinkValue;
064import org.opencms.xml.types.CmsXmlVfsFileValue;
065
066import java.util.ArrayList;
067import java.util.HashSet;
068import java.util.LinkedHashMap;
069import java.util.LinkedHashSet;
070import java.util.List;
071import java.util.Locale;
072import java.util.Map;
073import java.util.Set;
074
075import org.apache.commons.logging.Log;
076
077import com.google.common.collect.Lists;
078
079/**
080 * A class to parse ADE sitemap or module configuration files and create configuration objects from them.<p>
081 */
082public class CmsConfigurationReader {
083
084    /**
085     * Enum describing how to deal with inherited properties.
086     */
087    enum DiscardPropertiesMode {
088
089        /** Remove properties from parent sitemaps. */
090        discard("true"),
091
092        /** Inherit properties from parent sitemaps. */
093        keep("false"),
094
095        /** Remove properties from parent sitemaps, and mark properties defined in this sitemap as 'top', which moves them over the properties which are only defined in opencms-workplace.xml/opencms-modules.xml. */
096        top("top");
097
098        /** The value representing the mode in an actual sitemap configuration content. */
099        private String m_stringValue;
100
101        /**
102         * Creates the enum value.
103         *
104         * @param stringValue the string value from the configuration
105         */
106        private DiscardPropertiesMode(String stringValue) {
107
108            m_stringValue = stringValue;
109
110        }
111
112        /**
113         * Gets the string value occurring in the  sitemap configuration.
114         *
115         * @return the string value
116         */
117        public String getStringValue() {
118
119            return m_stringValue;
120        }
121    }
122
123    /** The default locale for configuration objects. */
124    public static final Locale DEFAULT_LOCALE = CmsLocaleManager.getLocale("en");
125
126    /** The AddContentReplacements node name. */
127    public static final String N_ADD_CONTENT_RESTRICTION = "AddContentRestriction";
128
129    /** Node name for added formatters. */
130    public static final String N_ADD_FORMATTER = "AddFormatter";
131
132    /** Node name for the nested content with the added formatters. */
133    public static final String N_ADD_FORMATTERS = "AddFormatters";
134
135    /** The AddPlugin node name. */
136    public static final String N_ADD_PLUGIN = "AddPlugin";
137
138    /** The AddPlugins node name. */
139    public static final String N_ADD_PLUGINS = "AddPlugins";
140
141    /** The Attribute node name. */
142    public static final String N_ATTRIBUTE = "Attribute";
143
144    /** Node name for the attribute editor configuration reference. */
145    public static final String N_ATTRIBUTE_EDITOR_CONFIG = "AttributeEditorConfig";
146
147    /** Node name for the 'Check reuse' option. */
148    public static final String N_CHECK_REUSE = "CheckReuse";
149
150    public static final String N_CONTENT_FOLDER = "ContentFolder";
151
152    /** The CopyInModels node name. */
153    public static final String N_COPY_IN_MODELS = "CopyInModels";
154
155    /** The create content locally node name. */
156    public static final String N_CREATE_CONTENTS_LOCALLY = "CreateContentsLocally";
157
158    /** The default node name. */
159    public static final String N_DEFAULT = "Default";
160
161    /** The description node name. */
162    public static final String N_DESCRIPTION = "Description";
163
164    /** The detail page node name. */
165    public static final String N_DETAIL_PAGE = "DetailPage";
166
167    /** The detail pages disabled node name. */
168    public static final String N_DETAIL_PAGES_DISABLED = "DetailPagesDisabled";
169
170    /** The disabled node name. */
171    public static final String N_DISABLED = "Disabled";
172
173    /** The DisabledFunctionsMode node name. */
174    public static final String N_DISABLED_FUNCTIONS_MODE = "DisabledFunctionsMode";
175
176    /** The DisabledTypesMode node name. */
177    public static final String N_DISABLED_TYPES_MODE = "DisabledTypesMode";
178
179    /** The discard model pages node name. */
180    public static final String N_DISCARD_MODEL_PAGES = "DiscardModelPages";
181
182    /** The discard properties node name. */
183    public static final String N_DISCARD_PROPERTIES = "DiscardProperties";
184
185    /** The discard types node name. */
186    public static final String N_DISCARD_TYPES = "DiscardTypes";
187
188    /** The display name node name. */
189    public static final String N_DISPLAY_NAME = "DisplayName";
190
191    /** The element view node name. */
192    public static final String N_ELEMENT_VIEW = "ElementView";
193
194    /** The error node name. */
195    public static final String N_ERROR = "Error";
196
197    /** The 'exclude external detail contents' node name. */
198    public static final String N_EXCLUDE_EXTERNAL_DETAIL_CONTENTS = "ExcludeExternalDetailContents";
199
200    /** The folder node name. */
201    public static final String N_FOLDER = "Folder";
202
203    /** The formatter node name. */
204    public static final String N_FORMATTER = "Formatter";
205
206    /** The function node name. */
207    public static final String N_FUNCTION = "Function";
208
209    /** The function node name. */
210    public static final String N_FUNCTION_DEFAULT_PAGE = "FunctionDefaultPage";
211
212    /** The function reference node name. */
213    public static final String N_FUNCTION_REF = "FunctionRef";
214
215    /** The 'include in site selector' node name. */
216    public static final String N_INCLUDE_IN_SITE_SELECTOR = "IncludeInSiteSelector";
217
218    /** The IncludeName node name. */
219    public static final String N_INCLUDE_NAME = "IncludeName";
220
221    /** The is default node name. */
222    public static final String N_IS_DEFAULT = "IsDefault";
223
224    /** The is preview node name. */
225    public static final String N_IS_PREVIEW = "IsPreview";
226
227    /** The JSP node name. */
228    public static final String N_JSP = "Jsp";
229
230    /** The Key node name. */
231    public static final String N_KEY = "Key";
232
233    /** The localization node name. */
234    public static final String N_LOCALIZATION = "Localization";
235
236    /** The master configuration node name. */
237    public static final String N_MASTER_CONFIG = "MasterConfig";
238
239    /** The max width node name. */
240    public static final String N_MAX_WIDTH = "MaxWidth";
241
242    /** The min width node name. */
243    public static final String N_MIN_WIDTH = "MinWidth";
244
245    /** The model page node name. */
246    public static final String N_MODEL_PAGE = "ModelPage";
247
248    /** The folder name node name. */
249    public static final String N_NAME = "Name";
250
251    /** The name pattern node name. */
252    public static final String N_NAME_PATTERN = "NamePattern";
253
254    /** The order node name. */
255    public static final String N_ORDER = "Order";
256
257    /** The page node name. */
258    public static final String N_PAGE = "Page";
259
260    /** The PageRelative node name. */
261    public static final String N_PAGE_RELATIVE = "PageRelative";
262
263    /** The folder path node name. */
264    public static final String N_PATH = "Path";
265
266    /** Placeholder node name. */
267    public static final String N_PLACEHOLDER = "Placeholder";
268
269    /** The Plugin node name. */
270    public static final String N_PLUGIN = "Plugin";
271
272    /** The  PreferDetailPagesForLocalContents node name. */
273    public static final String N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS = "PreferDetailPagesForLocalContents";
274
275    /** The prefer folder node name. */
276    public static final String N_PREFER_FOLDER = "PreferFolder";
277
278    /** The property node name. */
279    public static final String N_PROPERTY = "Property";
280
281    /** The property name node name. */
282    public static final String N_PROPERTY_NAME = "PropertyName";
283
284    /** XML node name. */
285    public static final String N_PROPERTY_NAME_ALIAS = "PropertyNameAlias";
286
287    /** Node name for the "Remove all formatters"-option. */
288    public static final String N_REMOVE_ALL_FORMATTERS = "RemoveAllFormatters";
289
290    /** Field name for the 'Remove all functions' setting. */
291    public static final String N_REMOVE_ALL_FUNCTIONS = "RemoveAllFunctions";
292
293    /** The RemoveAllPlugins node name. */
294    public static final String N_REMOVE_ALL_PLUGINS = "RemoveAllPlugins";
295
296    /** The RemoveAllSharedSettingOverrides node name. */
297    public static final String N_REMOVE_ALL_SHARED_SETTING_OVERRIDES = "RemoveAllSharedSettingOverrides";
298
299    /** Node name for removed formatters. */
300    public static final String N_REMOVE_FORMATTER = "RemoveFormatter";
301
302    /** Node name for the nested content with the removed formatters. */
303    public static final String N_REMOVE_FORMATTERS = "RemoveFormatters";
304
305    /** The remove function node name. */
306    public static final String N_REMOVE_FUNCTIONS = "RemoveFunctions";
307
308    /** The RemovePlugin node name. */
309    public static final String N_REMOVE_PLUGIN = "RemovePlugin";
310
311    /** The RemovePlugins node name. */
312    public static final String N_REMOVE_PLUGINS = "RemovePlugins";
313
314    /** The resource type node name. */
315    public static final String N_RESOURCE_TYPE = "ResourceType";
316
317    /** The regex rule node name. */
318    public static final String N_RULE_REGEX = "RuleRegex";
319
320    /** The rule type node name. */
321    public static final String N_RULE_TYPE = "RuleType";
322
323    /** The SharedSettingOverride node name. */
324    public static final String N_SHARED_SETTING_OVERRIDE = "SharedSettingOverride";
325
326    /** The ShowInDefaultView node name. */
327    public static final String N_SHOW_IN_DEFAULT_VIEW = "ShowInDefaultView";
328
329    /** The type node name. */
330    public static final String N_TYPE = "Type";
331
332    /** The type name node name. */
333    public static final String N_TYPE_NAME = "TypeName";
334
335    /** The node name for the type ordering mode. */
336    public static final String N_TYPE_ORDERING_MODE = "TypeOrderingMode";
337
338    /** Node name. */
339    public static final String N_USE_FORMATTER_KEYS = "UseFormatterKeys";
340
341    /** The Value node name. */
342    public static final String N_VALUE = "Value";
343
344    /** XML node name. */
345    public static final String N_VALUE_TRANSLATION = "ValueTranslation";
346
347    /** The widget node name. */
348    public static final String N_VISIBILITY = "Visibility";
349
350    /** The widget node name. */
351    public static final String N_WIDGET = "Widget";
352
353    /** The widget configuration node name. */
354    public static final String N_WIDGET_CONFIG = "WidgetConfig";
355
356    /** Scheme for explorer type view links. */
357    public static final String VIEW_SCHEME = "view://";
358
359    /** The log object for this class. */
360    private static final Log LOG = CmsLog.getLog(CmsConfigurationReader.class);
361
362    /** The ElementDeleteMode node name. */
363    private static final String N_ELEMENT_DELETE_MODE = "ElementDeleteMode";
364
365    /** The CMS context used for reading the configuration data. */
366    private CmsObject m_cms;
367
368    /** The parsed detail page configuration elements. */
369    private List<CmsDetailPageInfo> m_detailPageConfigs = new ArrayList<CmsDetailPageInfo>();
370
371    /** The list of configured function references. */
372    private List<CmsFunctionReference> m_functionReferences = new ArrayList<CmsFunctionReference>();
373
374    /** The parsed model page configuration elements. */
375    private List<CmsModelPageConfigWithoutResource> m_modelPageConfigs = new ArrayList<CmsModelPageConfigWithoutResource>();
376
377    /** The parsed property configuration elements. */
378    private List<CmsPropertyConfig> m_propertyConfigs = new ArrayList<CmsPropertyConfig>();
379
380    /** The resource type configuration objects. */
381    private List<CmsResourceTypeConfig> m_resourceTypeConfigs = new ArrayList<CmsResourceTypeConfig>();
382
383    /**
384     * Creates a new configuration reader.<p>
385     *
386     * @param cms the CMS context which should be used to read the configuration data.<p>
387     */
388    public CmsConfigurationReader(CmsObject cms) {
389
390        m_cms = cms;
391    }
392
393    /**
394     * Gets the string value of an XML content location.<p>
395     *
396     * @param cms the CMS context to use
397     * @param location an XML content location
398     *
399     * @return the string value of that XML content location
400     */
401    public static String getString(CmsObject cms, I_CmsXmlContentValueLocation location) {
402
403        if (location == null) {
404            return null;
405        }
406        return location.asString(cms);
407    }
408
409    /**
410     * Helper method to parse a property.<p>
411     *
412     * @param cms the CMS context to use
413     * @param field the location of the parent value
414     *
415     * @return the parsed property configuration
416     */
417    public static CmsPropertyConfig parseProperty(CmsObject cms, I_CmsXmlContentLocation field) {
418
419        String name = getString(cms, field.getSubValue(N_PROPERTY_NAME));
420        String includeName = getString(cms, field.getSubValue(N_INCLUDE_NAME));
421        String widget = getString(cms, field.getSubValue(N_WIDGET));
422        String widgetConfig = getString(cms, field.getSubValue(N_WIDGET_CONFIG));
423        String ruleRegex = getString(cms, field.getSubValue(N_RULE_REGEX));
424        String ruleType = getString(cms, field.getSubValue(N_RULE_TYPE));
425        String default1 = getString(cms, field.getSubValue(N_DEFAULT));
426        String placeholder = getString(cms, field.getSubValue(N_PLACEHOLDER));
427        String error = getString(cms, field.getSubValue(N_ERROR));
428        String niceName = getString(cms, field.getSubValue(N_DISPLAY_NAME));
429        String description = getString(cms, field.getSubValue(N_DESCRIPTION));
430        String preferFolder = getString(cms, field.getSubValue(N_PREFER_FOLDER));
431        String aliasName = getString(cms, field.getSubValue(N_PROPERTY_NAME_ALIAS));
432        String valueTranslations = getString(cms, field.getSubValue(N_VALUE_TRANSLATION));
433
434        String disabledStr = getString(cms, field.getSubValue(N_DISABLED));
435        boolean disabled = ((disabledStr != null) && Boolean.parseBoolean(disabledStr));
436
437        String orderStr = getString(cms, field.getSubValue(N_ORDER));
438        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
439
440        try {
441            order = Integer.parseInt(orderStr);
442        } catch (NumberFormatException e) {
443            // noop
444        }
445
446        Visibility visibility;
447        String visibilityStr = getString(cms, field.getSubValue(N_VISIBILITY));
448        try {
449            // to stay compatible with former visibility option values
450            if ("both".equals(visibilityStr)) {
451                visibilityStr = Visibility.elementAndParentIndividual.name();
452            } else if ("parent".equals(visibilityStr)) {
453                visibilityStr = Visibility.parentShared.name();
454            }
455            visibility = Visibility.valueOf(visibilityStr);
456        } catch (Exception e) {
457            visibility = null;
458        }
459        CmsXmlContentProperty prop = new CmsXmlContentProperty(
460            name,
461            aliasName,
462            "string",
463            visibility,
464            widget,
465            widgetConfig,
466            ruleRegex,
467            ruleType,
468            default1,
469            placeholder,
470            niceName,
471            description,
472            error,
473            preferFolder,
474            valueTranslations).withIncludeName(includeName);
475        // since these are real properties, using type vfslist makes no sense, so we always use the "string" type
476        CmsPropertyConfig propConfig = new CmsPropertyConfig(prop, disabled, order);
477        return propConfig;
478
479    }
480
481    /**
482     * Returns the list of function references.<p>
483     *
484     * @return the list of function references
485     */
486    public List<CmsFunctionReference> getFunctionReferences() {
487
488        return new ArrayList<CmsFunctionReference>(m_functionReferences);
489    }
490
491    /**
492     * Returns the modelPageConfigs.<p>
493     *
494     * @return the modelPageConfigs
495     */
496    public List<CmsModelPageConfigWithoutResource> getModelPageConfigs() {
497
498        return m_modelPageConfigs;
499    }
500
501    /**
502     * Parses the formatters to add.<p>
503     *
504     * @param node the parent node
505     * @return the set of keys of the formatters to add
506     */
507    public Set<String> parseAddFormatters(I_CmsXmlContentLocation node) {
508
509        Set<String> addFormatters = new HashSet<String>();
510        for (I_CmsXmlContentValueLocation addLoc : node.getSubValues(N_ADD_FORMATTERS + "/" + N_ADD_FORMATTER)) {
511            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)addLoc.getValue();
512            CmsLink link = value.getLink(m_cms);
513            if (link != null) {
514                CmsUUID structureId = link.getStructureId();
515                if (structureId != null) {
516                    addFormatters.add(structureId.toString());
517                }
518            }
519        }
520        return addFormatters;
521    }
522
523    /**
524     * Parses a configuration XML content and creates a configuration object from it.<p>
525     *
526     * @param basePath the base path
527     * @param content the XML content
528     *
529     * @return the created configuration object with the data from the XML content
530     * @throws CmsException if something goes wrong
531     */
532    public CmsADEConfigDataInternal parseConfiguration(String basePath, CmsXmlContent content) throws CmsException {
533
534        m_detailPageConfigs = Lists.newArrayList();
535        m_functionReferences = Lists.newArrayList();
536        m_modelPageConfigs = Lists.newArrayList();
537        m_propertyConfigs = Lists.newArrayList();
538        m_resourceTypeConfigs = Lists.newArrayList();
539        CmsUUID baseId = null;
540        if (basePath != null) {
541            try {
542                CmsResource folderRes = m_cms.readResource(basePath, CmsResourceFilter.IGNORE_EXPIRATION);
543                baseId = folderRes.getStructureId();
544            } catch (Exception e) {
545                LOG.error(e.getLocalizedMessage(), e);
546            }
547        }
548        if (!content.hasLocale(DEFAULT_LOCALE)) {
549            return CmsADEConfigDataInternal.emptyConfiguration(basePath);
550        }
551        CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, DEFAULT_LOCALE);
552        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_RESOURCE_TYPE)) {
553            try {
554                parseResourceTypeConfig(basePath, node);
555            } catch (CmsException e) {
556                LOG.warn(e.getLocalizedMessage(), e);
557            }
558        }
559        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_MODEL_PAGE)) {
560            try {
561                parseModelPage(node);
562            } catch (Exception e) {
563                LOG.warn(e.getLocalizedMessage(), e);
564            }
565        }
566        for (I_CmsXmlContentLocation node : root.getSubValues(N_DETAIL_PAGE)) {
567            try {
568                parseDetailPage(node);
569            } catch (Exception e) {
570                LOG.warn(e.getLocalizedMessage(), e);
571            }
572        }
573
574        for (I_CmsXmlContentLocation node : root.getSubValues(N_FUNCTION_REF)) {
575            parseFunctionReference(node);
576        }
577
578        CmsUUID sharedSettingOverride = null;
579        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_SHARED_SETTING_OVERRIDE)) {
580            sharedSettingOverride = ((CmsXmlVfsFileValue)node.getValue()).getLink(m_cms).getStructureId();
581        }
582
583        boolean removeSharedSettingOverrides = getBoolean(root, N_REMOVE_ALL_SHARED_SETTING_OVERRIDES);
584
585        boolean removeFunctions = false;
586        removeFunctions = getBoolean(root, N_REMOVE_ALL_FUNCTIONS);
587
588        Set<CmsUUID> functions = new LinkedHashSet<>();
589        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_FUNCTION)) {
590            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
591            CmsLink link = value.getLink(m_cms);
592            if (link != null) {
593                CmsUUID structureId = link.getStructureId();
594                if (structureId != null) {
595                    functions.add(link.getStructureId());
596                }
597            }
598        }
599
600        Set<CmsUUID> functionsToRemove = new LinkedHashSet<>();
601        for (I_CmsXmlContentValueLocation parent : root.getSubValues(N_REMOVE_FUNCTIONS)) {
602            for (I_CmsXmlContentValueLocation node : parent.getSubValues(N_FUNCTION)) {
603                CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
604                CmsLink link = value.getLink(m_cms);
605                if (link != null) {
606                    CmsUUID structureId = link.getStructureId();
607                    if (structureId != null) {
608                        functionsToRemove.add(link.getStructureId());
609                    }
610                }
611            }
612        }
613
614        boolean removeAllPlugins = getBoolean(root, N_REMOVE_ALL_PLUGINS);
615        Set<CmsUUID> pluginsToRemove = readInternalLinkListTargetIds(root, N_REMOVE_PLUGINS, N_PLUGIN);
616        Set<CmsUUID> pluginsToAdd = readInternalLinkListTargetIds(root, N_ADD_PLUGINS, N_PLUGIN);
617
618        boolean removeAllFormatters = getBoolean(root, N_REMOVE_ALL_FORMATTERS);
619        CmsFormatterChangeSet formatterChangeSet = parseFormatterChangeSet(
620            basePath,
621            root,
622            removeAllFormatters,
623            removeFunctions,
624            functions,
625            functionsToRemove);
626        boolean discardInheritedTypes = getBoolean(root, N_DISCARD_TYPES);
627        // boolean discardInheritedProperties = getBoolean(root, N_DISCARD_PROPERTIES);
628        I_CmsXmlContentValueLocation discardPropertiesLoc = root.getSubValue(N_DISCARD_PROPERTIES);
629        DiscardPropertiesMode discardPropertiesMode = DiscardPropertiesMode.keep;
630        if (discardPropertiesLoc != null) {
631            String discardPropertiesStr = discardPropertiesLoc.getValue().getStringValue(m_cms);
632            for (DiscardPropertiesMode discardMode : DiscardPropertiesMode.values()) {
633                if (discardMode.getStringValue().contentEquals(discardPropertiesStr)) {
634                    discardPropertiesMode = discardMode;
635                }
636            }
637        }
638        for (I_CmsXmlContentLocation node : root.getSubValues(N_PROPERTY)) {
639            parseProperty(node, discardPropertiesMode);
640        }
641
642        boolean discardInheritedModelPages = getBoolean(root, N_DISCARD_MODEL_PAGES);
643
644        boolean createContentsLocally = getBoolean(root, N_CREATE_CONTENTS_LOCALLY);
645        CmsContentFolderOption contentFolderOption = null;
646        for (I_CmsXmlContentValueLocation contentFolderLoc : root.getSubValues(N_CONTENT_FOLDER)) {
647            contentFolderOption = parseContentFolderOption(contentFolderLoc);
648        }
649        boolean preferDetailPagesForLocalContents = getBoolean(root, N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS);
650        boolean exludeExternalDetailContents = getBoolean(root, N_EXCLUDE_EXTERNAL_DETAIL_CONTENTS);
651        boolean includeInSiteSelector = getBoolean(root, N_INCLUDE_IN_SITE_SELECTOR);
652
653        String galleryDisabledTypesStr = getString(root.getSubValue(N_DISABLED_TYPES_MODE));
654        CmsGalleryDisabledTypesMode galleryDisabledTypesMode = null;
655        if (galleryDisabledTypesStr != null) {
656            galleryDisabledTypesMode = CmsGalleryDisabledTypesMode.valueOf(galleryDisabledTypesStr);
657        }
658
659        String galleryDisabledFunctionsStr = getString(root.getSubValue(N_DISABLED_FUNCTIONS_MODE));
660        CmsGalleryDisabledTypesMode galleryDisabledFunctionsMode = null;
661        if (galleryDisabledFunctionsStr != null) {
662            galleryDisabledFunctionsMode = CmsGalleryDisabledTypesMode.valueOf(galleryDisabledFunctionsStr);
663        }
664
665        I_CmsXmlContentValueLocation typeOrderingLoc = root.getSubValue(N_TYPE_ORDERING_MODE);
666        CmsTypeOrderingMode typeOrderingMode = null;
667        if (typeOrderingLoc != null) {
668            boolean byDisplayOrder = Boolean.parseBoolean(typeOrderingLoc.getValue().getStringValue(m_cms));
669            typeOrderingMode = byDisplayOrder ? CmsTypeOrderingMode.byDisplayOrder : CmsTypeOrderingMode.latestOnTop;
670        }
671
672        I_CmsXmlContentValueLocation useFormatterKeysLoc = root.getSubValue(N_USE_FORMATTER_KEYS);
673        Boolean useFormatterKeys = null;
674        if (useFormatterKeysLoc != null) {
675            useFormatterKeys = Boolean.valueOf(useFormatterKeysLoc.getValue().getStringValue(m_cms));
676        }
677
678        boolean isModuleConfig = OpenCms.getResourceManager().getResourceType(
679            content.getFile().getTypeId()).getTypeName().equals(CmsADEManager.MODULE_CONFIG_TYPE);
680
681        List<ConfigReference> masterConfigIds = new ArrayList<>();
682        for (I_CmsXmlContentValueLocation masterConfigLoc : root.getSubValues(N_MASTER_CONFIG)) {
683            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)masterConfigLoc.getValue();
684            CmsLink link = value.getUncheckedLink();
685            ConfigReferenceMeta meta = null;
686            if (link != null) {
687                meta = new ConfigReferenceMeta(link.getParameterMap());
688            }
689            CmsUUID id = masterConfigLoc.asId(m_cms);
690            if (id != null) {
691                masterConfigIds.add(new ConfigReference(id, meta));
692            }
693        }
694
695        Map<String, String> attributes = new LinkedHashMap<>();
696        for (I_CmsXmlContentValueLocation mappingLoc : root.getSubValues(N_ATTRIBUTE)) {
697            String key = getString(mappingLoc.getSubValue(N_KEY)).trim();
698            String value = getString(mappingLoc.getSubValue(N_VALUE)).trim();
699            attributes.put(key, value);
700        }
701
702        I_CmsXmlContentValueLocation attributeEditorConfigLoc = root.getSubValue(N_ATTRIBUTE_EDITOR_CONFIG);
703        CmsUUID attributeEditorConfigId = null;
704        if (attributeEditorConfigLoc != null) {
705            attributeEditorConfigId = attributeEditorConfigLoc.asId(m_cms);
706        }
707
708        CmsAddContentRestriction addContentRestriction = CmsAddContentRestriction.read(
709            m_cms,
710            root,
711            N_ADD_CONTENT_RESTRICTION);
712
713        CmsADEConfigDataInternal result = new CmsADEConfigDataInternal(
714            m_cms,
715            content.getFile(),
716            isModuleConfig,
717            basePath,
718            baseId,
719            masterConfigIds,
720            m_resourceTypeConfigs,
721            galleryDisabledTypesMode,
722            galleryDisabledFunctionsMode,
723            discardInheritedTypes,
724            m_propertyConfigs,
725            discardPropertiesMode,
726            m_detailPageConfigs,
727            m_modelPageConfigs,
728            m_functionReferences,
729            discardInheritedModelPages,
730            createContentsLocally,
731            contentFolderOption,
732            preferDetailPagesForLocalContents,
733            exludeExternalDetailContents,
734            includeInSiteSelector,
735            formatterChangeSet,
736            removeFunctions,
737            functions,
738            functionsToRemove,
739            removeAllPlugins,
740            pluginsToAdd,
741            pluginsToRemove,
742            useFormatterKeys,
743            typeOrderingMode,
744            addContentRestriction,
745            sharedSettingOverride,
746            removeSharedSettingOverrides,
747            attributeEditorConfigId,
748            attributes);
749        return result;
750    }
751
752    /**
753     * Parses the content folder configuration.
754     *
755     * @param location the XML content location from which to read the data.
756     *
757     * @return the content folder configuration
758     */
759    public CmsContentFolderOption parseContentFolderOption(I_CmsXmlContentLocation location) {
760
761        if (location == null) {
762            return null;
763        }
764        I_CmsXmlContentValueLocation defaultLoc = location.getSubValue(N_DEFAULT);
765        I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_PATH);
766        if (defaultLoc != null) {
767            return new CmsContentFolderOption(null);
768        }
769        if (pathLoc != null) {
770            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)pathLoc.getValue();
771            if ((value.getUncheckedLink() != null) && (value.getUncheckedLink().getStructureId() != null)) {
772
773                return new CmsContentFolderOption(value.getUncheckedLink().getStructureId());
774            }
775        }
776        if (LOG.isWarnEnabled()) {
777            LOG.warn(location.getDocument().getFile().getRootPath() + ": invalid content folder");
778        }
779        return null;
780    }
781
782    /**
783     * Parses a folder which may either be given as a path or as a folder name.<p>
784     *
785     * @param basePath the  base path for the configuration
786     * @param location the XML content node from which to parse the folder
787     * @return the folder bean
788     *
789     * @throws CmsException if something goes wrong
790     */
791    public CmsContentFolderDescriptor parseFolderOrName(String basePath, I_CmsXmlContentLocation location)
792    throws CmsException {
793
794        if (location == null) {
795            return null;
796        }
797        I_CmsXmlContentValueLocation nameLoc = location.getSubValue(N_NAME);
798        I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_PATH);
799        I_CmsXmlContentValueLocation pageRelativeLoc = location.getSubValue(N_PAGE_RELATIVE);
800        if (nameLoc != null) {
801            String name = nameLoc.asString(m_cms);
802            return new CmsContentFolderDescriptor(
803                basePath == null ? null : CmsStringUtil.joinPaths(basePath, CmsADEManager.CONTENT_FOLDER_NAME),
804                name);
805        } else if (pathLoc != null) {
806            String path = pathLoc.asString(m_cms);
807            CmsResource folder = m_cms.readResource(path);
808            return new CmsContentFolderDescriptor(folder);
809        } else if (pageRelativeLoc != null) {
810            return CmsContentFolderDescriptor.createPageRelativeFolderDescriptor();
811        } else {
812            return null;
813        }
814    }
815
816    /**
817     * Parses a formatter bean.<p>
818     *
819     * @param typeName the type name for which the formatter is being parsed
820     * @param node the node from which to parse the formatter data
821     *
822     * @return the formatter bean from the XML
823     */
824    public CmsFormatterBean parseFormatter(String typeName, I_CmsXmlContentLocation node) {
825
826        String type = getString(node.getSubValue(N_TYPE));
827        String minWidth = getString(node.getSubValue(N_MIN_WIDTH));
828        String maxWidth = getString(node.getSubValue(N_MAX_WIDTH));
829        boolean preview = false;
830        I_CmsXmlContentValueLocation previewLoc = node.getSubValue(N_IS_PREVIEW);
831        preview = (previewLoc != null) && Boolean.parseBoolean(previewLoc.asString(m_cms));
832        String jsp = m_cms.getRequestContext().addSiteRoot(getString(node.getSubValue(N_JSP)));
833        boolean searchContent = true;
834        CmsFormatterBean formatterBean = new CmsFormatterBean(
835            type,
836            jsp,
837            minWidth,
838            maxWidth,
839            "" + preview,
840            "" + searchContent,
841            null);
842        return formatterBean;
843
844    }
845
846    /**
847     * Parses model page data from the XML content.<p>
848     *
849     * @param node the XML content node
850     */
851    public void parseModelPage(I_CmsXmlContentLocation node) {
852
853        CmsXmlVfsFileValue pageValue = (CmsXmlVfsFileValue)node.getSubValue(N_PAGE).getValue();
854        CmsLink link = pageValue.getUncheckedLink();
855        if ((link == null) || (link.getStructureId() == null)) {
856            return;
857        }
858        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
859        boolean disabled = (disabledLoc != null) && Boolean.parseBoolean(disabledLoc.asString(m_cms));
860        I_CmsXmlContentValueLocation defaultLoc = node.getSubValue(N_IS_DEFAULT);
861        boolean isDefault = (defaultLoc != null) && Boolean.parseBoolean(defaultLoc.asString(m_cms));
862        CmsModelPageConfigWithoutResource modelPage = new CmsModelPageConfigWithoutResource(
863            link.getStructureId(),
864            isDefault,
865            disabled);
866        m_modelPageConfigs.add(modelPage);
867
868    }
869
870    /**
871     * Parses the set of formatters to remove.<p>
872     *
873     * @param node the parent node
874     * @return the set of formatters to remove
875     */
876    public Set<String> parseRemoveFormatters(I_CmsXmlContentLocation node) {
877
878        Set<String> removeFormatters = new HashSet<String>();
879        for (I_CmsXmlContentValueLocation removeLoc : node.getSubValues(
880            N_REMOVE_FORMATTERS + "/" + N_REMOVE_FORMATTER)) {
881            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)removeLoc.getValue();
882            CmsLink link = value.getLink(m_cms);
883            if (link != null) {
884                CmsUUID structureId = link.getStructureId();
885                if (structureId != null) {
886                    removeFormatters.add(structureId.toString());
887                }
888            }
889        }
890        return removeFormatters;
891    }
892
893    /**
894     * Parses a resource type configuration element from the XML content.<p>
895     *
896     * @param basePath the base path of the configuration
897     * @param node the XML configuration node
898     * @throws CmsException if something goes wrong
899     */
900    public void parseResourceTypeConfig(String basePath, I_CmsXmlContentLocation node) throws CmsException {
901
902        I_CmsXmlContentValueLocation typeNameLoc = node.getSubValue(N_TYPE_NAME);
903        String typeName = typeNameLoc.asString(m_cms);
904        CmsContentFolderDescriptor folderOrName = parseFolderOrName(basePath, node.getSubValue(N_FOLDER));
905        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
906        boolean disabled = false;
907        boolean addDisabled = false;
908        boolean createDisabled = false;
909        boolean editDisabled = false;
910        boolean listsOnly = false;
911        String disabledStr = disabledLoc == null ? null : disabledLoc.asString(m_cms);
912        boolean availabilityNotSet = false;
913        if (disabledStr != null) {
914            if ("add".equalsIgnoreCase(disabledStr.trim())) {
915                addDisabled = true;
916            } else if ("create".equalsIgnoreCase(disabledStr.trim())) {
917                createDisabled = true;
918            } else if ("createOrEdit".equalsIgnoreCase(disabledStr.trim())) {
919                createDisabled = true;
920                editDisabled = true;
921            } else if ("listsOnly".equalsIgnoreCase(disabledStr.trim())) {
922                listsOnly = true;
923                addDisabled = true;
924            } else {
925                disabled = Boolean.parseBoolean(disabledStr);
926            }
927        } else {
928            availabilityNotSet = true;
929        }
930
931        I_CmsXmlContentValueLocation namePatternLoc = node.getSubValue(N_NAME_PATTERN);
932        String namePattern = null;
933        if (namePatternLoc != null) {
934            namePattern = namePatternLoc.asString(m_cms);
935        }
936
937        boolean detailPagesDisabled = false;
938        I_CmsXmlContentValueLocation detailDisabledLoc = node.getSubValue(N_DETAIL_PAGES_DISABLED);
939        if (detailDisabledLoc != null) {
940            String detailPagesDisabledStr = detailDisabledLoc.asString(m_cms);
941            detailPagesDisabled = Boolean.parseBoolean(detailPagesDisabledStr);
942        }
943
944        Integer order = null;
945        I_CmsXmlContentValueLocation orderLoc = node.getSubValue(N_ORDER);
946        if (orderLoc != null) {
947            try {
948                String orderStr = orderLoc.asString(m_cms);
949                order = Integer.valueOf(orderStr);
950            } catch (NumberFormatException e) {
951                // noop
952            }
953        }
954
955        I_CmsXmlContentValueLocation elementViewLoc = node.getSubValue(N_ELEMENT_VIEW);
956        CmsUUID elementView = null;
957        if (elementViewLoc != null) {
958            try {
959                CmsXmlVarLinkValue elementViewValue = (CmsXmlVarLinkValue)elementViewLoc.getValue();
960                String stringValue = elementViewValue.getStringValue(m_cms);
961                if ("".equals(stringValue)) {
962                    elementView = CmsUUID.getNullUUID();
963                } else if (stringValue.startsWith(VIEW_SCHEME)) {
964                    elementView = new CmsUUID(stringValue.substring(VIEW_SCHEME.length()));
965                } else {
966                    elementView = elementViewValue.getLink(m_cms).getStructureId();
967                }
968            } catch (Exception e) {
969                // in case parsing the link fails, the default element view will be used
970            }
971        }
972
973        I_CmsXmlContentValueLocation locationLoc = node.getSubValue(N_LOCALIZATION);
974        String localization = null;
975        if (locationLoc != null) {
976            CmsXmlVfsFileValue locationValue = (CmsXmlVfsFileValue)locationLoc.getValue();
977            CmsLink link = locationValue.getLink(m_cms);
978            if (null != link) {
979                String stringValue = link.getSitePath(m_cms);
980                // extract bundle base name from the path to the bundle file
981                int lastSlashIndex = stringValue.lastIndexOf("/");
982                String fileName = stringValue.substring(lastSlashIndex + 1);
983                if (CmsFileUtil.getExtension(fileName).equals(".properties")) {
984                    fileName = fileName.substring(0, fileName.length() - ".properties".length());
985                }
986                String localeSuffix = CmsStringUtil.getLocaleSuffixForName(fileName);
987                if ((localeSuffix != null) && fileName.endsWith(localeSuffix)) {
988                    fileName = fileName.substring(0, fileName.length() - localeSuffix.length() - 1);
989                }
990                localization = fileName;
991            }
992        }
993
994        I_CmsXmlContentValueLocation showDefaultViewLoc = node.getSubValue(N_SHOW_IN_DEFAULT_VIEW);
995        Boolean showInDefaultView = null;
996        if (showDefaultViewLoc != null) {
997            showInDefaultView = Boolean.valueOf(
998                Boolean.parseBoolean(showDefaultViewLoc.getValue().getStringValue(m_cms)));
999        }
1000
1001        I_CmsXmlContentValueLocation copyInModelsLoc = node.getSubValue(N_COPY_IN_MODELS);
1002        Boolean copyInModels = null;
1003        if (copyInModelsLoc != null) {
1004            copyInModels = Boolean.valueOf(Boolean.parseBoolean(copyInModelsLoc.getValue().getStringValue(m_cms)));
1005        }
1006
1007        I_CmsXmlContentValueLocation elementDeleteModeLoc = node.getSubValue(N_ELEMENT_DELETE_MODE);
1008        ElementDeleteMode elementDeleteMode = null;
1009        if (elementDeleteModeLoc != null) {
1010            try {
1011                elementDeleteMode = ElementDeleteMode.valueOf(elementDeleteModeLoc.getValue().getStringValue(m_cms));
1012            } catch (Exception e) {
1013                LOG.warn(e.getLocalizedMessage(), e);
1014            }
1015        }
1016
1017        I_CmsXmlContentValueLocation checkReuseLoc = node.getSubValue(N_CHECK_REUSE);
1018        Boolean checkReuse = null;
1019        if (checkReuseLoc != null) {
1020            try {
1021                checkReuse = Boolean.valueOf(checkReuseLoc.getValue().getStringValue(m_cms));
1022            } catch (Exception e) {
1023                LOG.warn(e.getLocalizedMessage(), e);
1024            }
1025        }
1026
1027        List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>();
1028        for (I_CmsXmlContentValueLocation formatterLoc : node.getSubValues(N_FORMATTER)) {
1029            CmsFormatterBean formatter = parseFormatter(typeName, formatterLoc);
1030            formatters.add(formatter);
1031        }
1032
1033        CmsResourceTypeConfig typeConfig = new CmsResourceTypeConfig(
1034            typeName,
1035            disabled,
1036            folderOrName,
1037            namePattern,
1038            detailPagesDisabled,
1039            addDisabled,
1040            createDisabled,
1041            editDisabled,
1042            listsOnly,
1043            availabilityNotSet,
1044            elementView,
1045            localization,
1046            showInDefaultView,
1047            copyInModels,
1048            order,
1049            elementDeleteMode,
1050            checkReuse);
1051        m_resourceTypeConfigs.add(typeConfig);
1052    }
1053
1054    /**
1055     * Parses the sitemap configuration given the configuration file and base path.<p>
1056     *
1057     * @param basePath the base path
1058     * @param configRes the configuration file resource
1059     * @return the parsed configuration data
1060     * @throws CmsException if something goes wrong
1061     */
1062    public CmsADEConfigDataInternal parseSitemapConfiguration(String basePath, CmsResource configRes)
1063    throws CmsException {
1064
1065        LOG.info("Parsing configuration " + configRes.getRootPath());
1066        CmsFile configFile = m_cms.readFile(configRes);
1067        CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, configFile);
1068        return parseConfiguration(basePath, content);
1069    }
1070
1071    /**
1072     * Reads the configurations of all modules and combines them into a single configuration object.<p>
1073     *
1074     * @return the combined configuration object
1075     */
1076    public List<CmsADEConfigDataInternal> readModuleConfigurations() {
1077
1078        List<CmsADEConfigDataInternal> configurations = new ArrayList<CmsADEConfigDataInternal>();
1079        List<CmsModule> modules = OpenCms.getModuleManager().getAllInstalledModules();
1080        long beginTime = System.currentTimeMillis();
1081        for (CmsModule module : modules) {
1082            String configPath = module.getConfigurationPath();
1083            if (m_cms.existsResource(configPath)) {
1084                try {
1085                    CmsResource configFile = m_cms.readResource(configPath);
1086                    LOG.info("Found module configuration " + configPath + " for module " + module.getName());
1087                    CmsADEConfigDataInternal config = parseSitemapConfiguration(null, configFile);
1088                    configurations.add(config);
1089                } catch (CmsException e) {
1090                    // errors while parsing configuration
1091                    LOG.error(e.getLocalizedMessage(), e);
1092                } catch (CmsRuntimeException e) {
1093                    // may happen during import of org.opencms.ade.configuration module
1094                    LOG.warn(e.getLocalizedMessage(), e);
1095                } catch (Throwable e) {
1096                    LOG.error(e.getLocalizedMessage(), e);
1097                }
1098            }
1099        }
1100        long endTime = System.currentTimeMillis();
1101        LOG.debug("readModuleConfiguations took " + (endTime - beginTime) + "ms");
1102        return configurations;
1103    }
1104
1105    /**
1106     * Helper method to read a boolean value from the XML.<p>
1107     *
1108     * If the element is not found in the XML, false is returned.<p>
1109     *
1110     * @param parent the parent node
1111     * @param name the name of the XML content value
1112     * @return the boolean value
1113     */
1114    protected boolean getBoolean(I_CmsXmlContentLocation parent, String name) {
1115
1116        I_CmsXmlContentValueLocation location = parent.getSubValue(name);
1117        if (location == null) {
1118            return false;
1119        }
1120        String value = location.getValue().getStringValue(m_cms);
1121        return Boolean.parseBoolean(value);
1122    }
1123
1124    /**
1125     * Gets the string value of an XML content location.<p>
1126     *
1127     * @param location an XML content location
1128     *
1129     * @return the string value of that XML content location
1130     */
1131    protected String getString(I_CmsXmlContentValueLocation location) {
1132
1133        return getString(m_cms, location);
1134    }
1135
1136    /**
1137     * Parses the detail pages from an XML content node.<p>
1138     *
1139     * @param node the XML content node
1140     */
1141    protected void parseDetailPage(I_CmsXmlContentLocation node) {
1142
1143        I_CmsXmlContentValueLocation pageLoc = node.getSubValue(N_PAGE);
1144        String typeName = getString(node.getSubValue(N_TYPE));
1145        int qualifierPos = typeName.indexOf(CmsDetailPageInfo.QUALIFIER_SEPARATOR);
1146        String qualifier = null;
1147        if (qualifierPos != -1) {
1148            qualifier = typeName.substring(qualifierPos + 1);
1149            typeName = typeName.substring(0, qualifierPos);
1150        }
1151        CmsXmlVfsFileValue detailPageValue = (CmsXmlVfsFileValue)pageLoc.getValue();
1152        CmsLink uncheckedLink = detailPageValue.getUncheckedLink();
1153        if (uncheckedLink == null) {
1154            LOG.warn(
1155                "Missing detail page link in " + CmsLog.eval(LOG, () -> node.getDocument().getFile().getRootPath()));
1156            return;
1157        }
1158        String page = uncheckedLink.getTarget();
1159        CmsUUID structureId = uncheckedLink.getStructureId();
1160        if (structureId == null) {
1161            return;
1162        }
1163
1164        String iconClasses;
1165        if (typeName.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) {
1166            iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false);
1167        } else {
1168            iconClasses = CmsIconUtil.getIconClasses(typeName, null, false);
1169        }
1170        List<CmsUUID> folders = new ArrayList<>();
1171        for (I_CmsXmlContentValueLocation folderLoc : node.getSubValues(N_FOLDER)) {
1172            CmsXmlVfsFileValue folderValue = (CmsXmlVfsFileValue)folderLoc.getValue();
1173            CmsLink folderLink = folderValue.getUncheckedLink();
1174            if ((folderLink != null) && (folderLink.getStructureId() != null)) {
1175                folders.add(folderLink.getStructureId());
1176            }
1177        }
1178
1179        CmsDetailPageInfo detailPage = new CmsDetailPageInfo(
1180            structureId,
1181            page,
1182            typeName,
1183            qualifier,
1184            folders,
1185            iconClasses);
1186        m_detailPageConfigs.add(detailPage);
1187
1188    }
1189
1190    /**
1191     * Parses the formatter change set.<p>
1192     *
1193     * @param basePath the configuration base path
1194     * @param node the parent node
1195     * @param removeAllFormatters flag, indicating if all formatters that are not explicitly added should be removed
1196     * @param removeFunctions if true, remove functions
1197     * @param functions the functions to add
1198     * @param functionsToRemove the functions to remove
1199     *
1200     * @return the formatter change set
1201     */
1202    protected CmsFormatterChangeSet parseFormatterChangeSet(
1203        String basePath,
1204        I_CmsXmlContentLocation node,
1205        boolean removeAllFormatters,
1206        boolean removeFunctions,
1207        Set<CmsUUID> functions,
1208        Set<CmsUUID> functionsToRemove) {
1209
1210        Set<String> addFormatters = parseAddFormatters(node);
1211        addFormatters.addAll(readLocalFormatters(node));
1212        Set<String> removeFormatters = removeAllFormatters ? new HashSet<String>() : parseRemoveFormatters(node);
1213        String siteRoot = null;
1214        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(basePath)) {
1215            siteRoot = OpenCms.getSiteManager().getSiteRoot(basePath);
1216        }
1217        CmsFormatterChangeSet result = new CmsFormatterChangeSet(
1218            removeFormatters,
1219            addFormatters,
1220            siteRoot,
1221            removeAllFormatters,
1222            removeFunctions,
1223            functions,
1224            functionsToRemove);
1225        return result;
1226    }
1227
1228    /**
1229     * Parses a function reference node.<p>
1230     *
1231     * @param node the function reference node
1232     */
1233    protected void parseFunctionReference(I_CmsXmlContentLocation node) {
1234
1235        String name = node.getSubValue(N_NAME).asString(m_cms);
1236        CmsUUID functionId = node.getSubValue(N_FUNCTION).asId(m_cms);
1237        CmsUUID functionDefaultPageId = null;
1238        I_CmsXmlContentValueLocation defaultPageValue = node.getSubValue(N_FUNCTION_DEFAULT_PAGE);
1239        if (defaultPageValue != null) {
1240            functionDefaultPageId = defaultPageValue.asId(m_cms);
1241        }
1242        I_CmsXmlContentValueLocation orderNode = node.getSubValue(N_ORDER);
1243        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
1244        if (orderNode != null) {
1245            String orderStr = orderNode.asString(m_cms);
1246            try {
1247                order = Integer.parseInt(orderStr);
1248            } catch (NumberFormatException e) {
1249                // noop
1250            }
1251        }
1252        m_functionReferences.add(new CmsFunctionReference(name, functionId, functionDefaultPageId, order));
1253    }
1254
1255    /**
1256     * Parses a single field definition from a content value.<p>
1257     *
1258     * @param field the content value to parse the field from
1259     * @param mode the property discard mode
1260     */
1261    private void parseProperty(I_CmsXmlContentLocation field, DiscardPropertiesMode mode) {
1262
1263        CmsPropertyConfig propConfig = parseProperty(m_cms, field);
1264        if (CmsStringUtil.isEmptyOrWhitespaceOnly(propConfig.getName())) {
1265            return;
1266        }
1267        if (mode == DiscardPropertiesMode.top) {
1268            propConfig = propConfig.cloneWithTop(true);
1269        }
1270        m_propertyConfigs.add(propConfig);
1271    }
1272
1273    /**
1274     * Helper method for reading the target ids from a list of internal links two levels nested.
1275     *
1276     * @param root the parent location
1277     * @param childName the node name for the children
1278     * @param grandchildName the node name for the grandchildren
1279     *
1280     * @return the set of target ids collected from the grandchildren
1281     */
1282    private Set<CmsUUID> readInternalLinkListTargetIds(
1283        I_CmsXmlContentLocation root,
1284        String childName,
1285        String grandchildName) {
1286
1287        Set<CmsUUID> result = new LinkedHashSet<>();
1288        for (I_CmsXmlContentValueLocation parent : root.getSubValues(childName)) {
1289            for (I_CmsXmlContentValueLocation node : parent.getSubValues(grandchildName)) {
1290                CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
1291                CmsLink link = value.getLink(m_cms);
1292                if (link != null) {
1293                    CmsUUID structureId = link.getStructureId();
1294                    if (structureId != null) {
1295                        result.add(link.getStructureId());
1296                    }
1297                }
1298            }
1299        }
1300        return result;
1301    }
1302
1303    /**
1304     * Reads the local macro or flex formatters from the .formatters folder if present.<p>
1305     *
1306     * @param node the xml content node
1307     *
1308     * @return the local formatters
1309     */
1310    private Set<String> readLocalFormatters(I_CmsXmlContentLocation node) {
1311
1312        Set<String> addFormatters = new HashSet<String>();
1313        String path = m_cms.getSitePath(node.getDocument().getFile());
1314        path = CmsStringUtil.joinPaths(CmsResource.getParentFolder(path), ".formatters");
1315        try {
1316            if (m_cms.existsResource(path, CmsResourceFilter.IGNORE_EXPIRATION)) {
1317                I_CmsResourceType macroType = OpenCms.getResourceManager().getResourceType(
1318                    CmsFormatterConfigurationCache.TYPE_MACRO_FORMATTER);
1319                CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(macroType);
1320                List<CmsResource> macroFormatters = m_cms.readResources(path, filter);
1321                for (CmsResource formatter : macroFormatters) {
1322                    addFormatters.add(formatter.getStructureId().toString());
1323                }
1324                I_CmsResourceType flexType = OpenCms.getResourceManager().getResourceType(
1325                    CmsFormatterConfigurationCache.TYPE_FLEX_FORMATTER);
1326                CmsResourceFilter filterFlex = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(flexType);
1327                List<CmsResource> flexFormatters = m_cms.readResources(path, filterFlex);
1328                for (CmsResource formatter : flexFormatters) {
1329                    addFormatters.add(formatter.getStructureId().toString());
1330                }
1331            }
1332        } catch (CmsException e) {
1333            LOG.warn(e.getMessage(), e);
1334        }
1335        return addFormatters;
1336    }
1337
1338}