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.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.i18n.CmsMessages;
033import org.opencms.json.JSONException;
034import org.opencms.json.JSONObject;
035import org.opencms.main.CmsLog;
036import org.opencms.util.CmsMacroResolver;
037import org.opencms.util.CmsStringUtil;
038import org.opencms.workplace.CmsWorkplace;
039import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
040import org.opencms.xml.types.A_CmsXmlContentValue;
041
042import java.text.DateFormat;
043import java.text.ParseException;
044import java.text.SimpleDateFormat;
045import java.util.GregorianCalendar;
046import java.util.List;
047import java.util.Locale;
048import java.util.Map;
049import java.util.TimeZone;
050
051import org.apache.commons.logging.Log;
052
053/**
054 * Provides a DHTML calendar widget, for use on a widget dialog.<p>
055 *
056 * @since 6.0.0
057 */
058public class CmsCalendarWidget extends A_CmsWidget implements I_CmsADEWidget {
059
060    /** The log object for this class. */
061    private static final Log LOG = CmsLog.getLog(CmsCalendarWidget.class);
062
063    /**
064     * Creates a new calendar widget.<p>
065     */
066    public CmsCalendarWidget() {
067
068        // empty constructor is required for class registration
069        this("");
070    }
071
072    /**
073     * Creates a new calendar widget with the given configuration.<p>
074     *
075     * @param configuration the configuration to use
076     */
077    public CmsCalendarWidget(String configuration) {
078
079        super(configuration);
080    }
081
082    /**
083     * Creates the HTML JavaScript and stylesheet includes required by the calendar for the head of the page.<p>
084     *
085     * The default <code>"opencms"</code> style is used.<p>
086     *
087     * @param locale the locale to use for the calendar
088     *
089     * @return the necessary HTML code for the js and stylesheet includes
090     *
091     * @see #calendarIncludes(Locale, String)
092     */
093    public static String calendarIncludes(Locale locale) {
094
095        return calendarIncludes(locale, "opencms");
096    }
097
098    /**
099     * Creates the HTML JavaScript and stylesheet includes required by the calendar for the head of the page.<p>
100     *
101     * @param locale the locale to use for the calendar
102     * @param style the name of the used calendar style, e.g. "system", "blue"
103     *
104     * @return the necessary HTML code for the js and stylesheet includes
105     */
106    public static String calendarIncludes(Locale locale, String style) {
107
108        StringBuffer result = new StringBuffer(512);
109        String calendarPath = CmsWorkplace.getSkinUri() + "components/js_calendar/";
110        if (CmsStringUtil.isEmpty(style)) {
111            style = "system";
112        }
113        result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
114        result.append(calendarPath);
115        result.append("calendar-");
116        result.append(style);
117        result.append(".css\">\n");
118        result.append("<script  src=\"");
119        result.append(calendarPath);
120        result.append("calendar.js\"></script>\n");
121        result.append("<script  src=\"");
122        result.append(calendarPath);
123        result.append("lang/calendar-");
124        result.append(getLanguageSuffix(locale.getLanguage()));
125        result.append(".js\"></script>\n");
126        result.append("<script  src=\"");
127        result.append(calendarPath);
128        result.append("calendar-setup.js\"></script>\n");
129        return result.toString();
130    }
131
132    /**
133     * Generates the HTML to initialize the JavaScript calendar element on the end of a page.<p>
134     *
135     * This method must be called at the end of a HTML page, e.g. before the closing &lt;body&gt; tag.<p>
136     *
137     * @param messages the messages to use (for date and time formats)
138     * @param inputFieldId the ID of the input field where the date is pasted to
139     * @param triggerButtonId the ID of the button which triggers the calendar
140     * @param align initial position of the calendar popup element
141     * @param singleClick if true, a single click selects a date and closes the calendar, otherwise calendar is closed by doubleclick
142     * @param weekNumbers show the week numbers in the calendar or not
143     * @param mondayFirst show monday as first day of week
144     * @param dateStatusFunc name of the function which determines if/how a date should be disabled
145     * @param showTime true if the time selector should be shown, otherwise false
146     * @return the HTML code to initialize a calendar poup element
147     */
148    public static String calendarInit(
149        CmsMessages messages,
150        String inputFieldId,
151        String triggerButtonId,
152        String align,
153        boolean singleClick,
154        boolean weekNumbers,
155        boolean mondayFirst,
156        String dateStatusFunc,
157        boolean showTime) {
158
159        StringBuffer result = new StringBuffer(512);
160        if (CmsStringUtil.isEmpty(align)) {
161            align = "Bc";
162        }
163        result.append("<script >\n");
164        result.append("<!--\n");
165        result.append("\tCalendar.setup({\n");
166        result.append("\t\tinputField     :    \"");
167        result.append(inputFieldId);
168        result.append("\",\n");
169        result.append("\t\tifFormat       :    \"");
170        result.append(messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_DATE_FORMAT_0));
171        if (showTime) {
172            result.append(" ");
173            result.append(messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_TIME_FORMAT_0));
174        }
175        result.append("\",\n");
176        result.append("\t\tbutton         :    \"");
177        result.append(triggerButtonId);
178        result.append("\",\n");
179        result.append("\t\talign          :    \"");
180        result.append(align);
181        result.append("\",\n");
182        result.append("\t\tsingleClick    :    ");
183        result.append(singleClick);
184        result.append(",\n");
185        result.append("\t\tweekNumbers    :    ");
186        result.append(weekNumbers);
187        result.append(",\n");
188        result.append("\t\tmondayFirst    :    ");
189        result.append(mondayFirst);
190        result.append(",\n");
191        result.append("\t\tshowsTime      :    " + showTime);
192        if (showTime
193            && (messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_TIMEFORMAT_0).toLowerCase().indexOf(
194                "p") != -1)) {
195            result.append(",\n\t\ttimeFormat     :    \"12\"");
196        }
197        if (CmsStringUtil.isNotEmpty(dateStatusFunc)) {
198            result.append(",\n\t\tdateStatusFunc :    ");
199            result.append(dateStatusFunc);
200        }
201        result.append("\n\t});\n");
202
203        result.append("//-->\n");
204        result.append("</script>\n");
205        return result.toString();
206    }
207
208    /**
209     * Creates the time in milliseconds from the given parameter.<p>
210     *
211     * @param messages the messages that contain the time format definitions
212     * @param dateString the String representation of the date
213     * @param useTime true if the time should be parsed, too, otherwise false
214     *
215     * @return the time in milliseconds
216     *
217     * @throws ParseException if something goes wrong
218     */
219    public static long getCalendarDate(CmsMessages messages, String dateString, boolean useTime) throws ParseException {
220
221        long dateLong = 0;
222
223        // substitute some chars because calendar syntax != DateFormat syntax
224        String dateFormat = messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_DATE_FORMAT_0);
225        if (useTime) {
226            dateFormat += " " + messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_TIME_FORMAT_0);
227        }
228        dateFormat = CmsCalendarWidget.getCalendarJavaDateFormat(dateFormat);
229
230        SimpleDateFormat df = new SimpleDateFormat(dateFormat);
231        dateLong = df.parse(dateString).getTime();
232        return dateLong;
233    }
234
235    /**
236     * Parses the JavaScript calendar date format to the java patterns of SimpleDateFormat.<p>
237     *
238     * @param dateFormat the dateformat String of the JS calendar
239     * @return the parsed SimpleDateFormat pattern String
240     */
241    public static String getCalendarJavaDateFormat(String dateFormat) {
242
243        dateFormat = CmsStringUtil.substitute(dateFormat, "%", ""); // remove all "%"
244        dateFormat = CmsStringUtil.substitute(dateFormat, "m", "${month}");
245        dateFormat = CmsStringUtil.substitute(dateFormat, "H", "${hour}");
246        dateFormat = CmsStringUtil.substitute(dateFormat, "Y", "${4anno}");
247        dateFormat = dateFormat.toLowerCase();
248        dateFormat = CmsStringUtil.substitute(dateFormat, "${month}", "M");
249        dateFormat = CmsStringUtil.substitute(dateFormat, "${hour}", "H");
250        dateFormat = CmsStringUtil.substitute(dateFormat, "y", "yy");
251        dateFormat = CmsStringUtil.substitute(dateFormat, "${4anno}", "yyyy");
252        dateFormat = CmsStringUtil.substitute(dateFormat, "m", "mm"); // minutes with two digits
253        dateFormat = dateFormat.replace('e', 'd'); // day of month
254        dateFormat = dateFormat.replace('i', 'h'); // 12 hour format
255        dateFormat = dateFormat.replace('p', 'a'); // pm/am String
256        return dateFormat;
257    }
258
259    /**
260     * Returns the given timestamp as String formatted in a localized pattern.<p>
261     *
262     * @param locale the locale for the time format
263     * @param messages the messages that contain the time format definitions
264     * @param timestamp the time to format
265     *
266     * @return the given timestamp as String formatted in a localized pattern
267     */
268    public static String getCalendarLocalizedTime(Locale locale, CmsMessages messages, long timestamp) {
269
270        // get the current date & time
271        TimeZone zone = TimeZone.getDefault();
272        GregorianCalendar cal = new GregorianCalendar(zone, locale);
273        cal.setTimeInMillis(timestamp);
274        // format it nicely according to the localized pattern
275        DateFormat df = new SimpleDateFormat(
276            CmsCalendarWidget.getCalendarJavaDateFormat(
277                messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_DATE_FORMAT_0)
278                    + " "
279                    + messages.key(org.opencms.workplace.Messages.GUI_CALENDAR_TIME_FORMAT_0)));
280        return df.format(cal.getTime());
281    }
282
283    /**
284     * Returns the language suffix for the calendar-*.js localizations.<p>
285     *
286     * @param language the language from the locale
287     *
288     * @return the suffix to use for the calendar-*js localication file
289     */
290    private static String getLanguageSuffix(String language) {
291
292        if (language.equals(Locale.JAPANESE.getLanguage())) {
293            return "jp";
294        } else {
295            return language;
296        }
297    }
298
299    /**
300     * @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)
301     */
302    public String getConfiguration(
303        CmsObject cms,
304        A_CmsXmlContentValue schemaType,
305        CmsMessages messages,
306        CmsResource resource,
307        Locale contentLocale) {
308
309        String configStr = getConfiguration();
310        JSONObject resultObj = null;
311        // ensure configuration is either empty or a valid JSON object - if empty, convert to empty JSON object,
312        // otherwise convert to standard JSON format (fully quoted keys etc.)
313        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(configStr)) {
314            try {
315                resultObj = new JSONObject(configStr);
316                return resultObj.toString();
317            } catch (JSONException e) {
318                LOG.error(e.getLocalizedMessage(), e);
319            }
320        }
321        return "{}";
322    }
323
324    /**
325     * @see org.opencms.widgets.I_CmsADEWidget#getCssResourceLinks(org.opencms.file.CmsObject)
326     */
327    public List<String> getCssResourceLinks(CmsObject cms) {
328
329        return null;
330    }
331
332    /**
333     * @see org.opencms.widgets.I_CmsADEWidget#getDefaultDisplayType()
334     */
335    public DisplayType getDefaultDisplayType() {
336
337        return DisplayType.singleline;
338    }
339
340    /**
341     * @see org.opencms.widgets.I_CmsWidget#getDialogIncludes(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog)
342     */
343    @Override
344    public String getDialogIncludes(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
345
346        return calendarIncludes(widgetDialog.getLocale());
347    }
348
349    /**
350     * @see org.opencms.widgets.I_CmsWidget#getDialogWidget(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
351     */
352    public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
353
354        StringBuffer result = new StringBuffer(16);
355        result.append("<td class=\"xmlTd\">");
356        result.append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td>");
357        result.append("<input class=\"xmlInputSmall");
358        if (param.hasError()) {
359            result.append(" xmlInputError");
360        }
361        result.append("\" value=\"");
362        String dateTimeValue = getWidgetStringValue(cms, widgetDialog, param);
363        result.append(dateTimeValue);
364        String id = param.getId();
365        result.append("\" name=\"");
366        result.append(id);
367        result.append("\" id=\"");
368        result.append(id);
369        result.append("\"></td>");
370        result.append(widgetDialog.dialogHorizontalSpacer(10));
371        result.append("<td>");
372        result.append("<table class=\"editorbuttonbackground\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"");
373        result.append(id);
374        result.append(".calendar\"><tr>");
375        result.append(
376            widgetDialog.button(
377                "#",
378                null,
379                "calendar",
380                org.opencms.workplace.Messages.GUI_CALENDAR_CHOOSE_DATE_0,
381                widgetDialog.getButtonStyle()));
382        result.append("</tr></table>");
383        result.append("</td></tr></table>");
384
385        result.append(
386            calendarInit(widgetDialog.getMessages(), id, id + ".calendar", "cR", false, false, true, null, true));
387
388        result.append("</td>");
389
390        return result.toString();
391    }
392
393    /**
394     * @see org.opencms.widgets.I_CmsADEWidget#getInitCall()
395     */
396    public String getInitCall() {
397
398        return null;
399    }
400
401    /**
402     * @see org.opencms.widgets.I_CmsADEWidget#getJavaScriptResourceLinks(org.opencms.file.CmsObject)
403     */
404    public List<String> getJavaScriptResourceLinks(CmsObject cms) {
405
406        return null;
407    }
408
409    /**
410     * @see org.opencms.widgets.I_CmsADEWidget#getWidgetName()
411     */
412    public String getWidgetName() {
413
414        return CmsCalendarWidget.class.getName();
415    }
416
417    /**
418     * @see org.opencms.widgets.A_CmsWidget#getWidgetStringValue(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
419     */
420    @Override
421    public String getWidgetStringValue(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
422
423        String result = param.getStringValue(cms);
424        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result) && !"0".equals(result)) {
425            try {
426                result = getCalendarLocalizedTime(
427                    widgetDialog.getLocale(),
428                    widgetDialog.getMessages(),
429                    Long.parseLong(result));
430            } catch (NumberFormatException e) {
431                if (!CmsMacroResolver.isMacro(result, CmsMacroResolver.KEY_CURRENT_TIME)) {
432                    // neither long nor macro, show empty value
433                    result = "";
434                }
435            }
436        } else {
437            result = "";
438        }
439        return result;
440    }
441
442    /**
443     * @see org.opencms.widgets.I_CmsADEWidget#isInternal()
444     */
445    public boolean isInternal() {
446
447        return true;
448    }
449
450    /**
451     * @see org.opencms.widgets.I_CmsWidget#newInstance()
452     */
453    public I_CmsWidget newInstance() {
454
455        return new CmsCalendarWidget(getConfiguration());
456    }
457
458    /**
459     * @see org.opencms.widgets.I_CmsWidget#setEditorValue(org.opencms.file.CmsObject, java.util.Map, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
460     */
461    @Override
462    public void setEditorValue(
463        CmsObject cms,
464        Map<String, String[]> formParameters,
465        I_CmsWidgetDialog widgetDialog,
466        I_CmsWidgetParameter param) {
467
468        String[] values = formParameters.get(param.getId());
469        if ((values != null) && (values.length > 0)) {
470            String dateTimeValue = values[0].trim();
471            if (CmsMacroResolver.isMacro(dateTimeValue, CmsMacroResolver.KEY_CURRENT_TIME)) {
472                // a macro is used, redisplay it
473                param.setStringValue(cms, dateTimeValue);
474            } else {
475                // a date value should be used
476                long dateTime;
477                try {
478                    dateTime = Long.valueOf(param.getStringValue(cms)).longValue();
479                } catch (NumberFormatException e) {
480                    dateTime = 0;
481                }
482                if (CmsStringUtil.isNotEmpty(dateTimeValue)) {
483                    try {
484                        dateTime = getCalendarDate(widgetDialog.getMessages(), dateTimeValue, true);
485                    } catch (ParseException e) {
486                        // TODO: Better exception handling
487                        if (LOG.isWarnEnabled()) {
488                            LOG.warn(Messages.get().getBundle().key(Messages.ERR_PARSE_DATETIME_1, dateTimeValue), e);
489                        }
490                    }
491                } else {
492                    dateTime = 0;
493                }
494                param.setStringValue(cms, String.valueOf(dateTime));
495            }
496        }
497    }
498}