Annotated blosxom.cgi

    1  #!/usr/bin/perl
    2  
    3  # Blosxom                                                               # [1] [2] [3]
    4  # Author: Rael Dornfest <rael@oreilly.com>
    5  # Version: 2.0
    6  # Home/Docs/Licensing: http://www.raelity.org/apps/blosxom/
    7  
    8  package blosxom;                                                        # [4]
    9  
   10  # --- Configurable variables -----                                      # [5]
   11  
   12  # What's this blog's title?
   13  $blog_title = "My Weblog";                                              # [6]
   14  
   15  # What's this blog's description (for outgoing RSS feed)?
   16  $blog_description = "Yet another Blosxom weblog.";
   17  
   18  # What's this blog's primary language (for outgoing RSS feed)?
   19  $blog_language = "en";
   20  
   21  # Where are this blog's entries kept?
   22  $datadir = "/Library/WebServer/Documents/blosxom";
   23  
   24  # What's my preferred base URL for this blog (leave blank for automatic)?
   25  $url = "";
   26  
   27  # Should I stick only to the datadir for items or travel down the
   28  # directory hierarchy looking for items?  If so, to what depth?
   29  # 0 = infinite depth (aka grab everything), 1 = datadir only, n = n levels down
   30  $depth = 0;
   31  
   32  # How many entries should I show on the home page?
   33  $num_entries = 40;
   34  
   35  # What file extension signifies a blosxom entry?
   36  $file_extension = "txt";
   37  
   38  # What is the default flavour?
   39  $default_flavour = "html";
   40  
   41  # Should I show entries from the future (i.e. dated after now)?
   42  $show_future_entries = 0;
   43  
   44  # --- Plugins (Optional) -----
   45  
   46  # Where are my plugins kept?
   47  $plugin_dir = "";
   48  
   49  # Where should my modules keep their state information?
   50  $plugin_state_dir = "$plugin_dir/state";
   51  
   52  # --- Static Rendering -----
   53  
   54  # Where are this blog's static files to be created?
   55  $static_dir = "/Library/WebServer/Documents/blog";
   56  
   57  # What's my administrative password (you must set this for static rendering)?
   58  $static_password = "";
   59  
   60  # What flavours should I generate statically?
   61  @static_flavours = qw/html rss/;                                        # [7]
   62  
   63  # Should I statically generate individual entries?
   64  # 0 = no, 1 = yes
   65  $static_entries = 0;
   66  
   67  # --------------------------------
   68  
   69  use vars qw! $version $blog_title $blog_description $blog_language $datadir $url %template $template $depth $num_entries $file_extension $default_flavour $static_or_dynamic $plugin_dir $plugin_state_dir @plugins %plugins $static_dir $static_password @static_flavours $static_entries $path_info $path_info_yr $path_info_mo $path_info_da $path_info_mo_num $flavour $static_or_dynamic %month2num @num2month $interpolate $entries $output $header $show_future_entries %files %indexes %others !;  # [8]
   70  
   71  use strict;                                                             # [9]
   72  use FileHandle;                                                         # [10] [11]
   73  use File::Find;                                                         # [12]
   74  use File::stat;                                                         # [13]
   75  use Time::localtime;                                                    # [14]
   76  use CGI qw/:standard :netscape/;                                        # [15]
   77  
   78  $version = "2.0";                                                       # [16]
   79  
   80  my $fh = new FileHandle;                                                # [17] [18]
   81  
   82  %month2num = (nil=>'00', Jan=>'01', Feb=>'02', Mar=>'03', Apr=>'04', May=>'05', Jun=>'06', Jul=>'07', Aug=>'08', Sep=>'09', Oct=>'10', Nov=>'11', Dec=>'12');  # [19]
   83  @num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num;  # [20]
   84  
   85  # Use the stated preferred URL or figure it out automatically
   86  $url ||= url();                                                         # [21] [22]
   87  $url =~ s/^included:/http:/; # Fix for Server Side Includes (SSI)       # [23] [24]
   88  $url =~ s!/$!!;                                                         # [25]
   89  
   90  # Drop ending any / from dir settings
   91  $datadir =~ s!/$!!; $plugin_dir =~ s!/$!!; $static_dir =~ s!/$!!;
   92    
   93  # Fix depth to take into account datadir's path
   94  $depth and $depth += ($datadir =~ tr[/][]) - 1;                         # [26] [27]
   95  
   96  # Global variable to be used in head/foot.{flavour} templates
   97  $path_info = '';
   98  
   99  $static_or_dynamic = (!$ENV{GATEWAY_INTERFACE} and param('-password') and $static_password and param('-password') eq $static_password) ? 'static' : 'dynamic';  # [28] [29]
  100  $static_or_dynamic eq 'dynamic' and param(-name=>'-quiet', -value=>1);  # [30]
  101  
  102  # Path Info Magic
  103  # Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day
  104  my @path_info = split m{/}, path_info() || param('path');               # [31] [32]
  105  shift @path_info;                                                       # [33]
  106  
  107  while ($path_info[0] and $path_info[0] =~ /^[a-zA-Z].*$/ and $path_info[0] !~ /(.*)\.(.*)/) { $path_info .= '/' . shift @path_info; }  # [34] [35]
  108  
  109  # Flavour specified by ?flav={flav} or index.{flav}
  110  $flavour = '';
  111  
  112  if ( $path_info[$#path_info] =~ /(.+)\.(.+)$/ ) {                       # [36] [37]
  113    $flavour = $2;                                                        # [38]
  114    $1 ne 'index' and $path_info .= "/$1.$2";                             # [39]
  115    pop @path_info;                                                       # [40]
  116  } else {
  117    $flavour = param('flav') || $default_flavour;                         # [41]
  118  }
  119  
  120  # Strip spurious slashes
  121  $path_info =~ s!(^/*)|(/*$)!!g;                                         # [42]
  122  
  123  # Date fiddling
  124  ($path_info_yr,$path_info_mo,$path_info_da) = @path_info;               # [43] [44]
  125  $path_info_mo_num = $path_info_mo ? ( $path_info_mo =~ /\d{2}/ ? $path_info_mo : ($month2num{ucfirst(lc $path_info_mo)} || undef) ) : undef;  # [45] [46]
  126  
  127  # Define standard template subroutine, plugin-overridable at Plugins: Template
  128  $template =                                                             # [47] [48]
  129    sub {
  130      my ($path, $chunk, $flavour) = @_;                                  # [49] [50]
  131  
  132      do {                                                                # [51]
  133        return join '', <$fh> if $fh->open("< $datadir/$path/$chunk.$flavour");  # [52]
  134      } while ($path =~ s/(\/*[^\/]*)$// and $1);                         # [53]
  135  
  136      return join '', ($template{$flavour}{$chunk} || $template{error}{$chunk} || '');  # [54]
  137    };
  138  # Bring in the templates
  139  %template = ();                                                         # [55]
  140  while (<DATA>) {                                                        # [56] [57]
  141    last if /^(__END__)?$/;                                               # [58]
  142    my($ct, $comp, $txt) = /^(\S+)\s(\S+)\s(.*)$/;                        # [59]
  143    $txt =~ s/\\n/\n/mg;                                                  # [60]
  144    $template{$ct}{$comp} = $txt;                                         # [61]
  145  }
  146  
  147  # Plugins: Start
  148  if ( $plugin_dir and opendir PLUGINS, $plugin_dir ) {                   # [62]
  149    foreach my $plugin ( grep { /^\w+$/ && -f "$plugin_dir/$_"  } sort readdir(PLUGINS) ) {  # [63]
  150      my($plugin_name, $off) = $plugin =~ /^\d*(\w+?)(_?)$/;              # [64]
  151      my $on_off = $off eq '_' ? -1 : 1;                                  # [65]
  152      require "$plugin_dir/$plugin";                                      # [66]
  153      $plugin_name->start() and ( $plugins{$plugin_name} = $on_off ) and push @plugins, $plugin_name;  # [67]
  154    }
  155    closedir PLUGINS;                                                     # [68]
  156  }
  157  
  158  # Plugins: Template
  159  # Allow for the first encountered plugin::template subroutine to override the
  160  # default built-in template subroutine
  161  my $tmp; foreach my $plugin ( @plugins ) { $plugins{$plugin} > 0 and $plugin->can('template') and defined($tmp = $plugin->template()) and $template = $tmp and last; }  # [69] [70] [71]
  162  
  163  # Provide backward compatibility for Blosxom < 2.0rc1 plug-ins
  164  sub load_template {                                                     # [72]
  165    return &$template(@_);
  166  }
  167  
  168  # Define default find subroutine
  169  $entries =                                                              # [73]
  170    sub {
  171      my(%files, %indexes, %others);                                      # [74]
  172      find(                                                               # [75]
  173        sub {
  174          my $d; 
  175          my $curr_depth = $File::Find::dir =~ tr[/][];                   # [76]
  176          return if $depth and $curr_depth > $depth;                      # [77]
  177       
  178          if (                                                            # [78]
  179            # a match