001/* 
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018 */
019package org.apache.wiki.tags;
020
021import org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023import org.apache.wiki.api.core.ContextEnum;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.api.core.Page;
026import org.apache.wiki.pages.PageManager;
027import org.apache.wiki.util.HttpUtil;
028import org.apache.wiki.util.TextUtil;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.jsp.JspWriter;
032import java.io.IOException;
033import java.text.ParseException;
034import java.text.SimpleDateFormat;
035import java.util.Calendar;
036import java.util.Date;
037
038
039/**
040 *  Provides a nice calendar.  Responds to the following HTTP parameters:
041 *  <ul>
042 *  <li>calendar.date - If this parameter exists, then the calendar
043 *  date is taken from the month and year.  The date must be in ddMMyy
044 *  format.
045 *  <li>weblog.startDate - If calendar.date parameter does not exist,
046 *  we then check this date.
047 *  </ul>
048 *
049 *  If neither calendar.date nor weblog.startDate parameters exist,
050 *  then the calendar will default to the current month.
051 *
052 *  @since 2.0
053 */
054
055// FIXME: This class is extraordinarily lacking.
056public class CalendarTag extends WikiTagBase {
057
058    private static final long serialVersionUID = 0L;
059    private static final Logger log = LogManager.getLogger( CalendarTag.class );
060    
061    private SimpleDateFormat m_pageFormat;
062    private SimpleDateFormat m_urlFormat;
063    private SimpleDateFormat m_monthUrlFormat;
064    private SimpleDateFormat m_dateFormat = new SimpleDateFormat( "ddMMyy" );
065
066    /**
067     *  {@inheritDoc}
068     */
069    @Override
070    public void initTag()
071    {
072        super.initTag();
073        m_pageFormat = m_urlFormat = m_monthUrlFormat = null;
074        m_dateFormat = new SimpleDateFormat( "ddMMyy" );
075    }
076
077    /*
078    public void setYear( String year )
079    {
080        m_year = year;
081    }
082
083    public void setMonth( String month )
084    {
085        m_month = month;
086    }
087    */
088
089    /**
090     *  Sets the page format.  If a page corresponding to the format is found when
091     *  the calendar is being rendered, a link to that page is created.  E.g. if the
092     *  format is set to <tt>'Main_blogentry_'ddMMyy</tt>, it works nicely in
093     *  conjuction to the WeblogPlugin.
094     *  
095     *  @param format The format in the SimpleDateFormat fashion.
096     *  
097     *  @see SimpleDateFormat
098     *  @see org.apache.wiki.plugin.WeblogPlugin
099     */
100    public void setPageformat( final String format )
101    {
102        m_pageFormat = new SimpleDateFormat( format );
103    }
104
105    /**
106     *  Set the URL format.  If the pageformat is not set, all dates are
107     *  links to pages according to this format.  The pageformat
108     *  takes precedence.
109     *  
110     *  @param format The URL format in the SimpleDateFormat fashion.
111     *  @see SimpleDateFormat
112     */
113    public void setUrlformat( final String format )
114    {
115        m_urlFormat = new SimpleDateFormat( format );
116    }
117
118    /**
119     *  Set the format to be used for links for the months.
120     *  
121     *  @param format The format to set in the SimpleDateFormat fashion.
122     *  
123     *  @see SimpleDateFormat
124     */
125    public void setMonthurlformat( final String format )
126    {
127        m_monthUrlFormat = new SimpleDateFormat( format );
128    }
129
130    private String format( final String txt ) {
131        final Page p = m_wikiContext.getPage();
132        if( p != null ) {
133            return TextUtil.replaceString( txt, "%p", p.getName() );
134        }
135
136        return txt;
137    }
138
139    /**
140     *  Returns a link to the given day.
141     */
142    private String getDayLink( final Calendar day ) {
143        final Engine engine = m_wikiContext.getEngine();
144        final String result;
145
146        if( m_pageFormat != null ) {
147            final String pagename = m_pageFormat.format( day.getTime() );
148            
149            if( engine.getManager( PageManager.class ).wikiPageExists( pagename ) ) {
150                if( m_urlFormat != null ) {
151                    final String url = m_urlFormat.format( day.getTime() );
152                    result = "<td class=\"link\"><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
153                } else {
154                    result = "<td class=\"link\"><a href=\""+m_wikiContext.getViewURL( pagename )+"\">"+
155                             day.get( Calendar.DATE )+"</a></td>";
156                }
157            } else {
158                result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
159            }
160        } else if( m_urlFormat != null ) {
161            final String url = m_urlFormat.format( day.getTime() );
162            result = "<td><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
163        } else {
164            result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
165        }
166
167        return format( result );
168    }
169
170    private String getMonthLink( final Calendar day ) {
171        final SimpleDateFormat monthfmt = new SimpleDateFormat( "MMMM yyyy" );
172        final String result;
173
174        if( m_monthUrlFormat == null ) {
175            result = monthfmt.format( day.getTime() );
176        } else {
177            final Calendar cal = (Calendar)day.clone();
178            final int firstDay = cal.getActualMinimum( Calendar.DATE );
179            final int lastDay  = cal.getActualMaximum( Calendar.DATE );
180
181            cal.set( Calendar.DATE, lastDay );
182            String url = m_monthUrlFormat.format( cal.getTime() );
183
184            url = TextUtil.replaceString( url, "%d", Integer.toString( lastDay - firstDay + 1 ) );
185
186            result = "<a href=\""+url+"\">"+monthfmt.format(cal.getTime())+"</a>";
187        }
188
189        return format( result );
190    }
191
192    private String getMonthNaviLink( final Calendar day, final String txt, String queryString ) {
193        final String result;
194        queryString = TextUtil.replaceEntities( queryString );
195        final Calendar nextMonth = Calendar.getInstance();
196        nextMonth.set( Calendar.DATE, 1 );  
197        nextMonth.add( Calendar.DATE, -1);
198        nextMonth.add( Calendar.MONTH, 1 ); // Now move to 1st day of next month
199
200        if ( day.before( nextMonth ) ) {
201            final Page thePage = m_wikiContext.getPage();
202            final String pageName = thePage.getName();
203
204            final String calendarDate = m_dateFormat.format( day.getTime() );
205            String url = m_wikiContext.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), pageName,"calendar.date="+calendarDate );
206            final int queryStringLlength = queryString.length();
207            if(queryStringLlength > 0) {
208                //
209                // Ensure that the 'calendar.date=ddMMyy' has been removed from the queryString
210                //
211
212                // FIXME: Might be useful to have an entire library of 
213                //        routines for this.  Will fail if it's not calendar.date 
214                //        but something else.
215
216                final int pos1 = queryString.indexOf("calendar.date=");
217                if( pos1 >= 0 ) {
218                    String tmp = queryString.substring( 0, pos1 );
219                    // FIXME: Will this fail when we use & instead of &amp?
220                    // FIXME: should use some parsing routine
221                    final int pos2 = queryString.indexOf("&", pos1 ) + 1;
222                    if ( ( pos2 > 0 ) && ( pos2 < queryStringLlength ) ) {
223                        tmp = tmp + queryString.substring(pos2);
224                    }
225                    queryString = tmp;
226                }
227
228                if( queryStringLlength > 0 ) {
229                    url = url + "&amp;" + queryString;
230                }
231            }
232            result = "<td><a href=\""+url+"\">"+txt+"</a></td>";
233        } else {
234            result="<td> </td>";
235        }    
236
237        return format( result );
238    }
239
240    /**
241     *  {@inheritDoc}
242     */
243    @Override
244    public final int doWikiStartTag() throws IOException {
245        final Engine engine = m_wikiContext.getEngine();
246        final JspWriter out = pageContext.getOut();
247        final Calendar cal = Calendar.getInstance();
248        final Calendar prevCal = Calendar.getInstance();
249        final Calendar nextCal = Calendar.getInstance();
250
251        //
252        //  Check if there is a parameter in the request to set the date.
253        //
254        String calendarDate = pageContext.getRequest().getParameter( "calendar.date" );
255        if( calendarDate == null ) {
256            calendarDate = pageContext.getRequest().getParameter( "weblog.startDate" );
257        }
258        
259        if( calendarDate != null ) {
260            try {
261                final Date d = m_dateFormat.parse( calendarDate );
262                cal.setTime( d );
263                prevCal.setTime( d );
264                nextCal.setTime( d );
265            } catch( final ParseException e ) {
266                log.warn( "date format wrong: " + calendarDate );
267            }
268        }
269
270        cal.set( Calendar.DATE, 1 );     // First, set to first day of month
271        prevCal.set( Calendar.DATE, 1 );
272        nextCal.set( Calendar.DATE, 1 );
273
274        prevCal.add(Calendar.MONTH, -1); // Now move to first day of previous month
275        nextCal.add(Calendar.MONTH, 1);  // Now move to first day of next month
276
277        out.write( "<table class=\"calendar\">\n" );
278
279        final HttpServletRequest httpServletRequest = m_wikiContext.getHttpRequest();
280        final String queryString = HttpUtil.safeGetQueryString( httpServletRequest, engine.getContentEncoding() );
281        out.write( "<tr>"+
282                   getMonthNaviLink(prevCal,"&lt;&lt;", queryString)+
283                   "<td colspan=5 class=\"month\">"+
284                   getMonthLink( cal )+
285                   "</td>"+
286                   getMonthNaviLink(nextCal,"&gt;&gt;", queryString)+ 
287                   "</tr>\n"
288                 );
289
290        final int month = cal.get( Calendar.MONTH );
291        cal.set( Calendar.DAY_OF_WEEK, Calendar.MONDAY ); // Then, find the first day of the week.
292
293        out.write( "<tr><td class=\"weekdays\">Mon</td>"+
294                   "<td class=\"weekdays\">Tue</td>"+
295                   "<td class=\"weekdays\">Wed</td>"+
296                   "<td class=\"weekdays\">Thu</td>"+
297                   "<td class=\"weekdays\">Fri</td>"+
298                   "<td class=\"weekdays\">Sat</td>"+
299                   "<td class=\"weekdays\">Sun</td></tr>\n" );
300
301        boolean noMoreDates = false;
302        while( !noMoreDates ) {
303            out.write( "<tr>" );
304            
305            for( int i = 0; i < 7; i++ ) {
306                final int mth = cal.get( Calendar.MONTH );
307
308                if( mth != month ) {
309                    out.write("<td class=\"othermonth\">"+cal.get(Calendar.DATE)+"</td>");
310                } else {
311                    out.write( getDayLink(cal) );
312                }
313
314                cal.add( Calendar.DATE, 1 );
315            }
316
317            if( cal.get( Calendar.MONTH ) != month ) {
318                noMoreDates = true;
319            }
320
321            out.write( "</tr>\n" );
322        }
323
324        out.write( "</table>\n" );
325
326        return EVAL_BODY_INCLUDE;
327    }
328
329}