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 format.
044 *  <li>weblog.startDate - If calendar.date parameter does not exist,
045 *  we then check this date.
046 *  </ul>
047 *
048 *  If neither calendar.date nor weblog.startDate parameters exist,
049 *  then the calendar will default to the current month.
050 *
051 *  @since 2.0
052 */
053
054// FIXME: This class is extraordinarily lacking.
055public class CalendarTag extends WikiTagBase {
056
057    private static final long serialVersionUID = 0L;
058    private static final Logger LOG = LogManager.getLogger( CalendarTag.class );
059    private static final int NUM_PAGES_TO_CHECK = 3;
060    
061    private SimpleDateFormat m_pageFormat;
062    private SimpleDateFormat m_urlFormat;
063    private SimpleDateFormat m_monthUrlFormat;
064    private boolean m_addIndex;
065    private SimpleDateFormat m_dateFormat = new SimpleDateFormat( "ddMMyy" );
066
067    /**
068     *  {@inheritDoc}
069     */
070    @Override
071    public void initTag()
072    {
073        super.initTag();
074        m_pageFormat = m_urlFormat = m_monthUrlFormat = null;
075        m_dateFormat = new SimpleDateFormat( "ddMMyy" );
076    }
077
078    /*
079    public void setYear( String year )
080    {
081        m_year = year;
082    }
083
084    public void setMonth( String month )
085    {
086        m_month = month;
087    }
088    */
089
090    /**
091     *  Sets the page format.  If a page corresponding to the format is found when
092     *  the calendar is being rendered, a link to that page is created.  E.g. if the
093     *  format is set to <tt>'Main_blogentry_'ddMMyy</tt>, it works nicely in
094     *  conjunction to the WeblogPlugin.
095     *  
096     *  @param format The format in the SimpleDateFormat fashion.
097     *  
098     *  @see SimpleDateFormat
099     *  @see org.apache.wiki.plugin.WeblogPlugin
100     */
101    public void setPageformat( final String format )
102    {
103        m_pageFormat = new SimpleDateFormat( format );
104    }
105
106    /**
107     *  Set the URL format.  If the pageformat is not set, all dates are
108     *  links to pages according to this format.  The pageformat
109     *  takes precedence.
110     *  
111     *  @param format The URL format in the SimpleDateFormat fashion.
112     *  @see SimpleDateFormat
113     */
114    public void setUrlformat( final String format )
115    {
116        m_urlFormat = new SimpleDateFormat( format );
117    }
118
119    /**
120     *  Set the format to be used for links for the months.
121     *  
122     *  @param format The format to set in the SimpleDateFormat fashion.
123     *  
124     *  @see SimpleDateFormat
125     */
126    public void setMonthurlformat( final String format )
127    {
128        m_monthUrlFormat = new SimpleDateFormat( format );
129    }
130
131    /**
132     *  Sets whether or not the pageFormat contains a page index at the end.
133     *  This is the case for the WeblogPlugin.
134     *
135     *  @param addindex Whether a page index should be appended to the pageFormat
136     *
137     *  @see org.apache.wiki.plugin.WeblogPlugin
138     */
139    public void setAddindex( final boolean addIndex )
140    {
141        m_addIndex = addIndex;
142    }
143
144    private String format( final String txt ) {
145        final Page p = m_wikiContext.getPage();
146        if( p != null ) {
147            return TextUtil.replaceString( txt, "%p", p.getName() );
148        }
149
150        return txt;
151    }
152
153    /**
154     *  Returns a link to the given day.
155     */
156    private String getDayLink( final Calendar day ) {
157        final Engine engine = m_wikiContext.getEngine();
158        final String result;
159
160        if( m_pageFormat != null ) {
161            final String pagename = m_pageFormat.format( day.getTime() );
162
163            var somePageExistsOnThisDay = false;
164            if (m_addIndex) {
165                // Look at up to 3 pages for whether the page exists. This avoids an issue 
166                // with the WeblogPlugin when the first blog post(s) of a day gets deleted.
167            for (int pageIdx = 1; pageIdx <= NUM_PAGES_TO_CHECK; pageIdx++) {
168                        if( engine.getManager( PageManager.class ).wikiPageExists( pagename+pageIdx ) ) {
169                            somePageExistsOnThisDay = true;
170                            break;
171                        }
172                }
173            } else {
174                somePageExistsOnThisDay = engine.getManager( PageManager.class ).wikiPageExists( pagename );
175            }
176
177            if( somePageExistsOnThisDay ) {
178                if( m_urlFormat != null ) {
179                    final String url = m_urlFormat.format( day.getTime() );
180                    result = "<td class=\"link\"><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
181                } else {
182                    result = "<td class=\"link\"><a href=\""+m_wikiContext.getViewURL( pagename )+"\">"+
183                             day.get( Calendar.DATE )+"</a></td>";
184                }
185            } else {
186                result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
187            }
188        } else if( m_urlFormat != null ) {
189            final String url = m_urlFormat.format( day.getTime() );
190            result = "<td><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
191        } else {
192            result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
193        }
194
195        return format( result );
196    }
197
198    private String getMonthLink( final Calendar day ) {
199        final SimpleDateFormat monthfmt = new SimpleDateFormat( "MMMM yyyy" );
200        final String result;
201
202        if( m_monthUrlFormat == null ) {
203            result = monthfmt.format( day.getTime() );
204        } else {
205            final Calendar cal = (Calendar)day.clone();
206            final int firstDay = cal.getActualMinimum( Calendar.DATE );
207            final int lastDay  = cal.getActualMaximum( Calendar.DATE );
208
209            cal.set( Calendar.DATE, lastDay );
210            String url = m_monthUrlFormat.format( cal.getTime() );
211
212            url = TextUtil.replaceString( url, "%d", Integer.toString( lastDay - firstDay + 1 ) );
213
214            result = "<a href=\""+url+"\">"+monthfmt.format(cal.getTime())+"</a>";
215        }
216
217        return format( result );
218    }
219
220    private String getMonthNaviLink( final Calendar day, final String txt, String queryString ) {
221        final String result;
222        queryString = TextUtil.replaceEntities( queryString );
223        final Calendar nextMonth = Calendar.getInstance();
224        nextMonth.set( Calendar.DATE, 1 );  
225        nextMonth.add( Calendar.DATE, -1);
226        nextMonth.add( Calendar.MONTH, 1 ); // Now move to 1st day of next month
227
228        if ( day.before( nextMonth ) ) {
229            final Page thePage = m_wikiContext.getPage();
230            final String pageName = thePage.getName();
231
232            final String calendarDate = m_dateFormat.format( day.getTime() );
233            String url = m_wikiContext.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), pageName,"calendar.date="+calendarDate );
234            final int queryStringLlength = queryString.length();
235            if(queryStringLlength > 0) {
236                //
237                // Ensure that the 'calendar.date=ddMMyy' has been removed from the queryString
238                //
239
240                // FIXME: Might be useful to have an entire library of 
241                //        routines for this.  Will fail if it's not calendar.date 
242                //        but something else.
243
244                final int pos1 = queryString.indexOf("calendar.date=");
245                if( pos1 >= 0 ) {
246                    String tmp = queryString.substring( 0, pos1 );
247                    // FIXME: Will this fail when we use & instead of &amp?
248                    // FIXME: should use some parsing routine
249                    final int pos2 = queryString.indexOf("&", pos1 ) + 1;
250                    if ( ( pos2 > 0 ) && ( pos2 < queryStringLlength ) ) {
251                        tmp = tmp + queryString.substring(pos2);
252                    }
253                    queryString = tmp;
254                }
255
256                if( queryStringLlength > 0 ) {
257                    url = url + "&amp;" + queryString;
258                }
259            }
260            result = "<td><a href=\""+url+"\">"+txt+"</a></td>";
261        } else {
262            result="<td> </td>";
263        }    
264
265        return format( result );
266    }
267
268    /**
269     *  {@inheritDoc}
270     */
271    @Override
272    public final int doWikiStartTag() throws IOException {
273        final Engine engine = m_wikiContext.getEngine();
274        final JspWriter out = pageContext.getOut();
275        final Calendar cal = Calendar.getInstance();
276        final Calendar prevCal = Calendar.getInstance();
277        final Calendar nextCal = Calendar.getInstance();
278
279        //
280        //  Check if there is a parameter in the request to set the date.
281        //
282        String calendarDate = pageContext.getRequest().getParameter( "calendar.date" );
283        if( calendarDate == null ) {
284            calendarDate = pageContext.getRequest().getParameter( "weblog.startDate" );
285        }
286        
287        if( calendarDate != null ) {
288            try {
289                final Date d = m_dateFormat.parse( calendarDate );
290                cal.setTime( d );
291                prevCal.setTime( d );
292                nextCal.setTime( d );
293            } catch( final ParseException e ) {
294                LOG.warn( "date format wrong: " + calendarDate );
295            }
296        }
297
298        cal.set( Calendar.DATE, 1 );     // First, set to first day of month
299        prevCal.set( Calendar.DATE, 1 );
300        nextCal.set( Calendar.DATE, 1 );
301
302        prevCal.add(Calendar.MONTH, -1); // Now move to first day of previous month
303        nextCal.add(Calendar.MONTH, 1);  // Now move to first day of next month
304
305        out.write( "<table class=\"calendar\">\n" );
306
307        final HttpServletRequest httpServletRequest = m_wikiContext.getHttpRequest();
308        final String queryString = HttpUtil.safeGetQueryString( httpServletRequest, engine.getContentEncoding() );
309        out.write( "<tr>"+
310                   getMonthNaviLink(prevCal,"&lt;&lt;", queryString)+
311                   "<td colspan=5 class=\"month\">"+
312                   getMonthLink( cal )+
313                   "</td>"+
314                   getMonthNaviLink(nextCal,"&gt;&gt;", queryString)+ 
315                   "</tr>\n"
316                 );
317
318        final int month = cal.get( Calendar.MONTH );
319        cal.set( Calendar.DAY_OF_WEEK, Calendar.MONDAY ); // Then, find the first day of the week.
320
321        out.write( "<tr><td class=\"weekdays\">Mon</td>"+
322                   "<td class=\"weekdays\">Tue</td>"+
323                   "<td class=\"weekdays\">Wed</td>"+
324                   "<td class=\"weekdays\">Thu</td>"+
325                   "<td class=\"weekdays\">Fri</td>"+
326                   "<td class=\"weekdays\">Sat</td>"+
327                   "<td class=\"weekdays\">Sun</td></tr>\n" );
328
329        boolean noMoreDates = false;
330        while( !noMoreDates ) {
331            out.write( "<tr>" );
332            
333            for( int i = 0; i < 7; i++ ) {
334                final int mth = cal.get( Calendar.MONTH );
335
336                if( mth != month ) {
337                    out.write("<td class=\"othermonth\">"+cal.get(Calendar.DATE)+"</td>");
338                } else {
339                    out.write( getDayLink(cal) );
340                }
341
342                cal.add( Calendar.DATE, 1 );
343            }
344
345            if( cal.get( Calendar.MONTH ) != month ) {
346                noMoreDates = true;
347            }
348
349            out.write( "</tr>\n" );
350        }
351
352        out.write( "</table>\n" );
353
354        return EVAL_BODY_INCLUDE;
355    }
356
357}