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