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     *
147     * @return the widget information for the given path
148     */
149    public static WidgetInfo collectWidgetInfo(
150        CmsObject cms,
151        CmsXmlContentDefinition rootContentDefinition,
152        String path,
153        CmsMessages messages) {
154
155        String widgetConfig = null;
156        DisplayType configuredType = DisplayType.none;
157        I_CmsXmlSchemaType schemaType = rootContentDefinition.getSchemaType(path);
158
159        I_CmsWidget widget = null;
160        I_CmsComplexWidget complexWidget = null;
161        I_CmsXmlContentHandler contentHandler = schemaType.getContentDefinition().getContentHandler();
162        final List<I_CmsWidget> widgets = new ArrayList<>();
163        final List<String> widgetConfigs = new ArrayList<>();
164        final List<DisplayType> configuredDisplayTypes = new ArrayList<>();
165        final List<I_CmsComplexWidget> configuredComplexWidgets = new ArrayList<>();
166        if (messages == null) {
167            Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
168            CmsMultiMessages multi = new CmsMultiMessages(wpLocale);
169            multi.addMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale));
170            CmsMessages contentHandlerMessages = rootContentDefinition.getContentHandler().getMessages(wpLocale);
171            if (contentHandlerMessages != null) {
172                // Note: the default content handler class will always return a non-null messages object
173                multi.addMessages(contentHandlerMessages);
174            }
175            messages = multi;
176        }
177
178        // Use lists to store found widget configurations, and then use the first elements of each list.
179        // Because we iterate from the top level schema down to the nested schema, configurations in higher level schemas
180        // will have precedence over those in lower level schemas for the same element.
181
182        rootContentDefinition.findSchemaTypesForPath(path, (nestedType, remainingPath) -> {
183            remainingPath = CmsXmlUtils.concatXpath(nestedType.getName(), remainingPath);
184            I_CmsXmlContentHandler handler = nestedType.getContentDefinition().getContentHandler();
185            I_CmsWidget widgetForPath = handler.getWidget(cms, remainingPath);
186            CollectionUtils.addIgnoreNull(widgets, widgetForPath);
187            CollectionUtils.addIgnoreNull(widgetConfigs, handler.getConfiguration(remainingPath));
188            CollectionUtils.addIgnoreNull(
189                configuredDisplayTypes,
190                handler.getConfiguredDisplayType(remainingPath, null));
191            if (widgetForPath == null) {
192                // If we already have a normal widget, trying to find a complex widget for the same path is unnecessary,
193                // and would also cost performance (because of failing Class.forName calls in getComplexWidget).
194                CollectionUtils.addIgnoreNull(configuredComplexWidgets, handler.getComplexWidget(cms, remainingPath));
195            }
196
197        });
198        if (!widgets.isEmpty()) {
199            widget = widgets.get(0).newInstance();
200        } else {
201            widget = OpenCms.getXmlContentTypeManager().getWidgetDefault(schemaType.getTypeName());
202        }
203        if (!configuredDisplayTypes.isEmpty()) {
204            configuredType = configuredDisplayTypes.get(0);
205        }
206        if (!widgetConfigs.isEmpty()) {
207            widgetConfig = widgetConfigs.get(0);
208        } else if (widget != null) {
209            widgetConfig = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(widget);
210        }
211        CmsMacroResolver resolver = new CmsMacroResolver();
212        resolver.setCmsObject(cms);
213        resolver.setKeepEmptyMacros(false);
214        resolver.setMessages(messages);
215        if (widget != null) {
216            String resolvedConfig = resolveWidgetConfigMacros(resolver, widgetConfig);
217            widget.setConfiguration(resolvedConfig);
218        }
219        // default complex widget and default c. widget config have lower priorities than those directly defined, so put them at the end of the list
220        CollectionUtils.addIgnoreNull(configuredComplexWidgets, contentHandler.getDefaultComplexWidget());
221        List<String> complexWidgetConfigs = new ArrayList<>(widgetConfigs);
222        CollectionUtils.addIgnoreNull(complexWidgetConfigs, contentHandler.getDefaultComplexWidgetConfiguration());
223        if (!configuredComplexWidgets.isEmpty()) {
224            String config = "";
225            if (!complexWidgetConfigs.isEmpty()) {
226                config = complexWidgetConfigs.get(0);
227                config = resolveWidgetConfigMacros(resolver, config);
228            }
229            complexWidget = configuredComplexWidgets.get(0).configure(config);
230        }
231        WidgetInfo result = new WidgetInfo();
232        result.setComplexWidget(complexWidget);
233        result.setDisplayType(configuredType);
234        result.setWidget(widget);
235        return result;
236    }
237
238    /**
239     * Collects widget information for a given content value.<p>
240     *
241     * @param cms the current CMS context
242     * @param value a content value
243     *
244     * @return the widget information for the given value
245     */
246
247    public static WidgetInfo collectWidgetInfo(CmsObject cms, I_CmsXmlContentValue value) {
248
249        CmsXmlContentDefinition contentDef = value.getDocument().getContentDefinition();
250        String path = value.getPath();
251        return collectWidgetInfo(cms, contentDef, path, null);
252    }
253
254    /**
255     * 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.
256     *
257     * @param resolver the macro resolver
258     * @param widgetConfig the widget configuration
259     * @return the macro resolution result
260     */
261    private static String resolveWidgetConfigMacros(CmsMacroResolver resolver, String widgetConfig) {
262
263        if (Boolean.parseBoolean((String)OpenCms.getRuntimeProperty("widgets.config.resolveMacros.disabled"))) {
264            return widgetConfig;
265        } else {
266            return resolver.resolveMacros(widgetConfig);
267        }
268    }
269
270}