001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (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 GmbH, 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.widgets;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.i18n.CmsLocaleManager;
034import org.opencms.i18n.CmsMessages;
035import org.opencms.util.CmsMacroResolver;
036import org.opencms.util.CmsStringUtil;
037import org.opencms.util.I_CmsMacroResolver;
038import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
039import org.opencms.xml.types.A_CmsXmlContentValue;
040import org.opencms.xml.types.I_CmsXmlContentValue;
041
042import java.util.Iterator;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.regex.Matcher;
047import java.util.regex.Pattern;
048
049/**
050 * Provides a standard HTML form input widget for overwriting localized values of a resource bundle, for use on a widget dialog.<p>
051 *
052 * The resource bundle is configured with the widget configuration attribute. An optional key name to look up in the bundle
053 * can be given, too, in case it is different from the element name: <code>key=mykey</code>.<p>
054 * 
055 * The locale to get the value for can be configured, too, by adding a configuration directive: <code>locale=en</code>.<p>
056 * 
057 * Example: <code><layout element="elemname" widget="LocalizationWidget" configuration="org.opencms.workplace.messages|key=mykey|locale=en" /></code>.<p>
058 *
059 * To use the stored localization values and have the values of the resource bundles as fallback,
060 * use the {@link org.opencms.xml.CmsXmlMessages} object.<p>
061 * 
062 * @since 6.5.4
063 */
064public class CmsLocalizationWidget extends A_CmsWidget implements I_CmsADEWidget {
065
066    /** The option for the localized key name. */
067    public static final String OPTION_KEY = "key=";
068
069    /** The option for the locale to use.  */
070    public static final String OPTION_LOCALE = "locale=";
071
072    /** Pattern to get OpenCms like macros, e.g. "%(0)". */
073    private static Pattern PATTERN_MACRO = Pattern.compile(".*("
074        + I_CmsMacroResolver.MACRO_DELIMITER
075        + "\\"
076        + I_CmsMacroResolver.MACRO_START
077        + ")(\\d*)(\\"
078        + I_CmsMacroResolver.MACRO_END
079        + ").*");
080
081    /** Pattern to get message bundle arguments, e.g. "{0}". */
082    private static Pattern PATTERN_MESSAGEARGUMENT = Pattern.compile(".*(\\{)(\\d*)(\\}).*");
083
084    /** The bundle key (optional, if not equal to the element name). */
085    private String m_bundleKey;
086
087    /** The locale to get the value for. */
088    private Locale m_locale;
089
090    /** The localized bundle to get the value from. */
091    private CmsMessages m_messages;
092
093    /**
094     * Creates a new input localization widget.<p>
095     */
096    public CmsLocalizationWidget() {
097
098        // empty constructor is required for class registration
099        this("");
100    }
101
102    /**
103     * Creates a new input localization widget with the given configuration.<p>
104     * 
105     * @param configuration the configuration to use
106     */
107    public CmsLocalizationWidget(String configuration) {
108
109        super(configuration);
110    }
111
112    /**
113     * @see org.opencms.widgets.I_CmsADEWidget#getConfiguration(org.opencms.file.CmsObject, org.opencms.xml.types.A_CmsXmlContentValue, org.opencms.i18n.CmsMessages, org.opencms.file.CmsResource, java.util.Locale)
114     */
115    public String getConfiguration(
116        CmsObject cms,
117        A_CmsXmlContentValue schemaType,
118        CmsMessages messages,
119        CmsResource resource,
120        Locale contentLocale) {
121
122        initConfiguration(cms, schemaType);
123        return m_messages.key(m_bundleKey);
124    }
125
126    /**
127     * @see org.opencms.widgets.I_CmsADEWidget#getCssResourceLinks(org.opencms.file.CmsObject)
128     */
129    public List<String> getCssResourceLinks(CmsObject cms) {
130
131        return null;
132    }
133
134    /**
135     * @see org.opencms.widgets.I_CmsADEWidget#getDefaultDisplayType()
136     */
137    public DisplayType getDefaultDisplayType() {
138
139        return DisplayType.wide;
140    }
141
142    /**
143     * @see org.opencms.widgets.I_CmsWidget#getDialogWidget(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
144     */
145    public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
146
147        String id = param.getId();
148        // initialize bundle
149        initConfiguration(cms, param);
150
151        StringBuffer result = new StringBuffer(256);
152
153        result.append("<td class=\"xmlTd\">");
154        result.append("<input class=\"xmlInput textInput");
155        if (param.hasError()) {
156            result.append(" xmlInputError");
157        }
158        result.append("\"");
159        result.append(" name=\"");
160        result.append(id);
161        result.append("\" id=\"");
162        result.append(id);
163        result.append("\" value=\"");
164
165        // determine value to show in editor
166        String value = getValue(cms, param);
167        result.append(CmsEncoder.escapeXml(value));
168        result.append("\">");
169        result.append("</td>");
170
171        return result.toString();
172    }
173
174    /**
175     * @see org.opencms.widgets.I_CmsADEWidget#getInitCall()
176     */
177    public String getInitCall() {
178
179        return null;
180    }
181
182    /**
183     * @see org.opencms.widgets.I_CmsADEWidget#getJavaScriptResourceLinks(org.opencms.file.CmsObject)
184     */
185    public List<String> getJavaScriptResourceLinks(CmsObject cms) {
186
187        return null;
188    }
189
190    /**
191     * @see org.opencms.widgets.I_CmsADEWidget#getWidgetName()
192     */
193    public String getWidgetName() {
194
195        return CmsLocalizationWidget.class.getName();
196    }
197
198    /**
199     * @see org.opencms.widgets.I_CmsADEWidget#isInternal()
200     */
201    public boolean isInternal() {
202
203        return true;
204    }
205
206    /**
207     * @see org.opencms.widgets.I_CmsWidget#newInstance()
208     */
209    public I_CmsWidget newInstance() {
210
211        return new CmsLocalizationWidget(getConfiguration());
212    }
213
214    /**
215     * @see org.opencms.widgets.I_CmsWidget#setEditorValue(org.opencms.file.CmsObject, java.util.Map, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
216     */
217    @Override
218    public void setEditorValue(
219        CmsObject cms,
220        Map<String, String[]> formParameters,
221        I_CmsWidgetDialog widgetDialog,
222        I_CmsWidgetParameter param) {
223
224        String[] values = formParameters.get(param.getId());
225        if ((values != null) && (values.length > 0)) {
226            // initialize bundle
227            initConfiguration(cms, param);
228            String value = m_messages.key(m_bundleKey);
229            if (value.equals(values[0].trim())) {
230                // value is equal to localized value, do not save
231                value = "";
232            } else {
233                // value is different, save it
234                value = values[0];
235                // now replace message bundle like argument placeholders like "{0}" with OpenCms macros
236                Matcher matcher = PATTERN_MESSAGEARGUMENT.matcher(value);
237                while (matcher.matches()) {
238                    int startIndex = matcher.start(1);
239                    int endIndex = matcher.end(3);
240                    String number = CmsMacroResolver.formatMacro(matcher.group(2));
241                    // replace arguments with macros
242                    value = value.substring(0, startIndex) + number + value.substring(endIndex);
243                    matcher = PATTERN_MESSAGEARGUMENT.matcher(value);
244                }
245            }
246            param.setStringValue(cms, value);
247        }
248    }
249
250    /**
251     * Initializes the localized bundle to get the value from, the optional key name and the optional locale.<p>
252     * 
253     * @param cms an initialized instance of a CmsObject
254     * @param schemaType the widget parameter to generate the widget for
255     */
256    protected void initConfiguration(CmsObject cms, A_CmsXmlContentValue schemaType) {
257
258        // set the default bundle key
259        m_bundleKey = schemaType.getName();
260        // set the default locale for XML contents
261        m_locale = cms.getRequestContext().getLocale();
262        try {
263            I_CmsXmlContentValue value = schemaType;
264            m_locale = value.getLocale();
265        } catch (Exception e) {
266            // ignore, this is no XML content
267        }
268
269        // check the message bundle
270        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getConfiguration())) {
271            //initialize messages, the optional bundle key name and the optional locale from configuration String
272            String bundleName = "";
273            List<String> configs = CmsStringUtil.splitAsList(getConfiguration(), '|');
274            Iterator<String> i = configs.iterator();
275            while (i.hasNext()) {
276                String config = i.next();
277                if (config.startsWith(OPTION_KEY)) {
278                    m_bundleKey = config.substring(OPTION_KEY.length());
279                } else if (config.startsWith(OPTION_LOCALE)) {
280                    m_locale = CmsLocaleManager.getLocale(config.substring(OPTION_LOCALE.length()));
281                } else {
282                    bundleName = config;
283                }
284            }
285            // create messages object
286            m_messages = new CmsMessages(bundleName, m_locale);
287        } else {
288            // initialize empty messages object to avoid NPE
289            m_messages = new CmsMessages("", m_locale);
290        }
291    }
292
293    /**
294     * Initializes the localized bundle to get the value from, the optional key name and the optional locale.<p>
295     * 
296     * @param cms an initialized instance of a CmsObject
297     * @param param the widget parameter to generate the widget for
298     */
299    protected void initConfiguration(CmsObject cms, I_CmsWidgetParameter param) {
300
301        // set the default bundle key
302        m_bundleKey = param.getName();
303        // set the default locale for XML contents
304        m_locale = cms.getRequestContext().getLocale();
305        try {
306            I_CmsXmlContentValue value = (I_CmsXmlContentValue)param;
307            m_locale = value.getLocale();
308        } catch (Exception e) {
309            // ignore, this is no XML content
310        }
311
312        // check the message bundle
313        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getConfiguration())) {
314            //initialize messages, the optional bundle key name and the optional locale from configuration String
315            String bundleName = "";
316            List<String> configs = CmsStringUtil.splitAsList(getConfiguration(), '|');
317            Iterator<String> i = configs.iterator();
318            while (i.hasNext()) {
319                String config = i.next();
320                if (config.startsWith(OPTION_KEY)) {
321                    m_bundleKey = config.substring(OPTION_KEY.length());
322                } else if (config.startsWith(OPTION_LOCALE)) {
323                    m_locale = CmsLocaleManager.getLocale(config.substring(OPTION_LOCALE.length()));
324                } else {
325                    bundleName = config;
326                }
327            }
328            // create messages object
329            m_messages = new CmsMessages(bundleName, m_locale);
330        } else {
331            // initialize empty messages object to avoid NPE
332            m_messages = new CmsMessages("", m_locale);
333        }
334    }
335
336    /**
337     * Determine value to show in editor.<p>
338     * @param cms an initialized instance of a CmsObject
339     * @param param the widget parameter to generate the widget for
340     * 
341     * @return value to show in editor
342     */
343    private String getValue(CmsObject cms, I_CmsWidgetParameter param) {
344
345        String value = m_messages.key(m_bundleKey);
346        if ((CmsStringUtil.isNotEmptyOrWhitespaceOnly(param.getStringValue(cms)) && !value.equals(param.getStringValue(cms)))
347            || value.startsWith(CmsMessages.UNKNOWN_KEY_EXTENSION)) {
348            // saved value is provided and different from localized value in bundle or no value found in bundle, use it
349            value = param.getStringValue(cms);
350            // replace OpenCms macro syntax with message bundle arguments
351            Matcher matcher = PATTERN_MACRO.matcher(value);
352            while (matcher.matches()) {
353                int startIndex = matcher.start(1);
354                int endIndex = matcher.end(3);
355                String number = matcher.group(2);
356                value = value.substring(0, startIndex) + "{" + number + "}" + value.substring(endIndex);
357                matcher = PATTERN_MACRO.matcher(value);
358            }
359
360        }
361        return value;
362    }
363
364}