1 # Blosxom Plugin: calendar -*- perl -*-
2 # Author: Todd Larason (jtl@molehill.org)
4 # Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
5 # Calendar plugin Home/Docs/Licensing:
6 # http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/Calendar/
10 # --- Configuration Variables ---
12 @monthname = qw/January February March
15 October November December/ if ($#monthname != 11);
16 @monthabbr = qw/Jan Feb Mar
19 Oct Nov Dec/ if ($#monthabbr != 11);
20 @downame = qw/Sunday Monday Tuesday Wednesday Thursday
21 Friday Saturday/ if ($#downame != 6);
22 @dowabbr = qw/Sun Mon Tue Wed Thu Fri Sat/ if ($#dowabbr != 6);
25 if not defined $first_dow;
27 # set to 0 to disable attempted caching
28 $use_caching = 1 unless defined $use_caching;
29 $months_per_row = 3 unless defined $months_per_row;
30 $debug_level = 1 unless defined $debug_level;
31 # -------------------------------------------------------------------
38 $prev_month_link = '';
39 $next_month_link = '';
43 my $package = "calendar";
44 my $cachefile = "$blosxom::plugin_state_dir/.$package.cache";
50 my ($level, @msg) = @_;
52 if ($debug_level >= $level) {
53 print STDERR "$package debug $level: @msg\n";
60 return $blosxom::template->('', "$package.$bit", $blosxom::flavour);
64 my ($bit, $year, $month, $day, $dow) = @_;
65 my ($monthname, $monthabbr) = ($monthname[$month-1], $monthabbr[$month-1]);
66 my ($downame, $dowabbr) = ($downame[$dow], $dowabbr[$dow]);
67 my $year2digit = sprintf("%02d", $year % 100);
69 my $url = $blosxom::url;
70 $url .= sprintf("/%04d/", $year) if defined $year;
71 $url .= sprintf("%02d/", $month) if defined $month;
72 $url .= sprintf("%02d/", $day) if defined $day;
75 $date .= "$year" if defined $year;
76 $date .= "/$month" if defined $month;
77 $date .= "/$day" if defined $day;
78 my $count = $cache->{stories}{$date};
80 my $f = load_template($bit);
81 $f =~ s/((\$[\w:]+)|(\$\{[\w:]+\}))/$1 . "||''"/gee;
86 my ($year, $month) = @_;
87 my $days = (31,28,31,30,31,30,31,31,30,31,30,31)[$month-1];
90 (($year % 100 != 0) ||
91 ($year % 400 == 0)))) {
98 my ($year, $month, $day);
100 if (defined($blosxom::path_info_yr)) {
101 $year = $blosxom::path_info_yr + 0;
102 $month = $blosxom::path_info_mo_num + 0;
103 $day = $blosxom::path_info_da + 0;
104 if ($month == 0 && $cache->{stories}{$year}) {
105 for ($month = 12; $month > 0; $month--) {
106 last if $cache->{stories}{"$year/$month"};
112 # is this a single-article view?
113 # XXX this probably doesn't work for static
114 if ($blosxom::path_info =~ m!\.!) {
115 my $filename = $blosxom::path_info;
116 # replace whatever flavour with the file extension
117 $filename =~ s!\..*!.$blosxom::file_extension!;
118 # remove any dated dirs, if present
119 $filename =~ s!/\d+/.*/!/!;
120 # and convert from an URL relative to a filename
121 $filename = "$blosxom::datadir/$filename";
122 # at this point, it's our best guess at a filename
123 # if it's not in the index, oh well, we tried - fall back to present
124 $now = $files->{$filename} || $^T;
128 # no date given at all, do this month and highlight today
129 my @now = localtime($now);
130 $year = $now[5] + 1900;
131 $month = $now[4] + 1;
134 return ($year, $month, $day);
137 sub build_prev_month_link {
138 my ($year, $month) = @_;
139 my $year_orig = $year;
140 my $month_orig = $month;
144 if ($month <= 0) { # == 0 is right, <= 2xprotects against infinite loop bug
147 # XXX assumption: once a log is active, no full years are skipped
148 if ($cache->{stories}{"$year"} == 0) {
149 return report('prev_month_nolink',
150 $month_orig == 1 ? $year_orig-1 : $year_orig,
151 $month_orig == 1 ? 12 : $month_orig-1);
154 if ($cache->{stories}{"$year/$month"}) {
155 return report('prev_month_link', $year, $month);
160 sub build_next_month_link {
161 my ($year, $month) = @_;
162 my $year_orig = $year;
163 my $month_orig = $month;
170 # XXX assumption: once a log is active, no full years are skipped
171 if ($cache->{stories}{"$year"} == 0) {
172 return report('next_month_nolink',
173 $month_orig == 1 ? $year_orig-1 : $year_orig,
174 $month_orig == 1 ? 12 : $month_orig-1);
177 if ($cache->{stories}{"$year/$month"}) {
178 return report('next_month_link', $year, $month);
183 sub build_prev_year_link {
187 return report($cache->{stories}{"$year"} ?
188 'prev_year_link': 'prev_year_nolink',
192 sub build_next_year_link {
197 return report($cache->{stories}{"$year"} ?
198 'next_year_link': 'next_year_nolink',
202 sub build_month_calendar {
203 my ($year, $month, $highlight_dom) = @_;
206 my (@now, $monthstart, @monthstart);
207 my ($day, $days, $wday);
210 @now = localtime($^T);
211 $future_dom = $now[3]+1 if ($year == $now[5]+1900 && $month == $now[4]+1);
213 $days = days_in_month($year, $month);
214 $monthstart = timelocal(0,0,0,1,$month-1,$year-1900);
215 @monthstart = localtime($monthstart);
217 $results = report('month_head', $year, $month);
218 $results .= report('month_sub_head', $year, $month);
221 $results .= report('month_sub_day', $year, $month, undef, $wday);
224 } while ($wday != $first_dow);
225 $results .= report('month_sub_foot', $year, $month);
227 # First, skip over the first partial week (possibly empty)
228 # before the month started
229 for ($wday = $first_dow; $wday != $monthstart[6]; $wday++, $wday %= 7) {
230 $results .= report('week_head', $year, $month)
231 if ($wday == $first_dow);
232 $results .= report('noday', $year, $month, undef, $wday);
235 # now do the month itself
236 for ($day = 1; $day <= $days; $day++) {
237 $results .= report('week_head', $year, $month, $day)
238 if ($wday == $first_dow);
240 if ($day == $highlight_dom) {
241 if ($cache->{stories}{"$year/$month/$day"}){$tag='this_day_link'}
242 else {$tag = 'this_day_nolink'}}
243 elsif ($cache->{stories}{"$year/$month/$day"}){$tag = 'day_link'}
244 elsif ($future_dom && $day >= $future_dom) {$tag = 'day_future'}
245 else {$tag = 'day_nolink'}
246 $results .= report($tag, $year, $month, $day, $wday);
247 $wday = 0 if (++$wday == 7);
248 $results .= report('week_foot', $year, $month)
249 if ($wday == $first_dow);
252 # and finish up the last week, if any left
253 if ($wday != $first_dow) {
254 for(; $wday != $first_dow; $wday++, $wday %= 7) {
255 $results .= report('noday', $year, $month, undef, $wday);
257 $results .= report('week_foot', $year, $month);
260 $results .= report('month_foot', $year, $month);
265 sub build_year_calendar {
266 my ($year, $highlight_month) = @_;
269 my $future_month = 0;
271 @now = localtime($^T);
272 $future_month = $now[4]+1 if ($year == $now[5]+1900);
274 $results = report('year_head', $year);
275 for ($month = 1; $month <= 12; $month++) {
276 $results .= report('quarter_head', $year)
277 if ($month % $months_per_row == 1);
279 if ($month == $highlight_month) {
280 if ($cache->{stories}{"$year/$month"}) {$tag = 'this_month_link'}
281 else {$tag = 'this_month_nolink'}}
282 elsif ($cache->{stories}{"$year/$month"}) {$tag = 'month_link'}
283 elsif ($future_month && $month >=$future_month){$tag = 'month_future'}
284 else {$tag = 'month_nolink'}
285 $results .= report($tag, $year, $month);
286 $results .= report('quarter_foot', $year)
287 if ($month % $months_per_row == 0);
289 $results .= report('year_foot', $year);
295 return report('calendar');
300 my ($sub, $cachetag, @args) = @_;
301 my $cachekey = join '/',@args,$blosxom::flavour;
302 return $cache->{$cachetag}{$cachekey} ||=
303 (debug(1, "cache miss $cachetag @args $blosxom::flavour") and
304 ($save_cache = 1) and
309 my ($num_files) = @_;
310 return 0 if !$use_caching;
311 eval "require Storable";
313 debug(1, "cache disabled, Storable not available");
317 if (!Storable->can('lock_retrieve')) {
318 debug(1, "cache disabled, Storable::lock_retrieve not available");
322 $cache = (-r $cachefile ? Storable::lock_retrieve($cachefile) : undef);
323 # >= rather than ==, so that if we're being used along with a search
324 # plugin that reduces %files, rather than dumping the cache and showing
325 # a limited calendar, we'll display the full thing (if available) . I
326 # think that's preferable as well as being more efficient.
327 # XXX improvement: rather than dumping the whole thing, just update the
328 # count and dump the current month (and sometimes the previous month
333 $today = "$now[5]/$now[4]/$now[3]";
335 $cache->{num_files} >= $num_files &&
336 $cache->{today} eq $today) {
337 debug(1, "Using cached state");
340 $cache = {num_files => $num_files, today => $today};
345 return if (!$use_caching || !$save_cache);
346 debug(1, "Saving cache");
347 -d $blosxom::plugin_state_dir
348 or mkdir $blosxom::plugin_state_dir;
349 Storable::lock_store($cache, $cachefile);
353 debug(1, "start() called, enabled");
357 last if /^(__END__)?$/;
358 my ($flavour, $comp, $txt) = split ' ',$_,3;
360 $blosxom::template{$flavour}{"$package.$comp"} = $txt;
366 my ($pkg, $files_ref) = @_;
367 debug(1, "filter() called");
371 my $num_files = scalar keys %$files;
372 my @latest = (sort {$b <=> $a} values %$files);
373 while ($latest[0] == $^T) {
377 return 1 if prime_cache($num_files);
379 debug(1, "cache miss: %stories");
381 foreach (keys %{$files}) {
383 my @date = localtime($files->{$_});
385 my $month = $date[4] + 1;
386 my $year = $date[5] + 1900;
387 $cache->{stories}{"$year"}++;
388 $cache->{stories}{"$year/$month"}++;
389 $cache->{stories}{"$year/$month/$mday"}++;
391 debug(1, "filter() done");
396 debug(1, "head() called");
397 my ($year, $month, $day) = pseudo_now();
399 debug(0,"Bad year $year requested ($year, path_info $ENV{PATH_INFO}, year to 2000");
402 $prev_month_link = cached(\&build_prev_month_link, "pml", $year,$month);
403 $next_month_link = cached(\&build_next_month_link, "nml", $year,$month);
404 $prev_year_link = cached(\&build_prev_year_link, "pyl", $year);
405 $next_year_link = cached(\&build_next_year_link, "nyl", $year);
406 $month_calendar = cached(\&build_month_calendar, "mc", $year,$month,$day);
408 for (my $y = (localtime)[5]+1900; $cache->{stories}{$y}; $y--) {
409 my $varname = "year_calendar_$y";
411 $year_calendar = cached(\&build_year_calendar, "yc",$year,$month);
412 $$varname = $year_calendar;
414 $$varname = cached(\&build_year_calendar, "yc", $y);
417 $year_calendar = cached(\&build_year_calendar, "yc", $year,$month)
418 unless $year_calendar;
419 $calendar = cached(\&build_calendar, "c", $year,$month,$day);
423 debug(1, "head() done, length(\$month_calendar, \$year_calendar, \$calendar) = ", length($month_calendar), length($year_calendar), length($calendar));
427 # these look better (to me), but don't use <caption> like they 'should', and
428 # the year one assumes 3 columns
429 #html month_head <table class="month-calendar"><tr class="month-calendar-head"><th align="left">$prev_month_link</th><th colspan="5"><a title="$monthname $year" href="$url">$monthname</a></th><th align="right">$next_month_link</th></tr>\n
430 #html year_head <table class="year-calendar"><tr class="year-calendar-head"><th align="left">$prev_year_link</th><th><a title="$year" href="$url">$year</a></th><th align="right">$next_year_link</th></tr><tr><th class="year-calendar-subhead" colspan=$months_per_row>Months</th></tr>\n
434 error month_head <table class="month-calendar"><caption class="month-calendar-head">$prev_month_link<a title="$monthname $year ($count)" href="$url">$monthname</a>$next_month_link</caption>\n
435 error month_sub_head <tr>\n
436 error month_sub_day <th class="month-calendar-day-head $downame">$dowabbr</th>\n
437 error month_sub_foot </tr>\n
438 error week_head <tr>\n
439 error noday <td class="month-calendar-day-noday $downame"> </td>\n
440 error day_link <td class="month-calendar-day-link $downame"><a title="$downame, $day $monthname $year ($count)" href="$url">$day</a></td>\n
441 error day_nolink <td class="month-calendar-day-nolink $downame">$day</td>\n
442 error day_future <td class="month-calendar-day-future $downame">$day</td>\n
443 error this_day_link <td class="month-calendar-day-this-day $downame"><a title="$downame, $day $monthname $year (current) ($count)" href="$url">$day</a></td>\n
444 error this_day_nolink <td class="month-calendar-day-this-day $downame">$day</td>\n
445 error week_foot </tr>\n
446 error month_foot </table>\n
447 error prev_month_link <a title="$monthname $year ($count)" href="$url">←</a>
448 error next_month_link <a title="$monthname $year ($count)" href="$url">→</a>
449 error prev_month_nolink ←
450 error next_month_nolink →
451 error year_head <table class="year-calendar"><caption class="year-calendar-head">$prev_year_link<a title="$year ($count)" href="$url">$year</a>$next_year_link</caption><tr><th class="year-calendar-subhead" colspan="$months_per_row">Months</th></tr>\n
452 error quarter_head <tr>\n
453 error month_link <td class="year-calendar-month-link"><a title="$monthname $year ($count)" href="$url">$monthabbr</a></td>\n
454 error month_nolink <td class="year-calendar-month-nolink">$monthabbr</td>\n
455 error month_future <td class="year-calendar-month-future">$monthabbr</td>\n
456 error this_month_link <td class="year-calendar-this-month"><a title="$monthname $year ($count)" href="$url">$monthabbr</a></td>
457 error this_month_nolink <td class="year-calendar-this-month">$monthabbr</td>
458 error quarter_foot </tr>\n
459 error year_foot </table>\n
460 error prev_year_link <a title="$year ($count)" href="$url">←</a>
461 error next_year_link <a title="$year ($count)" href="$url">→</a>
462 error prev_year_nolink ←
463 error next_year_nolink →
464 error calendar <div class="calendar"><table><tr><td>$calendar::month_calendar</td><td>$calendar::year_calendar</td></tr></table></div>
469 Blosxom Plug-in: calendar
473 Purpose: Provides a Radio-style archive navigation calendar
475 * $calendar::calendar -- side-by-side month and year calendars
476 * $calendar::month_calendar -- month calendar only
477 * $calendar::year_calendar -- year calendar only
478 * $calendar::year_calendar_2003 -- year calendar for 2003; these are built for every year with stories.
488 Todd Larason <jtl@molehill.org>, http://molelog.molehill.org/
490 This plugin is now maintained by the Blosxom Sourceforge Team,
491 <blosxom-devel@lists.sourceforge.net>.
495 None known; please send bug reports and feedback to the Blosxom
496 development mailing list <blosxom-devel@lists.sourceforge.net>.
500 =head2 Configuration variables
502 C<@monthname>, C<@monthabbr>, C<@downame> and C<@dowabbr> contain the
503 long and short forms of the names of the months and days of the week;
504 @downame and @dowabbr should always start with Sunday, regardless of
507 C<$first_dow> sets the plugin's idea of the first day day of the week;
508 use 0 for Sunday, 1 for Monday, and so on; using a number outside the
509 range [0..6] will cause undefined behavior, possibly including a
512 C<$use_caching> controls whether or not to try to cache statistics and
513 formatted results; caching requires Storable, but the plugin will work
514 just fine without it.
516 C<$months_per_row> controls how many months are on each row of the
517 year calendar. This should be a number that evenly divides 12 (1, 2,
518 3, 4, 6 or 12), but nothing checks for that.
520 C<$debug_level> can be set to a value between 0 and 5; 0 will output
521 no debug information, while 5 will be very verbose. The default is 1,
522 and should be changed after you've verified the plugin is working
525 =head2 Classes for CSS control
527 There's an (over)abundance of classes used, available for CSS customization.
529 * C<calendar> -- the calendar as a whole
530 * C<month-calendar> -- the month calendar as a whole
531 * C<month-calendar-head> -- the head of the month calendar (ie,
533 * C<month-calendar-day-head> -- a column head in the month calendar
534 (ie, a day-of-week abbreviation)
535 * C<month-calendar-day-noday>, C<month-calendar-day-link>,
536 C<month-calendar-day-nolink>, C<month-calendar-day-future>,
537 C<month-calendar-day-this-day> -- the day squares on the month
538 calendar, for days that don't exist (before or after the month
539 itself), that don't have stories, that do have stories, that are
540 in the future, or are that currently selected, respectively
541 * Day-of-week-name -- each day square is also given a class matching
542 its day of week, from C<@downame>; this can be used to hilight
544 * C<year-calendar> -- the year calendar as a whole
545 * C<year-calendar-head> -- the head of the year calendar (ie,
547 * C<year-calendar-subhead> -- ie, "Months"
548 * C<year-calendar-month-link>, C<year-calendar-month-nolink>,
549 C<year-calendar-month-future>, C<year-calendar-this-month> -- the
550 month squares on the year calendar, for months with stories,
551 without, in the future, and currently selected, respectively.
553 =head2 Flavour-style files
555 If you want a format change that can't be made by CSS, you can
556 override the HTML generated by creating files similar to Blosxom's
557 flavour files. They should be named calendar.I<bit>.I<flavour>; for
558 available I<bit>s and their default meanings, see the C<__DATA__>
559 section in the plugin.
563 1. Download and unpack (if you're reading this, you've probably
565 2. Copy it to your plugins directory. Make sure it's world-readable.
566 3. Modify a C<head> or C<foot> file to include C<$calendar::calendar>,
567 C<$calendar::month_calendar> or C<$calendar::year_calendar>
568 4. Try it out -- load your blog in your browser. If you see a calendar, great!
569 5. Look at your error log. Verify you have an 'enabled' line.
570 6. If you're wanting to verify caching is working, load the page
571 again, and now look for an error log line "calendar debug 1: Using
573 7. Once you're satisfied it's working, edit the C<$debug_level> configuration
574 variable to C<0>. There are a couple other configuration variables you may
579 If the Storable module is available and $use_caching is set, various
580 bits of data will be cached; this includes the information on what
581 days have stories (and are thus linkable, and what months and years
582 are included in the next/forward lists), the contents of any flavour
583 files, and the final formatted output of any calendars generated.
585 The cache will be flushed whenever a story is added (but not when one
586 is removed), so in normal use should be invisible. If you're making
587 template changes however, or are removing stories, you may wish to
588 either disable the cache (by setting $use_caching to 0) or manually
589 flush the cache; this can be done by removing
590 $plugin_state_dir/.calendar.cache, and is always safe to do.
595 Copyright 2003, Todd Larason
597 (This license is the same as Blosxom's)
599 Permission is hereby granted, free of charge, to any person obtaining a
600 copy of this software and associated documentation files (the "Software"),
601 to deal in the Software without restriction, including without limitation
602 the rights to use, copy, modify, merge, publish, distribute, sublicense,
603 and/or sell copies of the Software, and to permit persons to whom the
604 Software is furnished to do so, subject to the following conditions:
606 The above copyright notice and this permission notice shall be included
607 in all copies or substantial portions of the Software.
609 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
610 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
611 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
612 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
613 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
614 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
615 OTHER DEALINGS IN THE SOFTWARE.