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