001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.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 complex widget. */
061        private I_CmsComplexWidget m_complexWidget;
062
063        /** The display type. */
064        private DisplayType m_displayType;
065
066        private String m_placeholder;
067
068        /** A widget instance. */
069        private I_CmsWidget m_widget;
070
071        /**
072         * Gets the complex widget.<p>
073         *
074         * @return the complex widget
075         */
076        public I_CmsComplexWidget getComplexWidget() {
077
078            return m_complexWidget;
079        }
080
081        /**
082         * Gets the display type.<p>
083         *
084         * @return the display type
085         */
086        public DisplayType getDisplayType() {
087
088            return m_displayType;
089        }
090
091        public String getPlaceholder() {
092
093            return m_placeholder;
094        }
095
096        /**
097         * Gets the widget instance.<p>
098         *
099         * @return the widget instance
100         */
101        public I_CmsWidget getWidget() {
102
103            return m_widget;
104        }
105
106        /**
107         * Sets the complex widget.<p>
108         *
109         * @param complexWidget the complex widget to set
110         */
111        public void setComplexWidget(I_CmsComplexWidget complexWidget) {
112
113            m_complexWidget = complexWidget;
114
115        }
116
117        /**
118         * Sets the display type.<p>
119         *
120         * @param displayType the display type
121         */
122        public void setDisplayType(DisplayType displayType) {
123
124            m_displayType = displayType;
125        }
126
127        public void setPlaceholder(String placeholder) {
128
129            m_placeholder = placeholder;
130        }
131
132        /**
133         * Sets the widget.<p>
134         *
135         * @param widget the widget
136         */
137        public void setWidget(I_CmsWidget widget) {
138
139            m_widget = widget;
140        }
141    }
142
143    /**
144     * Hidden default constructor.
145     */
146    private CmsWidgetUtil() {
147
148        // hidden default constructor
149    }
150
151    /**
152     * Collects widget information for a given content definition and content value path.<p>
153     *
154     * @param cms the the CMS context to use
155     * @param rootContentDefinition the content definition
156     * @param path the path relative to the given content definition
157     * @param messages the message bundle to use
158     * @param overrideLocale the explicit locale to use for resolving message keys (if null, use the workplace locale of the user)
159     *
160     * @return the widget information for the given path
161     */
162    public static WidgetInfo collectWidgetInfo(
163        CmsObject cms,
164        CmsXmlContentDefinition rootContentDefinition,
165        String path,
166        CmsMessages messages,
167        Locale overrideLocale) {
168
169        String widgetConfig = null;
170        DisplayType configuredType = DisplayType.none;
171        I_CmsXmlSchemaType schemaType = rootContentDefinition.getSchemaType(path);
172
173        I_CmsWidget widget = null;
174        I_CmsComplexWidget complexWidget = null;
175        I_CmsXmlContentHandler contentHandler = schemaType.getContentDefinition().getContentHandler();
176        final List<I_CmsWidget> widgets = new ArrayList<>();
177        final List<String> widgetConfigs = new ArrayList<>();
178        final List<String> placeholders = new ArrayList<>();
179        final List<DisplayType> configuredDisplayTypes = new ArrayList<>();
180        final List<I_CmsComplexWidget> configuredComplexWidgets = new ArrayList<>();
181        Locale locale = overrideLocale != null ? overrideLocale : OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
182        if (messages == null) {
183            CmsMultiMessages multi = new CmsMultiMessages(locale);
184            multi.addMessages(OpenCms.getWorkplaceManager().getMessages(locale));
185            CmsMessages contentHandlerMessages = rootContentDefinition.getContentHandler().getMessages(locale);
186            if (contentHandlerMessages != null) {
187                // Note: the default content handler class will always return a non-null messages object
188                multi.addMessages(contentHandlerMessages);
189            }
190            messages = multi;
191        }
192
193        // Use lists to store found widget configurations, and then use the first elements of each list.
194        // Because we iterate from the top level schema down to the nested schema, configurations in higher level schemas
195        // will have precedence over those in lower level schemas for the same element.
196
197        rootContentDefinition.findSchemaTypesForPath(path, (nestedType, remainingPath) -> {
198            remainingPath = CmsXmlUtils.concatXpath(nestedType.getName(), remainingPath);
199            I_CmsXmlContentHandler handler = nestedType.getContentDefinition().getContentHandler();
200            I_CmsWidget widgetForPath = handler.getWidget(cms, remainingPath);
201            CollectionUtils.addIgnoreNull(widgets, widgetForPath);
202            CollectionUtils.addIgnoreNull(widgetConfigs, handler.getConfiguration(remainingPath));
203            CollectionUtils.addIgnoreNull(placeholders, handler.getPlaceholder(remainingPath));
204            CollectionUtils.addIgnoreNull(
205                configuredDisplayTypes,
206                handler.getConfiguredDisplayType(remainingPath, null));
207            if (widgetForPath == null) {
208                // If we already have a normal widget, trying to find a complex widget for the same path is unnecessary,
209                // and would also cost performance (because of failing Class.forName calls in getComplexWidget).
210                CollectionUtils.addIgnoreNull(configuredComplexWidgets, handler.getComplexWidget(cms, remainingPath));
211            }
212
213        });
214        if (!widgets.isEmpty()) {
215            widget = widgets.get(0).newInstance();
216        } else {
217            widget = OpenCms.getXmlContentTypeManager().getWidgetDefault(schemaType.getTypeName());
218        }
219        if (!configuredDisplayTypes.isEmpty()) {
220            configuredType = configuredDisplayTypes.get(0);
221        }
222        if (!widgetConfigs.isEmpty()) {
223            widgetConfig = widgetConfigs.get(0);
224        } else if (widget != null) {
225            widgetConfig = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(widget);
226        }
227        CmsMacroResolver resolver = new CmsMacroResolver();
228        resolver.setCmsObject(cms);
229        resolver.setKeepEmptyMacros(false);
230        resolver.setMessages(messages);
231        if (widget != null) {
232            String resolvedConfig = resolveWidgetConfigMacros(resolver, widgetConfig);
233            widget.setConfiguration(resolvedConfig);
234        }
235        String placeholder = "";
236        if (placeholders.size() > 0) {
237            placeholder = placeholders.get(0);
238            placeholder = resolver.resolveMacros(placeholder);
239        }
240        // default complex widget and default c. widget config have lower priorities than those directly defined, so put them at the end of the list
241        CollectionUtils.addIgnoreNull(configuredComplexWidgets, contentHandler.getDefaultComplexWidget());
242        List<String> complexWidgetConfigs = new ArrayList<>(widgetConfigs);
243        CollectionUtils.addIgnoreNull(complexWidgetConfigs, contentHandler.getDefaultComplexWidgetConfiguration());
244        if (!configuredComplexWidgets.isEmpty()) {
245            String config = "";
246            if (!complexWidgetConfigs.isEmpty()) {
247                config = complexWidgetConfigs.get(0);
248                config = resolveWidgetConfigMacros(resolver, config);
249            }
250            complexWidget = configuredComplexWidgets.get(0).configure(config);
251        }
252        WidgetInfo result = new WidgetInfo();
253        result.setComplexWidget(complexWidget);
254        result.setDisplayType(configuredType);
255        result.setWidget(widget);
256        result.setPlaceholder(placeholder);
257        return result;
258    }
259
260    /**
261     * Collects widget information for a given content value.<p>
262     *
263     * @param cms the current CMS context
264     * @param value a content value
265     *
266     * @return the widget information for the given value
267     */
268
269    public static WidgetInfo collectWidgetInfo(CmsObject cms, I_CmsXmlContentValue value) {
270
271        CmsXmlContentDefinition contentDef = value.getDocument().getContentDefinition();
272        String path = value.getPath();
273        return collectWidgetInfo(cms, contentDef, path, null, null);
274    }
275
276    /**
277     * 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.
278     *
279     * @param resolver the macro resolver
280     * @param widgetConfig the widget configuration
281     * @return the macro resolution result
282     */
283    private static String resolveWidgetConfigMacros(CmsMacroResolver resolver, String widgetConfig) {
284
285        if (Boolean.parseBoolean((String)OpenCms.getRuntimeProperty("widgets.config.resolveMacros.disabled"))) {
286            return widgetConfig;
287        } else {
288            return resolver.resolveMacros(widgetConfig);
289        }
290    }
291
292}