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 GmbH & Co. KG, 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.ade.galleries.shared.I_CmsGalleryProviderConstants;
031import org.opencms.file.CmsObject;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.i18n.CmsMessages;
034import org.opencms.json.JSONArray;
035import org.opencms.json.JSONException;
036import org.opencms.json.JSONObject;
037import org.opencms.loader.CmsImageScaler;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.util.CmsMacroResolver;
041import org.opencms.util.CmsStringUtil;
042import org.opencms.workplace.CmsDialog;
043import org.opencms.workplace.CmsWorkplace;
044import org.opencms.xml.types.CmsXmlVfsImageValue;
045
046import java.util.List;
047import java.util.Map;
048
049import org.apache.commons.logging.Log;
050
051/**
052 * Provides a widget for an extended image selection using the advanced gallery dialog.<p>
053 *
054 * @since 7.5.0
055 */
056public class CmsVfsImageWidget extends CmsAdeImageGalleryWidget {
057
058    /** The static log object for this class. */
059    private static final Log LOG = CmsLog.getLog(CmsVfsImageWidget.class);
060
061    /** Input field prefix for the description field. */
062    private static final String PREFIX_DESCRIPTION = "desc.";
063
064    /** Input field prefix for the format field. */
065    private static final String PREFIX_FORMAT = "format.";
066
067    /** Input field prefix for the hidden format value field. */
068    private static final String PREFIX_FORMATVALUE = "fmtval.";
069
070    /** Input field prefix for the image field. */
071    private static final String PREFIX_IMAGE = "img.";
072
073    /** Input field prefix for the image ratio field. */
074    private static final String PREFIX_IMAGERATIO = "imgrat.";
075
076    /** Input field prefix for the hidden scale field. */
077    private static final String PREFIX_SCALE = "scale.";
078
079    /**
080     * Creates a new image widget.<p>
081     */
082    public CmsVfsImageWidget() {
083
084        // empty constructor is required for class registration
085        super();
086    }
087
088    /**
089     * Creates an image widget with the specified configuration options.<p>
090     *
091     * @param configuration the configuration (possible options) for the image widget
092     */
093    public CmsVfsImageWidget(String configuration) {
094
095        super(configuration);
096    }
097
098    /**
099     * @see org.opencms.widgets.I_CmsWidget#getDialogIncludes(org.opencms.file.CmsObject,org.opencms.widgets.I_CmsWidgetDialog)
100     */
101    @Override
102    public String getDialogIncludes(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
103
104        StringBuffer result = new StringBuffer(256);
105        // import the JavaScript for the image widget
106        result.append(getJSIncludeFile(CmsWorkplace.getSkinUri() + "components/widgets/vfsimage.js"));
107        return result.toString();
108    }
109
110    /**
111     * @see org.opencms.widgets.I_CmsWidget#getDialogWidget(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
112     */
113    @Override
114    public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
115
116        String id = param.getId();
117        long idHash = id.hashCode();
118        if (idHash < 0) {
119            // negative hash codes will not work as JS variable names, so convert them
120            idHash = -idHash;
121            // add 2^32 to the value to ensure that it is unique
122            idHash += 4294967296L;
123        }
124        // cast parameter to xml value to access the specific methods
125        CmsXmlVfsImageValue value = (CmsXmlVfsImageValue)param;
126        String imageLink = value.getRequestLink(cms);
127        if (imageLink == null) {
128            imageLink = "";
129        }
130
131        StringBuffer result = new StringBuffer(4096);
132
133        result.append("<td class=\"xmlTd\" style=\"height: 25px;\">");
134
135        result.append("<table class=\"xmlTableNested\">");
136        result.append("<tr>");
137        result.append("<td class=\"xmlLabel\">");
138        result.append(widgetDialog.getMessages().key(Messages.GUI_EDITOR_LABEL_IMAGE_PATH_0));
139        result.append(" </td>");
140        result.append("<td>");
141        result.append("<input class=\"xmlInputMedium\" value=\"").append(imageLink).append("\" name=\"");
142        result.append(PREFIX_IMAGE).append(id).append("\" id=\"");
143        result.append(PREFIX_IMAGE).append(id);
144        result.append("\" onkeyup=\"checkVfsImagePreview('");
145        result.append(id);
146        result.append("');\" />");
147        result.append("</td>");
148
149        result.append(widgetDialog.dialogHorizontalSpacer(10));
150        result.append(
151            "<td><table class=\"editorbuttonbackground\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr>");
152
153        result.append(
154            widgetDialog.button(
155                getOpenGalleryCall(cms, widgetDialog, param, idHash),
156                null,
157                getGalleryName() + "gallery",
158                Messages.getButtonName(getGalleryName()),
159                widgetDialog.getButtonStyle()));
160
161        // create preview button
162        String previewClass = "hide";
163        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(imageLink)) {
164            // show button if preview is enabled
165            previewClass = "show";
166        }
167        result.append("<td class=\"");
168        result.append(previewClass);
169        result.append("\" id=\"preview");
170        result.append(id);
171        result.append("\">");
172        result.append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr>");
173        result.append(
174            widgetDialog.button(
175                getOpenPreviewCall(widgetDialog, PREFIX_IMAGE + param.getId()),
176                null,
177                "preview.png",
178                Messages.GUI_BUTTON_PREVIEW_0,
179                widgetDialog.getButtonStyle()));
180        result.append("</tr></table></td>");
181
182        result.append("</tr></table></td>");
183        result.append("</tr>");
184
185        JSONObject additional = null;
186        try {
187            additional = getAdditionalGalleryInfo(
188                cms,
189                widgetDialog instanceof CmsDialog ? ((CmsDialog)widgetDialog).getParamResource() : null,
190                widgetDialog.getMessages(),
191                param);
192        } catch (JSONException e) {
193            LOG.error("Error parsing widget configuration", e);
194        }
195        if (additional != null) {
196            result.append("\n<script >\n");
197            result.append("var cms_additional_").append(idHash).append("=");
198            result.append(additional.toString()).append(";\n");
199            result.append("</script>");
200        }
201        CmsVfsImageWidgetConfiguration configuration = getWidgetConfiguration(cms, widgetDialog.getMessages(), param);
202        String format = value.getFormat(cms);
203        if (configuration.isShowFormat()) {
204            // show the format select box, also create hidden format value field
205            result.append("<tr>");
206            result.append("<td class=\"xmlLabel\">");
207            result.append(widgetDialog.getMessages().key(Messages.GUI_EDITOR_LABEL_IMAGE_FORMAT_0));
208            result.append(" </td>");
209            result.append("<td class=\"xmlTd\">");
210            result.append("<select class=\"xmlInput");
211            if (param.hasError()) {
212                result.append(" xmlInputError");
213            }
214            result.append("\" name=\"");
215            result.append(PREFIX_FORMAT).append(id);
216            result.append("\" id=\"");
217            result.append(PREFIX_FORMAT).append(id);
218            result.append("\"");
219            result.append(" onchange=\"setImageFormat(\'");
220            result.append(id);
221            result.append("\', \'imgFmts");
222            result.append(idHash);
223            result.append("\');\"");
224            result.append(">");
225
226            // get select box options from default value String
227            List<CmsSelectWidgetOption> options = configuration.getSelectFormat();
228            String selected = getSelectedValue(cms, options, format);
229            int selectedIndex = 0;
230            for (int i = 0; i < options.size(); i++) {
231                CmsSelectWidgetOption option = options.get(i);
232                // create the option
233                result.append("<option value=\"");
234                result.append(option.getValue());
235                result.append("\"");
236                if ((selected != null) && selected.equals(option.getValue())) {
237                    result.append(" selected=\"selected\"");
238                    selectedIndex = i;
239                }
240                result.append(">");
241                result.append(option.getOption());
242                result.append("</option>");
243            }
244            result.append("</select>");
245            result.append("</td>");
246            result.append("</tr>");
247            List<String> formatValues = configuration.getFormatValues();
248            String selectedFormat = "";
249            try {
250                selectedFormat = formatValues.get(selectedIndex);
251            } catch (Exception e) {
252                // ignore, just didn't find a matching format value
253            }
254            // create hidden field to store the matching image format value
255            result.append("<input type=\"hidden\" value=\"").append(selectedFormat).append("\" name=\"");
256            result.append(PREFIX_FORMATVALUE).append(id).append("\" id=\"");
257            result.append(PREFIX_FORMATVALUE).append(id).append("\" />");
258            // create hidden field to store image ratio
259            String ratio = "";
260            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(imageLink)) {
261                // an image is specified, calculate ratio
262                try {
263                    CmsImageScaler scaler = new CmsImageScaler(cms, cms.readResource(imageLink));
264                    float r = scaler.getWidth() / (float)scaler.getHeight();
265                    ratio = String.valueOf(r);
266                } catch (CmsException e) {
267                    // ignore, image not found in VFS
268                }
269            }
270            result.append("<input type=\"hidden\" value=\"").append(ratio).append("\" name=\"");
271            result.append(PREFIX_IMAGERATIO).append(id).append("\" id=\"");
272            result.append(PREFIX_IMAGERATIO).append(id).append("\" />");
273            // add possible format names and values as JS variables to access them from image gallery window
274            result.append("\n<script >");
275            JSONArray formatsJson = new JSONArray(configuration.getFormatValues());
276            result.append("\nvar imgFmts").append(idHash).append(" = ").append(formatsJson).append(";");
277            result.append("\nvar imgFmtNames").append(idHash).append(" = \"").append(
278                CmsEncoder.escape(configuration.getSelectFormatString(), CmsEncoder.ENCODING_UTF_8)).append("\";");
279            result.append("\nvar useFmts").append(idHash).append(" = true;");
280            result.append("\n</script>");
281        } else {
282            result.append("<input type=\"hidden\" value=\"\" name=\"");
283            result.append(PREFIX_IMAGERATIO).append(id).append("\" id=\"");
284            result.append(PREFIX_IMAGERATIO).append(id).append("\" />");
285            result.append("<input type=\"hidden\" value=\"").append(format).append("\" name=\"");
286            result.append(PREFIX_FORMAT).append(id).append("\" id=\"");
287            result.append(PREFIX_FORMAT).append(id).append("\" />");
288            result.append("\n<script >");
289            result.append("\nvar useFmts").append(idHash).append(" = false;");
290            result.append("\n</script>");
291        }
292
293        String description = value.getDescription(cms);
294        if (description == null) {
295            description = "";
296        }
297
298        if (configuration.isShowDescription()) {
299            result.append("<tr>");
300            result.append("<td class=\"xmlLabel\">");
301            result.append(widgetDialog.getMessages().key(Messages.GUI_EDITOR_LABEL_IMAGE_DESC_0));
302            result.append("</td>");
303            result.append("<td class=\"xmlTd\">");
304            result.append("<textarea class=\"xmlInput maxwidth");
305            if (param.hasError()) {
306                result.append(" xmlInputError");
307            }
308            result.append("\" name=\"");
309            result.append(PREFIX_DESCRIPTION).append(id).append("\" id=\"");
310            result.append(PREFIX_DESCRIPTION).append(id);
311            result.append("\" rows=\"");
312            result.append(2);
313            result.append("\" cols=\"60\" style=\"height: 3em; overflow:auto;\">");
314            result.append(CmsEncoder.escapeXml(description));
315            result.append("</textarea>");
316            result.append("</td>");
317            result.append("</tr>");
318        } else {
319            result.append("<input type=\"hidden\" value=\"").append(CmsEncoder.escapeXml(description)).append(
320                "\" name=\"");
321            result.append(PREFIX_DESCRIPTION).append(id).append("\" id=\"");
322            result.append(PREFIX_DESCRIPTION).append(id).append("\" />");
323
324        }
325        result.append("</table>");
326
327        String scale = value.getScaleOptions(cms);
328        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(configuration.getScaleParams())
329            && (scale.indexOf(configuration.getScaleParams()) == -1)) {
330            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(scale)) {
331                scale += ",";
332            }
333            scale += configuration.getScaleParams();
334
335        }
336        result.append("<input type=\"hidden\" value=\"").append(scale).append("\" name=\"");
337        result.append(PREFIX_SCALE).append(id).append("\" id=\"");
338        result.append(PREFIX_SCALE).append(id).append("\" />");
339
340        result.append("</td>");
341
342        return result.toString();
343    }
344
345    /**
346     * @see org.opencms.widgets.I_CmsADEWidget#getWidgetName()
347     */
348    @Override
349    public String getWidgetName() {
350
351        return CmsVfsImageWidget.class.getName();
352    }
353
354    /**
355     * @see org.opencms.widgets.A_CmsWidget#getWidgetStringValue(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
356     */
357    @Override
358    public String getWidgetStringValue(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
359
360        String result = super.getWidgetStringValue(cms, widgetDialog, param);
361        String configuration = CmsMacroResolver.resolveMacros(getConfiguration(), cms, widgetDialog.getMessages());
362        if (configuration == null) {
363            configuration = param.getDefault(cms);
364        }
365        List<CmsSelectWidgetOption> options = CmsSelectWidgetOption.parseOptions(configuration);
366        for (int m = 0; m < options.size(); m++) {
367            CmsSelectWidgetOption option = options.get(m);
368            if (result.equals(option.getValue())) {
369                result = option.getOption();
370                break;
371            }
372        }
373        return result;
374    }
375
376    /**
377     * @see org.opencms.widgets.I_CmsWidget#newInstance()
378     */
379    @Override
380    public I_CmsWidget newInstance() {
381
382        return new CmsVfsImageWidget(getConfiguration());
383    }
384
385    /**
386     * @see org.opencms.widgets.I_CmsWidget#setEditorValue(org.opencms.file.CmsObject, java.util.Map, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
387     */
388    @Override
389    public void setEditorValue(
390        CmsObject cms,
391        Map<String, String[]> formParameters,
392        I_CmsWidgetDialog widgetDialog,
393        I_CmsWidgetParameter param) {
394
395        String[] imgValues = formParameters.get(PREFIX_IMAGE + param.getId());
396        if ((imgValues != null) && (imgValues.length > 0)) {
397            param.setStringValue(cms, imgValues[0]);
398        }
399
400        CmsXmlVfsImageValue value = (CmsXmlVfsImageValue)param;
401
402        String[] descValues = formParameters.get(PREFIX_DESCRIPTION + param.getId());
403        value.setDescription(cms, descValues[0]);
404
405        String[] formatValues = formParameters.get(PREFIX_FORMAT + param.getId());
406        value.setFormat(cms, formatValues[0]);
407
408        String[] scaleValues = formParameters.get(PREFIX_SCALE + param.getId());
409        value.setScaleOptions(cms, scaleValues[0]);
410    }
411
412    /**
413     * @see org.opencms.widgets.CmsAdeImageGalleryWidget#getAdditionalGalleryInfo(org.opencms.file.CmsObject, java.lang.String, org.opencms.i18n.CmsMessages, org.opencms.widgets.I_CmsWidgetParameter)
414     */
415    @Override
416    protected JSONObject getAdditionalGalleryInfo(
417        CmsObject cms,
418        String resource,
419        CmsMessages messages,
420        I_CmsWidgetParameter param) throws JSONException {
421
422        JSONObject result = super.getAdditionalGalleryInfo(cms, resource, messages, param);
423        result.put("isAdvancedWidget", true);
424        return result;
425    }
426
427    /**
428     * @see org.opencms.widgets.A_CmsAdeGalleryWidget#getGalleryOpenParams(org.opencms.file.CmsObject, org.opencms.i18n.CmsMessages, org.opencms.widgets.I_CmsWidgetParameter, java.lang.String, long)
429     */
430    @Override
431    protected Map<String, String> getGalleryOpenParams(
432        CmsObject cms,
433        CmsMessages widgetDialog,
434        I_CmsWidgetParameter param,
435        String resource,
436        long hashId) {
437
438        Map<String, String> result = super.getGalleryOpenParams(cms, widgetDialog, param, resource, hashId);
439        // the current element value will be read by java-script including the image input field and the scale input field
440        StringBuffer currentElement = new StringBuffer("'+document.getElementById('");
441        if (param != null) {
442            currentElement.append(PREFIX_IMAGE).append(param.getId());
443        }
444        currentElement.append("').getAttribute('value')+'");
445        // only try reading scale and format info if formats are used
446        if (param != null) {
447            if (getWidgetConfiguration(cms, widgetDialog, param).isShowFormat()) {
448                currentElement.append("%3F__scale%3D'+document.getElementById('");
449                currentElement.append(PREFIX_SCALE).append(param.getId()).append("').getAttribute('value')+'");
450                currentElement.append("%26format%3D'+escape(document.getElementById('").append(PREFIX_FORMAT).append(
451                    param.getId()).append("')[document.getElementById('").append(PREFIX_FORMAT).append(
452                        param.getId()).append("').selectedIndex].value)+'");
453            }
454        }
455        result.put(I_CmsGalleryProviderConstants.CONFIG_CURRENT_ELEMENT, currentElement.toString());
456        return result;
457    }
458
459    /**
460     * Returns the currently selected value of the select widget.<p>
461     *
462     * If a value is found in the given parameter, this is used. Otherwise
463     * the default value of the select options are used. If there is neither a parameter value
464     * nor a default value, <code>null</code> is returned.<p>
465     *
466     * @param cms the current users OpenCms context
467     * @param selectOptions the available select options
468     * @param currentValue the current value that is selected
469     *
470     * @return the currently selected value of the select widget
471     */
472    protected String getSelectedValue(CmsObject cms, List<CmsSelectWidgetOption> selectOptions, String currentValue) {
473
474        String paramValue = currentValue;
475        if (CmsStringUtil.isEmpty(paramValue)) {
476            CmsSelectWidgetOption option = CmsSelectWidgetOption.getDefaultOption(selectOptions);
477            if (option != null) {
478                paramValue = option.getValue();
479            }
480        }
481        return paramValue;
482    }
483}