6fcd8c2ca660747eb62a626e22f63339687b06e7
[ikiwiki.git] / IkiWiki / Plugin / calendar.pm
1 #! /usr/bin/perl
2 # Copyright (c) 2006, 2007 Manoj Srivastava <srivasta@debian.org>
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 5.002;
19 package IkiWiki::Plugin::calendar;
20
21 use warnings;
22 use strict;
23 use IkiWiki 3.00;
24 use Time::Local;
25
26 my $time=time;
27 my @now=localtime($time);
28
29 sub import {
30         hook(type => "checkconfig", id => "calendar", call => \&checkconfig);
31         hook(type => "getsetup", id => "calendar", call => \&getsetup);
32         hook(type => "needsbuild", id => "calendar", call => \&needsbuild);
33         hook(type => "preprocess", id => "calendar", call => \&preprocess);
34         hook(type => "scan", id => "calendar", call => \&scan);
35 }
36
37 sub getsetup () {
38         return
39                 plugin => {
40                         safe => 1,
41                         rebuild => undef,
42                         section => "widget",
43                 },
44                 archivebase => {
45                         type => "string",
46                         example => "archives",
47                         description => "base of the archives hierarchy",
48                         safe => 1,
49                         rebuild => 1,
50                 },
51                 archive_pagespec => {
52                         type => "pagespec",
53                         example => "page(posts/*) and !*/Discussion",
54                         description => "PageSpec of pages to include in the archives, if option `calendar_autocreate` is true.",
55                         link => 'ikiwiki/PageSpec',
56                         safe => 1,
57                         rebuild => 0,
58                 },
59                 calendar_autocreate => {
60                         type => "boolean",
61                         example => 1,
62                         description => "autocreate new calendar pages?",
63                         safe => 1,
64                         rebuild => undef,
65                 },
66                 calendar_autocreate_commit => {
67                         type => "boolean",
68                         example => 1,
69                         default => 1,
70                         description => "commit autocreated calendar pages",
71                         safe => 1,
72                         rebuild => 0,
73                 },
74                 calendar_fill_gaps => {
75                         type => "boolean",
76                         example => 1,
77                         default => 1,
78                         description => "if set, when building calendar pages, also build pages of year and month when no pages were published (building empty calendars).",
79                         safe => 1,
80                         rebuild => 0,
81                 },
82 }
83
84 sub checkconfig () {
85         if (! defined $config{calendar_autocreate}) {
86                 $config{calendar_autocreate} = defined $config{archivebase} || defined $config{calendar_autocreate_commit};
87         }
88         if (! defined $config{calendar_autocreate_commit}) {
89                 $config{calendar_autocreate_commit} = 1;
90         }
91         if (! defined $config{archive_pagespec}) {
92                 $config{archive_pagespec} = '*';
93         }
94         if (! defined $config{archivebase}) {
95                 $config{archivebase} = 'archives';
96         }
97         if (! defined $config{calendar_fill_gaps}) {
98                 $config{calendar_fill_gaps} = 1;
99         }
100 }
101
102 sub is_leap_year (@) {
103         my %params=@_;
104         return ($params{year} % 4 == 0 && (($params{year} % 100 != 0) || $params{year} % 400 == 0));
105 }
106
107 sub month_days {
108         my %params=@_;
109         my $days_in_month = (31,28,31,30,31,30,31,31,30,31,30,31)[$params{month}-1];
110         if ($params{month} == 2 && is_leap_year(%params)) {
111                 $days_in_month++;
112         }
113         return $days_in_month;
114 }
115
116 sub autocreate {
117         my ($page, $pagefile, $year, $month) = @_;
118         my $message=sprintf(gettext("creating calendar page %s"), $page);
119         debug($message);
120
121         my $template;
122         if (defined $month) {
123                 $template=template("calendarmonth.tmpl");
124         } else {
125                 $template=template("calendaryear.tmpl");
126         }
127         $template->param(year => $year);
128         $template->param(month => $month) if defined $month;
129         $template->param(pagespec => $config{archive_pagespec});
130
131         my $dir = $config{srcdir};
132         if (! $config{calendar_autocreate_commit}) {
133                 $dir = $IkiWiki::Plugin::transient::transientdir;
134         }
135
136         writefile($pagefile, $dir, $template->output);
137         if ($config{rcs} && $config{calendar_autocreate_commit}) {
138                 IkiWiki::disable_commit_hook();
139                 IkiWiki::rcs_add($pagefile);
140                 IkiWiki::rcs_commit_staged(message => $message);
141                 IkiWiki::enable_commit_hook();
142         }
143 }
144
145 sub calendarlink($;$) {
146         my ($year, $month) = @_;
147         if (defined $month) {
148                 return $config{archivebase} . "/" . $year . "/" . $month;
149         } else {
150                 return $config{archivebase} . "/" . $year;
151         }
152 }
153
154 sub gencalendarmonth{
155         my $year = shift;
156         my $month = sprintf("%02d", shift);
157
158         my $page = calendarlink($year, $month);
159         my $pagefile = newpagefile($page, $config{default_pageext});
160         add_autofile(
161                 $pagefile, "calendar",
162                 sub {return autocreate($page, $pagefile, $year, $month);}
163         );
164 }
165
166 sub gencalendaryear {
167         my $year = shift;
168         my %params = @_;
169
170         return unless $config{calendar_autocreate};
171
172         # Building year page
173         my $page = calendarlink($year);
174         my $pagefile = newpagefile($page, $config{default_pageext});
175         add_autofile(
176                 $pagefile, "calendar",
177                 sub {return autocreate($page, $pagefile, $year);}
178         );
179
180         if (not exists $wikistate{calendar}{minyear}) {
181                 $wikistate{calendar}{minyear} = $year;
182         }
183         if (not exists $wikistate{calendar}{maxyear}) {
184                 $wikistate{calendar}{maxyear} = $year;
185         }
186
187         if ($config{calendar_fill_gaps}) {
188                 # Building month pages
189                 foreach my $month (1 .. 12) {
190                         gencalendarmonth($year, $month);
191                 }
192
193                 # Filling potential gaps in years (e.g. calendar goes from 2010 to 2014,
194                 # and we just added year 2005. We have to had years 2006 to 2009).
195                 return if $params{norecurse};
196                 if ($wikistate{calendar}{minyear} > $year) {
197                         foreach my $other ($year + 1 .. $wikistate{calendar}{minyear} - 1) {
198                                 gencalendaryear($other, norecurse => 1);
199                         }
200                         $wikistate{calendar}{minyear} = $year;
201                 }
202                 if ($wikistate{calendar}{maxyear} < $year) {
203                         foreach my $other ($wikistate{calendar}{maxyear} + 1 .. $year - 1) {
204                                 gencalendaryear($other, norecurse => 1);
205                         }
206                         $wikistate{calendar}{maxyear} = $year;
207                 }
208         }
209         if ($year < $wikistate{calendar}{minyear}) {
210                 $wikistate{calendar}{minyear} = $year;
211         }
212         if ($year >  $wikistate{calendar}{maxyear}) {
213                 $wikistate{calendar}{maxyear} = $year;
214         }
215 }
216
217 sub format_month (@) {
218         my %params=@_;
219
220         my %linkcache;
221         foreach my $p (pagespec_match_list($params{page}, 
222                                 "creation_year($params{year}) and creation_month($params{month}) and ($params{pages})",
223                                 # add presence dependencies to update
224                                 # month calendar when pages are added/removed
225                                 deptype => deptype("presence"))) {
226                 my $mtime = $IkiWiki::pagectime{$p};
227                 my @date  = localtime($mtime);
228                 my $mday  = $date[3];
229                 my $month = $date[4] + 1;
230                 my $year  = $date[5] + 1900;
231                 my $mtag  = sprintf("%02d", $month);
232
233                 if (! $linkcache{"$year/$mtag/$mday"}) {
234                         $linkcache{"$year/$mtag/$mday"} = [];
235                 }
236                 push(@{$linkcache{"$year/$mtag/$mday"}}, $p);
237         }
238                 
239         my $archivebase = 'archives';
240         $archivebase = $config{archivebase} if defined $config{archivebase};
241         $archivebase = $params{archivebase} if defined $params{archivebase};
242   
243         my $pmonth = $params{month};
244         my $pyear  = $params{year};
245         while (((not exists $pagesources{"$archivebase/$pyear/" . sprintf("%02d", $pmonth)}) and ($pyear >= $wikistate{calendar}{minyear})) or ($pmonth == $params{month} and $pyear == $params{year})) {
246                 $pmonth -= 1;
247                 if ($pmonth == 0) {
248                         $pyear -= 1;
249                         $pmonth = 12;
250                 }
251         }
252
253         my $nmonth = $params{month};
254         my $nyear  = $params{year};
255         while (((not exists $pagesources{"$archivebase/$nyear/" . sprintf("%02d", $nmonth)}) and ($nyear <= $wikistate{calendar}{maxyear})) or ($nmonth == $params{month} and $nyear == $params{year})) {
256                 $nmonth += 1;
257                 if ($nmonth == 13) {
258                         $nyear += 1;
259                         $nmonth = 1;
260                 }
261         }
262
263         # Add padding.
264         $pmonth=sprintf("%02d", $pmonth);
265         $nmonth=sprintf("%02d", $nmonth);
266
267         my $calendar="\n";
268
269         # When did this month start?
270         my @monthstart = localtime(timelocal(0,0,0,1,$params{month}-1,$params{year}-1900));
271
272         my $future_dom = 0;
273         my $today      = 0;
274         if ($params{year} == $now[5]+1900 && $params{month} == $now[4]+1) {
275                 $future_dom = $now[3]+1;
276                 $today      = $now[3];
277         }
278
279         # Find out month names for this, next, and previous months
280         my $monthabbrev=strftime_utf8("%b", @monthstart);
281         my $monthname=strftime_utf8("%B", @monthstart);
282         my $pmonthname=strftime_utf8("%B", localtime(timelocal(0,0,0,1,$pmonth-1,$pyear-1900)));
283         my $nmonthname=strftime_utf8("%B", localtime(timelocal(0,0,0,1,$nmonth-1,$nyear-1900)));
284
285         # Calculate URL's for monthly archives.
286         my ($url, $purl, $nurl)=("$monthname $params{year}",'','');
287         if (exists $pagesources{"$archivebase/$params{year}/$params{month}"}) {
288                 $url = htmllink($params{page}, $params{destpage}, 
289                         "$archivebase/$params{year}/".$params{month},
290                         noimageinline => 1,
291                         linktext => "$monthabbrev $params{year}",
292                         title => $monthname);
293         }
294         add_depends($params{page}, "$archivebase/$params{year}/$params{month}",
295                 deptype("presence"));
296         if (exists $pagesources{"$archivebase/$pyear/$pmonth"}) {
297                 $purl = htmllink($params{page}, $params{destpage}, 
298                         "$archivebase/$pyear/$pmonth",
299                         noimageinline => 1,
300                         linktext => "\&larr;",
301                         title => $pmonthname);
302         }
303         add_depends($params{page}, "$archivebase/$pyear/$pmonth",
304                 deptype("presence"));
305         if (exists $pagesources{"$archivebase/$nyear/$nmonth"}) {
306                 $nurl = htmllink($params{page}, $params{destpage}, 
307                         "$archivebase/$nyear/$nmonth",
308                         noimageinline => 1,
309                         linktext => "\&rarr;",
310                         title => $nmonthname);
311         }
312         add_depends($params{page}, "$archivebase/$nyear/$nmonth",
313                 deptype("presence"));
314
315         # Start producing the month calendar
316         $calendar=<<EOF;
317 <table class="month-calendar">
318         <tr>
319         <th class="month-calendar-arrow">$purl</th>
320         <th class="month-calendar-head" colspan="5">$url</th>
321         <th class="month-calendar-arrow">$nurl</th>
322         </tr>
323         <tr>
324 EOF
325
326         # Suppose we want to start the week with day $week_start_day
327         # If $monthstart[6] == 1
328         my $week_start_day = $params{week_start_day};
329
330         my $start_day = 1 + (7 - $monthstart[6] + $week_start_day) % 7;
331         my %downame;
332         my %dowabbr;
333         for my $dow ($week_start_day..$week_start_day+6) {
334                 my @day=localtime(timelocal(0,0,0,$start_day++,$params{month}-1,$params{year}-1900));
335                 my $downame = strftime_utf8("%A", @day);
336                 my $dowabbr = substr($downame, 0, 1);
337                 $downame{$dow % 7}=$downame;
338                 $dowabbr{$dow % 7}=$dowabbr;
339                 $calendar.= qq{\t\t<th class="month-calendar-day-head $downame" title="$downame">$dowabbr</th>\n};
340         }
341
342         $calendar.=<<EOF;
343         </tr>
344 EOF
345
346         my $wday;
347         # we start with a week_start_day, and skip until we get to the first
348         for ($wday=$week_start_day; $wday != $monthstart[6]; $wday++, $wday %= 7) {
349                 $calendar.=qq{\t<tr>\n} if $wday == $week_start_day;
350                 $calendar.=qq{\t\t<td class="month-calendar-day-noday $downame{$wday}">&nbsp;</td>\n};
351         }
352
353         # At this point, either the first is a week_start_day, in which case
354         # nothing has been printed, or else we are in the middle of a row.
355         for (my $day = 1; $day <= month_days(year => $params{year}, month => $params{month});
356              $day++, $wday++, $wday %= 7) {
357                 # At this point, on a week_start_day, we close out a row,
358                 # and start a new one -- unless it is week_start_day on the
359                 # first, where we do not close a row -- since none was started.
360                 if ($wday == $week_start_day) {
361                         $calendar.=qq{\t</tr>\n} unless $day == 1;
362                         $calendar.=qq{\t<tr>\n};
363                 }
364                 
365                 my $tag;
366                 my $key="$params{year}/$params{month}/$day";
367                 if (defined $linkcache{$key}) {
368                         if ($day == $today) {
369                                 $tag='month-calendar-day-this-day';
370                         }
371                         else {
372                                 $tag='month-calendar-day-link';
373                         }
374                         $calendar.=qq{\t\t<td class="$tag $downame{$wday}">};
375                         $calendar.=qq{<div class='popup'>$day<div class='balloon'>};
376                         # Several postings on this page
377                         $calendar.=qq{<ul>};
378                         foreach my $page (@{$linkcache{$key}}) {
379                                 $calendar.= qq{\n\t\t\t<li>};
380                                 my $title;
381                                 if (exists $pagestate{$page}{meta}{title}) {
382                                         $title = "$pagestate{$page}{meta}{title}";
383                                 }
384                                 else {
385                                         $title = pagetitle(IkiWiki::basename($page));
386                                 }
387                                 $calendar.=htmllink($params{page}, $params{destpage}, 
388                                         $page,
389                                         noimageinline => 1,
390                                         linktext => $title,
391                                         title => $title);
392                                 $calendar.= '</li>';
393                         }
394                         $calendar.=qq{\n\t\t</ul>};
395                         $calendar.=qq{</div></div>};
396                         $calendar.=qq{</td>\n};
397                 }
398                 else {
399                         if ($day == $today) {
400                                 $tag='month-calendar-day-this-day';
401                         }
402                         elsif ($day == $future_dom) {
403                                 $tag='month-calendar-day-future';
404                         }
405                         else {
406                                 $tag='month-calendar-day-nolink';
407                         }
408                         $calendar.=qq{\t\t<td class="$tag $downame{$wday}">$day</td>\n};
409                 }
410         }
411
412         # finish off the week
413         for (; $wday != $week_start_day; $wday++, $wday %= 7) {
414                 $calendar.=qq{\t\t<td class="month-calendar-day-noday $downame{$wday}">&nbsp;</td>\n};
415         }
416         $calendar.=<<EOF;
417         </tr>
418 </table>
419 EOF
420
421         return $calendar;
422 }
423
424 sub format_year (@) {
425         my %params=@_;
426
427         my @post_months;
428         foreach my $p (pagespec_match_list($params{page}, 
429                                 "creation_year($params{year}) and ($params{pages})",
430                                 # add presence dependencies to update
431                                 # year calendar's links to months when
432                                 # pages are added/removed
433                                 deptype => deptype("presence"))) {
434                 my $mtime = $IkiWiki::pagectime{$p};
435                 my @date  = localtime($mtime);
436                 my $month = $date[4] + 1;
437                 my $year  = $date[5] + 1900;
438
439                 $post_months[$month]++;
440         }
441                 
442         my $calendar="\n";
443
444         my $archivebase = 'archives';
445         $archivebase = $config{archivebase} if defined $config{archivebase};
446         $archivebase = $params{archivebase} if defined $params{archivebase};
447         
448         my $pyear = $params{year}  - 1;
449         while ((not exists $pagesources{"$archivebase/$pyear"}) and ($pyear > $wikistate{calendar}{minyear})) {
450                 $pyear -= 1;
451         }
452         my $nyear = $params{year}  + 1;
453         while ((not exists $pagesources{"$archivebase/$nyear"}) and ($nyear < $wikistate{calendar}{maxyear})) {
454                 $nyear += 1;
455         }
456
457         my $thisyear = $now[5]+1900;
458         my $future_month = 0;
459         $future_month = $now[4]+1 if $params{year} == $thisyear;
460
461         # calculate URL's for previous and next years
462         my ($url, $purl, $nurl)=("$params{year}",'','');
463         if (exists $pagesources{"$archivebase/$params{year}"}) {
464                 $url = htmllink($params{page}, $params{destpage}, 
465                         "$archivebase/$params{year}",
466                         noimageinline => 1,
467                         linktext => $params{year},
468                         title => $params{year});
469         }
470         add_depends($params{page}, "$archivebase/$params{year}", deptype("presence"));
471         if (exists $pagesources{"$archivebase/$pyear"}) {
472                 $purl = htmllink($params{page}, $params{destpage}, 
473                         "$archivebase/$pyear",
474                         noimageinline => 1,
475                         linktext => "\&larr;",
476                         title => $pyear);
477         }
478         add_depends($params{page}, "$archivebase/$pyear", deptype("presence"));
479         if (exists $pagesources{"$archivebase/$nyear"}) {
480                 $nurl = htmllink($params{page}, $params{destpage}, 
481                         "$archivebase/$nyear",
482                         noimageinline => 1,
483                         linktext => "\&rarr;",
484                         title => $nyear);
485         }
486         add_depends($params{page}, "$archivebase/$nyear", deptype("presence"));
487
488         # Start producing the year calendar
489         my $m=$params{months_per_row}-2;
490         $calendar=<<EOF;
491 <table class="year-calendar">
492         <tr>
493         <th class="year-calendar-arrow">$purl</th>
494         <th class="year-calendar-head" colspan="$m">$url</th>
495         <th class="year-calendar-arrow">$nurl</th>
496         </tr>
497         <tr>
498                 <th class="year-calendar-subhead" colspan="$params{months_per_row}">Months</th>
499         </tr>
500 EOF
501
502         for (my $month = 1; $month <= 12; $month++) {
503                 my @day=localtime(timelocal(0,0,0,15,$month-1,$params{year}-1900));
504                 my $murl;
505                 my $monthname = strftime_utf8("%B", @day);
506                 my $monthabbr = strftime_utf8("%b", @day);
507                 $calendar.=qq{\t<tr>\n}  if ($month % $params{months_per_row} == 1);
508                 my $tag;
509                 my $mtag=sprintf("%02d", $month);
510                 if ($month == $params{month} && $thisyear == $params{year}) {
511                         $tag = 'year-calendar-this-month';
512                 }
513                 elsif ($pagesources{"$archivebase/$params{year}/$mtag"}) {
514                         $tag = 'year-calendar-month-link';
515                 } 
516                 elsif ($future_month && $month >= $future_month) {
517                         $tag = 'year-calendar-month-future';
518                 } 
519                 else {
520                         $tag = 'year-calendar-month-nolink';
521                 }
522
523                 if ($pagesources{"$archivebase/$params{year}/$mtag"} &&
524                     $post_months[$mtag]) {
525                         $murl = htmllink($params{page}, $params{destpage}, 
526                                 "$archivebase/$params{year}/$mtag",
527                                 noimageinline => 1,
528                                 linktext => $monthabbr,
529                                 title => $monthname);
530                         $calendar.=qq{\t<td class="$tag">};
531                         $calendar.=$murl;
532                         $calendar.=qq{\t</td>\n};
533                 }
534                 else {
535                         $calendar.=qq{\t<td class="$tag">$monthabbr</td>\n};
536                 }
537                 add_depends($params{page}, "$archivebase/$params{year}/$mtag",
538                         deptype("presence"));
539
540                 $calendar.=qq{\t</tr>\n} if ($month % $params{months_per_row} == 0);
541         }
542
543         $calendar.=<<EOF;
544 </table>
545 EOF
546
547         return $calendar;
548 }
549
550 sub setnextchange ($$) {
551         my $page=shift;
552         my $timestamp=shift;
553
554         if (! exists $pagestate{$page}{calendar}{nextchange} ||
555             $pagestate{$page}{calendar}{nextchange} > $timestamp) {
556                 $pagestate{$page}{calendar}{nextchange}=$timestamp;
557         }
558 }
559
560 sub preprocess (@) {
561         my %params=@_;
562
563         my $thisyear=1900 + $now[5];
564         my $thismonth=1 + $now[4];
565
566         $params{pages} = "*"            unless defined $params{pages};
567         $params{type}  = "month"        unless defined $params{type};
568         $params{week_start_day} = 0     unless defined $params{week_start_day};
569         $params{months_per_row} = 3     unless defined $params{months_per_row};
570         $params{year}  = $thisyear      unless defined $params{year};
571         $params{month} = $thismonth     unless defined $params{month};
572
573         my $relativeyear=0;
574         if ($params{year} < 1) {
575                 $relativeyear=1;
576                 $params{year}=$thisyear+$params{year};
577         }
578         my $relativemonth=0;
579         if ($params{month} < 1) {
580                 $relativemonth=1;
581                 my $monthoff=$params{month};
582                 $params{month}=($thismonth+$monthoff) % 12;
583                 $params{month}=12 if $params{month}==0;
584                 my $yearoff=POSIX::ceil(($thismonth-$params{month}) / -12)
585                         - int($monthoff / 12);
586                 $params{year}-=$yearoff;
587         }
588         
589         $params{month} = sprintf("%02d", $params{month});
590         
591         if ($params{type} eq 'month' && $params{year} == $thisyear
592             && $params{month} == $thismonth) {
593                 # calendar for current month, updates next midnight
594                 setnextchange($params{destpage}, ($time
595                         + (60 - $now[0])                # seconds
596                         + (59 - $now[1]) * 60           # minutes
597                         + (23 - $now[2]) * 60 * 60      # hours
598                 ));
599         }
600         elsif ($params{type} eq 'month' &&
601                (($params{year} == $thisyear && $params{month} > $thismonth) ||
602                 $params{year} > $thisyear)) {
603                 # calendar for upcoming month, updates 1st of that month
604                 setnextchange($params{destpage},
605                         timelocal(0, 0, 0, 1, $params{month}-1, $params{year}));
606         }
607         elsif (($params{type} eq 'year' && $params{year} == $thisyear) ||
608                $relativemonth) {
609                 # Calendar for current year updates 1st of next month.
610                 # Any calendar relative to the current month also updates
611                 # then.
612                 if ($thismonth < 12) {
613                         setnextchange($params{destpage},
614                                 timelocal(0, 0, 0, 1, $thismonth+1-1, $params{year}));
615                 }
616                 else {
617                         setnextchange($params{destpage},
618                                 timelocal(0, 0, 0, 1, 1-1, $params{year}+1));
619                 }
620         }
621         elsif ($relativeyear) {
622                 # Any calendar relative to the current year updates 1st
623                 # of next year.
624                 setnextchange($params{destpage},
625                         timelocal(0, 0, 0, 1, 1-1, $thisyear+1));
626         }
627         elsif ($params{type} eq 'year' && $params{year} > $thisyear) {
628                 # calendar for upcoming year, updates 1st of that year
629                 setnextchange($params{destpage},
630                         timelocal(0, 0, 0, 1, 1-1, $params{year}));
631         }
632         else {
633                 # calendar for past month or year, does not need
634                 # to update any more
635                 delete $pagestate{$params{destpage}}{calendar};
636         }
637
638         my $calendar="";
639         if ($params{type} eq 'month') {
640                 $calendar=format_month(%params);
641         }
642         elsif ($params{type} eq 'year') {
643                 $calendar=format_year(%params);
644         }
645
646         return "\n<div><div class=\"calendar\">$calendar</div></div>\n";
647 } #}}
648
649 sub needsbuild (@) {
650         my $needsbuild=shift;
651         foreach my $page (keys %pagestate) {
652                 if (exists $pagestate{$page}{calendar}{nextchange}) {
653                         if ($pagestate{$page}{calendar}{nextchange} <= $time) {
654                                 # force a rebuild so the calendar shows
655                                 # the current day
656                                 push @$needsbuild, $pagesources{$page};
657                         }
658                         if (exists $pagesources{$page} && 
659                             grep { $_ eq $pagesources{$page} } @$needsbuild) {
660                                 # remove state, will be re-added if
661                                 # the calendar is still there during the
662                                 # rebuild
663                                 delete $pagestate{$page}{calendar};
664                         }
665                 }
666         }
667
668         return $needsbuild;
669 }
670
671 sub scan (@) {
672         my %params=@_;
673         my $page=$params{page};
674
675         # Check if year pages have to be generated
676         if (pagespec_match($page, $config{archive_pagespec})) {
677                 my @ctime = localtime($IkiWiki::pagectime{$page});
678                 gencalendaryear($ctime[5]+1900);
679                 gencalendarmonth($ctime[5]+1900, $ctime[4]+1);
680         }
681 }
682
683 1