001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, 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.contenteditor;
029
030import org.opencms.file.CmsObject;
031import org.opencms.i18n.CmsMessages;
032import org.opencms.i18n.CmsMultiMessages;
033import org.opencms.main.OpenCms;
034import org.opencms.util.CmsMacroResolver;
035import org.opencms.widgets.I_CmsComplexWidget;
036import org.opencms.widgets.I_CmsWidget;
037import org.opencms.xml.CmsXmlContentDefinition;
038import org.opencms.xml.CmsXmlUtils;
039import org.opencms.xml.content.I_CmsXmlContentHandler;
040import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
041import org.opencms.xml.types.I_CmsXmlContentValue;
042import org.opencms.xml.types.I_CmsXmlSchemaType;
043
044import java.util.ArrayList;
045import java.util.List;
046import java.util.Locale;
047
048import org.apache.commons.collections4.CollectionUtils;
049
050/**
051 * Utility methods for getting widget informations out of content definitions.<p>
052 */
053public final class CmsWidgetUtil {
054
055    /**
056     * Bean representing widget information.<p>
057     */
058    public static class WidgetInfo {
059
060        /** The display type. */
061        private DisplayType m_displayType;
062
063        /** A widget instance. */
064        private I_CmsWidget m_widget;
065
066        /** The complex widget. */
067        private I_CmsComplexWidget m_complexWidget;
068
069        /**
070         * Gets the complex widget.<p>
071         *
072         * @return the complex widget
073         */
074        public I_CmsComplexWidget getComplexWidget() {
075
076            return m_complexWidget;
077        }
078
079        /**
080         * Gets the display type.<p>
081         *
082         * @return the display type
083         */
084        public DisplayType getDisplayType() {
085
086            return m_displayType;
087        }
088
089        /**
090         * Gets the widget instance.<p>
091         *
092         * @return the widget instance
093         */
094        public I_CmsWidget getWidget() {
095
096            return m_widget;
097        }
098
099        /**
100         * Sets the complex widget.<p>
101         *
102         * @param complexWidget the complex widget to set
103         */
104        public void setComplexWidget(I_CmsComplexWidget complexWidget) {
105
106            m_complexWidget = complexWidget;
107
108        }
109
110        /**
111         * Sets the display type.<p>
112         *
113         * @param displayType the display type
114         */
115        public void setDisplayType(DisplayType displayType) {
116
117            m_displayType = displayType;
118        }
119
120        /**
121         * Sets the widget.<p>
122         *
123         * @param widget the widget
124         */
125        public void setWidget(I_CmsWidget widget) {
126
127            m_widget = widget;
128        }
129    }
130
131    /**
132     * Hidden default constructor.
133     */
134    private CmsWidgetUtil() {
135
136        // hidden default constructor
137    }
138
139    /**
140     * Collects widget information for a given content definition and content value path.<p>
141     *
142     * @param cms the the CMS context to use
143     * @param rootContentDefinition the content definition
144     * @param path the path relative to the given content definition
145     * @param messages the message bundle to use
146     * @param overrideLocale the explicit locale to use for resolving message keys (if null, use the workplace locale of the user)
147     *
148     * @return the widget information for the given path
149     */
150    public static WidgetInfo collectWidgetInfo(
151        CmsObject cms,
152        CmsXmlContentDefinition rootContentDefinition,
153        String path,
154        CmsMessages messages,
155        Locale overrideLocale) {
156
157        String widgetConfig = null;
158        DisplayType configuredType = DisplayType.none;
159        I_CmsXmlSchemaType schemaType = rootContentDefinition.getSchemaType(path);
160
161        I_CmsWidget widget = null;
162        I_CmsComplexWidget complexWidget = null;
163        I_CmsXmlContentHandler contentHandler = schemaType.getContentDefinition().getContentHandler();
164        final List<I_CmsWidget> widgets = new ArrayList<>();
165        final List<String> widgetConfigs = new ArrayList<>();
166        final List<DisplayType> configuredDisplayTypes = new ArrayList<>();
167        final List<I_CmsComplexWidget> configuredComplexWidgets = new ArrayList<>();
168        Locale locale = overrideLocale != null ? overrideLocale : OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
169        if (messages == null) {
170            CmsMultiMessages multi = new CmsMultiMessages(locale);
171            multi.addMessages(OpenCms.getWorkplaceManager().getMessages(locale));
172            CmsMessages contentHandlerMessages = rootContentDefinition.getContentHandler().getMessages(locale);
173            if (contentHandlerMessages != null) {
174                // Note: the default content handler class will always return a non-null messages object
175                multi.addMessages(contentHandlerMessages);
176            }
177            messages = multi;
178        }
179
180        // Use lists to store found widget configurations, and then use the first elements of each list.
181        // Because we iterate from the top level schema down to the nested schema, configurations in higher level schemas
182        // will have precedence over those in lower level schemas for the same element.
183
184        rootContentDefinition.findSchemaTypesForPath(path, (nestedType, remainingPath) -> {
185            remainingPath = CmsXmlUtils.concatXpath(nestedType.getName(), remainingPath);
186            I_CmsXmlContentHandler handler = nestedType.getContentDefinition().getContentHandler();
187            I_CmsWidget widgetForPath = handler.getWidget(cms, remainingPath);
188            CollectionUtils.addIgnoreNull(widgets, widgetForPath);
189            CollectionUtils.addIgnoreNull(widgetConfigs, handler.getConfiguration(remainingPath));
190            CollectionUtils.addIgnoreNull(
191                configuredDisplayTypes,
192                handler.getConfiguredDisplayType(remainingPath, null));
193            if (widgetForPath == null) {
194                // If we already have a normal widget, trying to find a complex widget for the same path is unnecessary,
195                // and would also cost performance (because of failing Class.forName calls in getComplexWidget).
196                CollectionUtils.addIgnoreNull(configuredComplexWidgets, handler.getComplexWidget(cms, remainingPath));
197            }
198
199        });
200        if (!widgets.isEmpty()) {
201            widget = widgets.get(0).newInstance();
202        } else {
203            widget = OpenCms.getXmlContentTypeManager().getWidgetDefault(schemaType.getTypeName());
204        }
205        if (!configuredDisplayTypes.isEmpty()) {
206            configuredType = configuredDisplayTypes.get(0);
207        }
208        if (!widgetConfigs.isEmpty()) {
209            widgetConfig = widgetConfigs.get(0);
210        } else if (widget != null) {
211            widgetConfig = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(widget);
212        }
213        CmsMacroResolver resolver = new CmsMacroResolver();
214        resolver.setCmsObject(cms);
215        resolver.setKeepEmptyMacros(false);
216        resolver.setMessages(messages);
217        if (widget != null) {
218            String resolvedConfig = resolveWidgetConfigMacros(resolver, widgetConfig);
219            widget.setConfiguration(resolvedConfig);
220        }
221        // default complex widget and default c. widget config have lower priorities than those directly defined, so put them at the end of the list
222        CollectionUtils.addIgnoreNull(configuredComplexWidgets, contentHandler.getDefaultComplexWidget());
223        List<String> complexWidgetConfigs = new ArrayList<>(widgetConfigs);
224        CollectionUtils.addIgnoreNull(complexWidgetConfigs, contentHandler.getDefaultComplexWidgetConfiguration());
225        if (!configuredComplexWidgets.isEmpty()) {
226            String config = "";
227            if (!complexWidgetConfigs.isEmpty()) {
228                config = complexWidgetConfigs.get(0);
229                config = resolveWidgetConfigMacros(resolver, config);
230            }
231            complexWidget = configuredComplexWidgets.get(0).configure(config);
232        }
233        WidgetInfo result = new WidgetInfo();
234        result.setComplexWidget(complexWidget);
235        result.setDisplayType(configuredType);
236        result.setWidget(widget);
237        return result;
238    }
239
240    /**
241     * Collects widget information for a given content value.<p>
242     *
243     * @param cms the current CMS context
244     * @param value a content value
245     *
246     * @return the widget information for the given value
247     */
248
249    public static WidgetInfo collectWidgetInfo(CmsObject cms, I_CmsXmlContentValue value) {
250
251        CmsXmlContentDefinition contentDef = value.getDocument().getContentDefinition();
252        String path = value.getPath();
253        return collectWidgetInfo(cms, contentDef, path, null, null);
254    }
255
256    /**
257     * Resolves macros in a string using the given macro resolver, unless universal macro resolution for widget configurations is turned off by setting the widgets.config.resolveMacros.disabled runtime property to true in opencms-system.xml.
258     *
259     * @param resolver the macro resolver
260     * @param widgetConfig the widget configuration
261     * @return the macro resolution result
262     */
263    private static String resolveWidgetConfigMacros(CmsMacroResolver resolver, String widgetConfig) {
264
265        if (Boolean.parseBoolean((String)OpenCms.getRuntimeProperty("widgets.config.resolveMacros.disabled"))) {
266            return widgetConfig;
267        } else {
268            return resolver.resolveMacros(widgetConfig);
269        }
270    }
271
272}