| 1 | #!/usr/bin/perl |
|---|
| 2 | |
|---|
| 3 | # Blosxom |
|---|
| 4 | # Author: Rael Dornfest <rael@oreilly.com> |
|---|
| 5 | # Version: 2.0.2 |
|---|
| 6 | # Home/Docs/Licensing: http://www.blosxom.com/ |
|---|
| 7 | # Development/Downloads: http://sourceforge.net/projects/blosxom |
|---|
| 8 | |
|---|
| 9 | package blosxom; |
|---|
| 10 | |
|---|
| 11 | use strict; |
|---|
| 12 | use warnings; |
|---|
| 13 | |
|---|
| 14 | our $version = '2.0.2'; |
|---|
| 15 | |
|---|
| 16 | # -- require modules ----------------- # |
|---|
| 17 | |
|---|
| 18 | use FileHandle; |
|---|
| 19 | use DirHandle; |
|---|
| 20 | use FindBin (); |
|---|
| 21 | use File::Find; |
|---|
| 22 | use File::stat; |
|---|
| 23 | use Time::localtime; |
|---|
| 24 | use CGI qw( :standard :netscape ); |
|---|
| 25 | |
|---|
| 26 | # -- package variables --------------- # |
|---|
| 27 | |
|---|
| 28 | our ( |
|---|
| 29 | # weblog |
|---|
| 30 | $blog_title, $blog_description, $blog_language, $url, |
|---|
| 31 | # entries # flavour |
|---|
| 32 | $datadir, $depth, $file_extension, $flavour_dir, $default_flavour, |
|---|
| 33 | # view # plugins |
|---|
| 34 | $num_entries, $show_future_entries, $plugin_dir, $plugin_state_dir, @plugin_order, |
|---|
| 35 | # request |
|---|
| 36 | $path_info, $path_info_yr, $path_info_mo, $path_info_mo_num, $path_info_da, $flavour, |
|---|
| 37 | # response # system |
|---|
| 38 | $output, $header, %template, @plugins, %plugins, %files, %indexes, %others, |
|---|
| 39 | # variables # subroutines |
|---|
| 40 | %month2num, @num2month, $template, $interpolate, $entries, |
|---|
| 41 | # static |
|---|
| 42 | $static_or_dynamic, $static_dir, $static_password, @static_flavours, $static_entries, |
|---|
| 43 | ); |
|---|
| 44 | |
|---|
| 45 | my $fh = FileHandle->new; |
|---|
| 46 | |
|---|
| 47 | %month2num = ( nil => '00', Jan => '01', Feb => '02', Mar => '03', |
|---|
| 48 | Apr => '04', May => '05', Jun => '06', Jul => '07', |
|---|
| 49 | Aug => '08', Sep => '09', Oct => '10', Nov => '11', Dec => '12' ); |
|---|
| 50 | @num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num; |
|---|
| 51 | |
|---|
| 52 | # ------------------------------------ # |
|---|
| 53 | |
|---|
| 54 | # load configuration file |
|---|
| 55 | { |
|---|
| 56 | my $file = env_value('config') || "$FindBin::Bin/config.pl"; |
|---|
| 57 | eval { require $file }; |
|---|
| 58 | die "Failed to load configuration file: $file: $@" if ( $@ ); |
|---|
| 59 | } |
|---|
| 60 | |
|---|
| 61 | # Use the stated preferred URL or figure it out automatically |
|---|
| 62 | $url ||= url( -path_info => 1 ); |
|---|
| 63 | $url =~ s{^include:}{http:} if ( ( $ENV{'SERVER_PROTOCOL'} || q{} ) eq 'INCLUDED' ); |
|---|
| 64 | |
|---|
| 65 | # NOTE: Since v3.12, it looks as if CGI.pm misbehaves for SSIs and |
|---|
| 66 | # always appends path_info to the url. To fix this, we always |
|---|
| 67 | # request an url with path_info, and always remove it from the end of the |
|---|
| 68 | # string. |
|---|
| 69 | { |
|---|
| 70 | my $path_info = ( $ENV{'PATH_INFO'} || q{} ); |
|---|
| 71 | my $len = length $path_info; |
|---|
| 72 | my $frag = substr( $url, -$len ); |
|---|
| 73 | substr( $url, -$len ) = q{} if ( $frag eq $path_info ); |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | $url =~ s!/$!!; |
|---|
| 77 | |
|---|
| 78 | # Drop ending any / from dir settings |
|---|
| 79 | for ( $datadir, $flavour_dir, $plugin_dir, $static_dir ) { |
|---|
| 80 | $_ =~ s{/$}{}; |
|---|
| 81 | } |
|---|
| 82 | |
|---|
| 83 | # Fix depth to take into account datadir's path |
|---|
| 84 | $depth += ( $datadir =~ tr{/}{} ) -1 if ( $depth ); |
|---|
| 85 | |
|---|
| 86 | # Global variable to be used in head/foot.{flavour} templates |
|---|
| 87 | $path_info = q{}; |
|---|
| 88 | |
|---|
| 89 | $static_or_dynamic = ( !$ENV{'GATEWAY_INTERFACE'} |
|---|
| 90 | && param('-password') |
|---|
| 91 | && $static_password |
|---|
| 92 | && param('-password') eq $static_password ) |
|---|
| 93 | ? 'static' |
|---|
| 94 | : 'dynamic' |
|---|
| 95 | ; |
|---|
| 96 | param( -name => "-quiet", -value => 1 ) if ( $static_or_dynamic eq 'dynamic' ); |
|---|
| 97 | # Path Info Magic |
|---|
| 98 | # Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day |
|---|
| 99 | my @path_info = split m{/+}, path_info() || param('path') || q{}; |
|---|
| 100 | shift @path_info; |
|---|
| 101 | |
|---|
| 102 | while ( $path_info[0] && $path_info[0] =~ /^[a-zA-Z].*$/ && $path_info[0] !~ /^.*\..*$/) { |
|---|
| 103 | $path_info .= '/' . shift @path_info; |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | # Flavour specified by ?flav={flav} or index.{flav} |
|---|
| 107 | $flavour = q{}; |
|---|
| 108 | |
|---|
| 109 | if ( $path_info[$#path_info] && $path_info[$#path_info] =~ /^(.+)\.(.+?)$/ ) { |
|---|
| 110 | $flavour = $2; |
|---|
| 111 | $path_info .= "/$1.$2" if ( $1 ne 'index' ); |
|---|
| 112 | pop @path_info; |
|---|
| 113 | } |
|---|
| 114 | elsif ( ! -d "${datadir}${path_info}" ) { |
|---|
| 115 | $path_info .= ".${default_flavour}"; |
|---|
| 116 | $flavour = param('flav') || $default_flavour; |
|---|
| 117 | } |
|---|
| 118 | else { |
|---|
| 119 | $flavour = param('flav') || $default_flavour; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | # Strip spurious slashes |
|---|
| 123 | $path_info =~ s{^/*|/*$}{}g; |
|---|
| 124 | |
|---|
| 125 | # Date fiddling |
|---|
| 126 | ( $path_info_yr, $path_info_mo, $path_info_da ) = @path_info; |
|---|
| 127 | $path_info_mo_num = ( $path_info_mo ) |
|---|
| 128 | ? ( ( $path_info_mo =~ /\d{2}/ ) |
|---|
| 129 | ? $path_info_mo |
|---|
| 130 | : ($month2num{ucfirst(lc $path_info_mo)} || undef) ) |
|---|
| 131 | : undef ; |
|---|
| 132 | |
|---|
| 133 | $path_info_yr ||= q{}; |
|---|
| 134 | $path_info_mo_num ||= q{}; |
|---|
| 135 | $path_info_da ||= q{}; |
|---|
| 136 | |
|---|
| 137 | for ( $path_info, $path_info_yr, $path_info_mo, $path_info_mo_num, $path_info_da, $flavour ) { |
|---|
| 138 | $_ = html_escape( $_ ); |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | # Define standard template subroutine, plugin-overridable at Plugins: Template |
|---|
| 142 | $template = sub { |
|---|
| 143 | my ( $path, $chunk, $flavour ) = @_; |
|---|
| 144 | my $dir = $flavour_dir || $datadir; |
|---|
| 145 | $path ||= q{}; |
|---|
| 146 | do { |
|---|
| 147 | if ( $fh->open("${dir}/${path}/${chunk}.${flavour}", '<') ) { |
|---|
| 148 | my $file = do { local $/; <$fh> }; |
|---|
| 149 | $fh->close; |
|---|
| 150 | return $file; |
|---|
| 151 | } |
|---|
| 152 | } |
|---|
| 153 | while ( $path =~ s{(/*[^/]*)$}{} && $1 ); |
|---|
| 154 | |
|---|
| 155 | # Check for definedness, since flavour can be the empty string |
|---|
| 156 | if ( defined $template{$flavour}{$chunk} ) { |
|---|
| 157 | return $template{$flavour}{$chunk}; |
|---|
| 158 | } |
|---|
| 159 | elsif ( defined $template{'error'}{$chunk} ) { |
|---|
| 160 | return $template{'error'}{$chunk} |
|---|
| 161 | } |
|---|
| 162 | else { |
|---|
| 163 | return q{}; |
|---|
| 164 | } |
|---|
| 165 | }; |
|---|
| 166 | # Bring in the templates |
|---|
| 167 | %template = (); |
|---|
| 168 | while (<DATA>) { |
|---|
| 169 | last if ( $_ =~ m{^__END__$} ); |
|---|
| 170 | my ( $flav, $chunk, $text ) = ( $_ =~ m{^(\S+)\s(\S+)(?:\s(.*))?$} ) or next; |
|---|
| 171 | $text =~ s{\\n}{\n}mg; |
|---|
| 172 | $template{$flav}{$chunk} .= "${text}\n"; |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | # Plugins: Start |
|---|
| 176 | if ( $plugin_dir ) { |
|---|
| 177 | if ( @plugin_order > 0 ) { |
|---|
| 178 | for my $plugin ( @plugin_order ) { |
|---|
| 179 | require "${plugin_dir}/${plugin}"; |
|---|
| 180 | if ( $plugin->start() ) { |
|---|
| 181 | $plugins{$plugin} = 1; |
|---|
| 182 | push @plugins, $plugin; |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | } |
|---|
| 186 | elsif ( defined( my $dh = DirHandle->new( $plugin_dir ) ) ) { |
|---|
| 187 | for my $plugin ( grep { $_ =~ m{^\w+$} && -f "${plugin_dir}/${_}" } sort $dh->read ) { |
|---|
| 188 | next if ( $plugin =~ m{~$} ); # Ignore emacs backup |
|---|
| 189 | my ( $name, $off ) = ( $plugin =~ m{^\d*(\w+?)(_?)$} ); |
|---|
| 190 | my $on_off = ( $off eq '_' ) ? -1 : 1 ; |
|---|
| 191 | require "${plugin_dir}/${plugin}"; |
|---|
| 192 | if ( $name->start() ) { |
|---|
| 193 | $plugins{$name} = $on_off; |
|---|
| 194 | push @plugins, $name; |
|---|
| 195 | } |
|---|
| 196 | } |
|---|
| 197 | $dh->close; |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | # Plugins: Template |
|---|
| 202 | # Allow for the first encountered plugin::template subroutine to override the |
|---|
| 203 | # default built-in template subroutine |
|---|
| 204 | overwrite_sub('template'); |
|---|
| 205 | |
|---|
| 206 | # Provide backward compatibility for Blosxom < 2.0rc1 plug-ins |
|---|
| 207 | sub load_template { $template->( @_ ) } |
|---|
| 208 | |
|---|
| 209 | # Define default entries subroutine |
|---|
| 210 | $entries = sub { |
|---|
| 211 | my ( %files, %indexes, %others ); |
|---|
| 212 | find( |
|---|
| 213 | sub { |
|---|
| 214 | my $currnet = $File::Find::dir =~ tr{/}{}; |
|---|
| 215 | return if ( $depth && $currnet > $depth ); |
|---|
| 216 | |
|---|
| 217 | my $name = $File::Find::name; |
|---|
| 218 | |
|---|
| 219 | return if ( -d $name ); |
|---|
| 220 | return if ( ! -r $name ); |
|---|
| 221 | |
|---|
| 222 | my $time = time; |
|---|
| 223 | my $mtime = stat($name)->mtime; |
|---|
| 224 | |
|---|
| 225 | my ( $path, $fn ) |
|---|
| 226 | = ( $name =~ m{^$datadir/(?:(.*)/)?(.+)\.$file_extension$} ); |
|---|
| 227 | |
|---|
| 228 | if ( ! $fn && $fn eq 'index' || $fn =~ m{\.} ) { |
|---|
| 229 | $others{$name} = $mtime; |
|---|
| 230 | return; |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | return if ( ! $show_future_entries && $mtime > $time ); |
|---|
| 234 | $files{$name} = $mtime; |
|---|
| 235 | |
|---|
| 236 | if ( $static_dir && $static_flavours[0] ) { |
|---|
| 237 | my $check = "${static_dir}/${path}/index.$static_flavours[0]"; |
|---|
| 238 | if ( param('-all') || ! -f $check || stat($check)->mtime < $mtime ) { |
|---|
| 239 | $indexes{$path} = 1; |
|---|
| 240 | my $dir = join q{/}, (nice_date($mtime))[5,2,3]; |
|---|
| 241 | $indexes{$dir} = $dir; |
|---|
| 242 | $indexes{( $path ? '${path}/' : '' )."${fn}.${file_extension}"} = 1 |
|---|
| 243 | if ( $static_entries ); |
|---|
| 244 | } |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | }, |
|---|
| 248 | $datadir, |
|---|
| 249 | ); |
|---|
| 250 | |
|---|
| 251 | return ( \%files, \%indexes, \%others, ); |
|---|
| 252 | }; |
|---|
| 253 | |
|---|
| 254 | # Plugins: Entries |
|---|
| 255 | # Allow for the first encountered plugin::entries subroutine to override the |
|---|
| 256 | # default built-in entries subroutine |
|---|
| 257 | overwrite_sub('entries'); |
|---|
| 258 | |
|---|
| 259 | my ( $files, $indexes, $others ) = $entries->(); |
|---|
| 260 | %indexes = %{ $indexes }; |
|---|
| 261 | |
|---|
| 262 | # Static |
|---|
| 263 | if ( $static_or_dynamic eq 'static' ) { |
|---|
| 264 | print "Blosxom is generating static index pages...\n" if ( ! param('-quiet') ); |
|---|
| 265 | |
|---|
| 266 | # Home Page and Directory Indexes |
|---|
| 267 | my %done; |
|---|
| 268 | for my $item ( sort keys %indexes ) { |
|---|
| 269 | my $path = q{}; |
|---|
| 270 | for ( '', split m{/+}, $item ) { |
|---|
| 271 | $path .= "/$_"; |
|---|
| 272 | $path =~ s{^/}{}; |
|---|
| 273 | $done{$path}++ and next; |
|---|
| 274 | if ( ! -d "${static_dir}/${path}" && $path !~ m{\.$file_extension$} ) { |
|---|
| 275 | mkdir "${static_dir}/${path}", 0755; |
|---|
| 276 | } |
|---|
| 277 | for $flavour ( @static_flavours ) { |
|---|
| 278 | my $content_type = $template->( $path, 'content_type', $flavour ); |
|---|
| 279 | $content_type =~ s{\n.*}{}s; |
|---|
| 280 | my $fn = ( $path =~ m{^(.+)\.$file_extension} ) ? $1 : "${path}/index" ; |
|---|
| 281 | print "${fn}.${flavour}\n" if ( ! param('-quiet') ); |
|---|
| 282 | $fh->open( "${static_dir}/${fn}.${flavour}", '>' ) |
|---|
| 283 | or die "Couldn't open ${static_dir}/${path} for writing: $!"; |
|---|
| 284 | $output = q{}; |
|---|
| 285 | # dir, entry |
|---|
| 286 | if ( $indexes{$path} == 1 ) { |
|---|
| 287 | $path_info = $path; |
|---|
| 288 | $path_info =~ s{\.$file_extension}{.$flavour}; |
|---|
| 289 | print $fh &generate('static', $path_info, '', $flavour, $content_type); |
|---|
| 290 | } |
|---|
| 291 | # date |
|---|
| 292 | else { |
|---|
| 293 | local ( $path_info_yr, $path_info_mo, $path_info_da, $path_info ) |
|---|
| 294 | = split m{/+}, $path, 4; |
|---|
| 295 | $path_info = q{} if ( ! defined $path_info ); |
|---|
| 296 | print $fh &generate('static', '', $path, $flavour, $content_type ); |
|---|
| 297 | } |
|---|
| 298 | $fh->close; |
|---|
| 299 | } |
|---|
| 300 | } |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | # Dynamic |
|---|
| 304 | else { |
|---|
| 305 | my $content_type = $template->( $path_info, 'content_type', $flavour ); |
|---|
| 306 | $content_type =~ s{\n.*}{}s; |
|---|
| 307 | |
|---|
| 308 | $header = { -type => $content_type }; |
|---|
| 309 | |
|---|
| 310 | print generate('dynamic', $path_info, "$path_info_yr/$path_info_mo_num/$path_info_da", $flavour, $content_type); |
|---|
| 311 | } |
|---|
| 312 | |
|---|
| 313 | # Plugins: End |
|---|
| 314 | run_plugins('end'); |
|---|
| 315 | |
|---|
| 316 | # Generate |
|---|
| 317 | sub generate { |
|---|
| 318 | my ( $static_or_dynamic, $currentdir, $date, $flavour, $content_type ) = @_; |
|---|
| 319 | |
|---|
| 320 | %files = %{ $files }; |
|---|
| 321 | %others = ref $others ? %{ $others } : (); |
|---|
| 322 | |
|---|
| 323 | # Plugins: Filter |
|---|
| 324 | run_plugins( filter => \%files, \%others ); |
|---|
| 325 | |
|---|
| 326 | my %f = %files; |
|---|
| 327 | |
|---|
| 328 | # Plugins: Skip |
|---|
| 329 | # Allow plugins to decide if we can cut short story generation |
|---|
| 330 | my $skip; |
|---|
| 331 | if ( defined( my $flag = run_plugins_first('skip') ) ) { |
|---|
| 332 | $skip = $flag; |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | # Define default interpolation subroutine |
|---|
| 336 | $interpolate = sub { |
|---|
| 337 | package blosxom; |
|---|
| 338 | my $tmpl = shift; |
|---|
| 339 | $tmpl =~ s{(\$\w+(?:::)?\w*)}{"defined $1 ? $1 : ''"}gee; |
|---|
| 340 | return $tmpl; |
|---|
| 341 | }; |
|---|
| 342 | |
|---|
| 343 | if ( ! defined $skip || ! $skip ) { |
|---|
| 344 | # Plugins: Interpolate |
|---|
| 345 | # Allow for the first encountered plugin::interpolate subroutine to |
|---|
| 346 | # override the default built-in interpolate subroutine |
|---|
| 347 | overwrite_sub('interpolate'); |
|---|
| 348 | |
|---|
| 349 | # Head |
|---|
| 350 | my $head = $template->( $currentdir, 'head', $flavour ); |
|---|
| 351 | # Plugins: Head |
|---|
| 352 | run_plugins( head => $currentdir, \$head ); |
|---|
| 353 | $head = $interpolate->($head); |
|---|
| 354 | $output .= $head; |
|---|
| 355 | |
|---|
| 356 | # Stories |
|---|
| 357 | my $curdate = q{}; |
|---|
| 358 | my $ne = $num_entries; |
|---|
| 359 | if ( $currentdir =~ m{(.*?)([^/]+)\.(.+)$} && $2 ne 'index' ) { |
|---|
| 360 | $currentdir = "$1$2.$file_extension"; |
|---|
| 361 | my $fullpath = "${datadir}/${currentdir}"; |
|---|
| 362 | %f = ( $fullpath => $files{$fullpath} ) if ( $files{$fullpath} ); |
|---|
| 363 | } |
|---|
| 364 | else { |
|---|
| 365 | $currentdir =~ s{/index\..+$}{}; |
|---|
| 366 | } |
|---|
| 367 | |
|---|
| 368 | # Define a default sort subroutine |
|---|
| 369 | my $sort = sub { |
|---|
| 370 | my ( $files_ref ) = @_; |
|---|
| 371 | return sort { $files_ref->{$b} <=> $files_ref->{$a} } keys %{ $files_ref }; |
|---|
| 372 | }; |
|---|
| 373 | # Plugins: Sort |
|---|
| 374 | # Allow for the first encountered plugin::sort subroutine to override the |
|---|
| 375 | # default built-in sort subroutine |
|---|
| 376 | if ( defined( my $sub = run_plugins_first('sort') ) ) { |
|---|
| 377 | $sort = $sub; |
|---|
| 378 | } |
|---|
| 379 | |
|---|
| 380 | for my $path_file ( $sort->( \%f, \%others ) ) { |
|---|
| 381 | last if ( $ne <= 0 && $date !~ /\d/ ); |
|---|
| 382 | use vars qw( $path $fn ); |
|---|
| 383 | ( $path, $fn ) |
|---|
| 384 | = ( $path_file =~ m{^$datadir/(?:(.*)/)?(.*)\.$file_extension} ); |
|---|
| 385 | $path ||= q{}; |
|---|
| 386 | # Only stories in the right hierarchy |
|---|
| 387 | if ( $path !~ m{^$currentdir} && $path_file ne "${datadir}/${currentdir}" ) { |
|---|
| 388 | next; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | # Prepend a slash for use in templates only if a path exists |
|---|
| 392 | $path &&= "/$path"; |
|---|
| 393 | |
|---|
| 394 | # Date fiddling for by-{year,month,day} archive views |
|---|
| 395 | use vars qw/ $dw $mo $mo_num $da $ti $yr $hr $min $hr12 $ampm /; |
|---|
| 396 | ( $dw, $mo, $mo_num, $da, $ti, $yr ) = nice_date( $files{"$path_file"} ); |
|---|
| 397 | ( $hr, $min) = split m{:}, $ti; |
|---|
| 398 | ( $hr12, $ampm ) = ( $hr >= 12 ) ? ( $hr - 12,'pm' ) : ( $hr, 'am' ) ; |
|---|
| 399 | $hr12 =~ s{^0}{}; |
|---|
| 400 | $hr12 = 12 if ( $hr12 == 0 ); |
|---|
| 401 | |
|---|
| 402 | # Only stories from the right date |
|---|
| 403 | my ( $path_info_yr, $path_info_mo_num, $path_info_da ) = split m{/+}, $date; |
|---|
| 404 | next if ( $path_info_yr && $yr != $path_info_yr ); |
|---|
| 405 | last if ( $path_info_yr && $yr < $path_info_yr ); |
|---|
| 406 | next if ( $path_info_mo_num && $mo ne $num2month[$path_info_mo_num] ); |
|---|
| 407 | next if ( $path_info_da && $da != $path_info_da ); |
|---|
| 408 | last if ( $path_info_da && $da < $path_info_da ); |
|---|
| 409 | |
|---|
| 410 | # Date |
|---|
| 411 | my $date = $template->( $path, 'date', $flavour ); |
|---|
| 412 | |
|---|
| 413 | # Plugins: Date |
|---|
| 414 | run_plugins( date => $currentdir, \$date, $files{$path_file}, $dw, $mo, $mo_num, $da, $ti, $yr ); |
|---|
| 415 | $date = $interpolate->($date); |
|---|
| 416 | |
|---|
| 417 | if ( $curdate ne $date ) { |
|---|
| 418 | $curdate = $date; |
|---|
| 419 | $output .= $date; |
|---|
| 420 | } |
|---|
| 421 | |
|---|
| 422 | use vars qw( $title $body $raw ); |
|---|
| 423 | if ( -f $path_file && $fh->open( $path_file, '<' ) ) { |
|---|
| 424 | chomp( $title = <$fh> ); |
|---|
| 425 | chomp( $body = do { local $/; <$fh> } ); |
|---|
| 426 | $fh->close; |
|---|
| 427 | $raw = "${title}\n{$body}"; |
|---|
| 428 | } |
|---|
| 429 | my $story = $template->( $path, 'story', $flavour ); |
|---|
| 430 | |
|---|
| 431 | # Plugins: Story |
|---|
| 432 | run_plugins( story => $path, $fn, \$story, \$title, \$body ); |
|---|
| 433 | |
|---|
| 434 | if ( $content_type =~ m{\bxml\b} ) { |
|---|
| 435 | # Escape <, >, and &, and to produce valid RSS |
|---|
| 436 | for ( $title, $body ) { |
|---|
| 437 | $_ = html_escape( $_ ); |
|---|
| 438 | } |
|---|
| 439 | } |
|---|
| 440 | |
|---|
| 441 | $story = $interpolate->( $story ); |
|---|
| 442 | $output .= $story; |
|---|
| 443 | $ne--; |
|---|
| 444 | } |
|---|
| 445 | |
|---|
| 446 | # Foot |
|---|
| 447 | my $foot = $template->( $currentdir, 'foot', $flavour ); |
|---|
| 448 | |
|---|
| 449 | # Plugins: Foot |
|---|
| 450 | run_plugins( foot => $currentdir, \$foot ); |
|---|
| 451 | $foot = $interpolate->( $foot ); |
|---|
| 452 | $output .= $foot; |
|---|
| 453 | |
|---|
| 454 | # Plugins: Last |
|---|
| 455 | run_plugins('last'); |
|---|
| 456 | }# End skip |
|---|
| 457 | |
|---|
| 458 | # Finally, add the header, if any and running dynamically |
|---|
| 459 | $output = header( $header ) . $output |
|---|
| 460 | if ( $static_or_dynamic eq 'dynamic' && $header ); |
|---|
| 461 | return $output; |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | sub run_plugins { |
|---|
| 465 | my ( $method, @args ) = @_; |
|---|
| 466 | |
|---|
| 467 | for my $plugin ( @plugins ) { |
|---|
| 468 | if ( $plugins{$plugin} > 0 && $plugin->can($method) ) { |
|---|
| 469 | $entries = $plugin->$method( @args ); |
|---|
| 470 | } |
|---|
| 471 | } |
|---|
| 472 | } |
|---|
| 473 | |
|---|
| 474 | sub run_plugins_first { |
|---|
| 475 | my ( $method, @args ) = @_; |
|---|
| 476 | |
|---|
| 477 | for my $plugin ( @plugins ) { |
|---|
| 478 | if ( $plugins{$plugin} > 0 |
|---|
| 479 | && $plugin->can($method) |
|---|
| 480 | && defined( my $ret = $plugin->$method( @args ) ) ) { |
|---|
| 481 | return $ret; |
|---|
| 482 | } |
|---|
| 483 | } |
|---|
| 484 | |
|---|
| 485 | return; |
|---|
| 486 | } |
|---|
| 487 | |
|---|
| 488 | sub overwrite_sub { |
|---|
| 489 | my ( $method, @args ) = @_; |
|---|
| 490 | |
|---|
| 491 | if ( defined( my $sub = run_plugins_first( $method => @args ) ) ) { |
|---|
| 492 | die "Return value of plugin is not CODE reference: method:$method" |
|---|
| 493 | if ( ref $sub ne 'CODE' ); |
|---|
| 494 | no strict 'refs'; |
|---|
| 495 | ${"${method}"} = $sub; |
|---|
| 496 | } |
|---|
| 497 | } |
|---|
| 498 | |
|---|
| 499 | sub nice_date { |
|---|
| 500 | my ( $unixtime ) = @_; |
|---|
| 501 | |
|---|
| 502 | my $ctime = ctime($unixtime); |
|---|
| 503 | my ( $dw, $mo, $da, $ti, $yr ) |
|---|
| 504 | = ( $ctime =~ m{(\w{3})[ ]+(\w{3})[ ]+(\d{1,2})[ ]+(\d{2}:\d{2}):\d{2}[ ]+(\d{4})$} ); |
|---|
| 505 | $da = sprintf('%02d', $da); |
|---|
| 506 | my $mo_num = $month2num{$mo}; |
|---|
| 507 | |
|---|
| 508 | return ( $dw, $mo, $mo_num, $da, $ti, $yr ); |
|---|
| 509 | } |
|---|
| 510 | |
|---|
| 511 | sub env_value { |
|---|
| 512 | my $prefix = uc __PACKAGE__; |
|---|
| 513 | my $key = uc( shift @_ ); |
|---|
| 514 | |
|---|
| 515 | my $env = "${prefix}_${key}"; |
|---|
| 516 | |
|---|
| 517 | if ( exists $ENV{$env} ) { |
|---|
| 518 | return $ENV{$env}; |
|---|
| 519 | } |
|---|
| 520 | return; |
|---|
| 521 | } |
|---|
| 522 | |
|---|
| 523 | |
|---|
| 524 | |
|---|
| 525 | sub html_escape { |
|---|
| 526 | my ( $str ) = @_; |
|---|
| 527 | |
|---|
| 528 | my %escape = ( |
|---|
| 529 | '<' => '<', |
|---|
| 530 | '>' => '>', |
|---|
| 531 | '&' => '&', |
|---|
| 532 | '"' => '"', |
|---|
| 533 | "'" => ''', |
|---|
| 534 | ); |
|---|
| 535 | |
|---|
| 536 | my $escape_re = join q{|}, keys %escape; |
|---|
| 537 | |
|---|
| 538 | $str =~ s{($escape_re)}{$escape{$1}}sg; |
|---|
| 539 | |
|---|
| 540 | return $str; |
|---|
| 541 | } |
|---|
| 542 | |
|---|
| 543 | # Default HTML and RSS template bits |
|---|
| 544 | __DATA__ |
|---|
| 545 | html content_type text/html |
|---|
| 546 | |
|---|
| 547 | html head <html> |
|---|
| 548 | html head <head> |
|---|
| 549 | html head <link rel="alternate" type="type="application/rss+xml" title="RSS" href="$url/index.rss" /> |
|---|
| 550 | html head <title>$blog_title $path_info_da $path_info_mo $path_info_yr |
|---|
| 551 | html head </title> |
|---|
| 552 | html head </head> |
|---|
| 553 | html head <body> |
|---|
| 554 | html head <center> |
|---|
| 555 | html head <font size="+3">$blog_title</font><br /> |
|---|
| 556 | html head $path_info_da $path_info_mo $path_info_yr |
|---|
| 557 | html head </center> |
|---|
| 558 | html head <p /> |
|---|
| 559 | |
|---|
| 560 | html story <p> |
|---|
| 561 | html story <a name="$fn"><b>$title</b></a><br /> |
|---|
| 562 | html story $body<br /> |
|---|
| 563 | html story <br /> |
|---|
| 564 | html story posted at: $ti | path: <a href="$url$path">$path </a> | <a href="$url/$yr/$mo_num/$da#$fn">permanent link to this entry</a> |
|---|
| 565 | html story </p> |
|---|
| 566 | |
|---|
| 567 | html date <h3>$dw, $da $mo $yr</h3> |
|---|
| 568 | |
|---|
| 569 | html foot |
|---|
| 570 | html foot <p /> |
|---|
| 571 | html foot <center> |
|---|
| 572 | html foot <a href="http://www.blosxom.com/"><img src="http://www.blosxom.com/images/pb_blosxom.gif" border="0" /></a> |
|---|
| 573 | html foot </center> |
|---|
| 574 | html foot </body> |
|---|
| 575 | html foot </html> |
|---|
| 576 | |
|---|
| 577 | rss content_type text/xml |
|---|
| 578 | |
|---|
| 579 | rss head <?xml version="1.0"?> |
|---|
| 580 | rss head <!-- name="generator" content="blosxom/$version" --> |
|---|
| 581 | rss head <!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN" "http://my.netscape.com/publish/formats/rss-0.91.dtd"> |
|---|
| 582 | rss head |
|---|
| 583 | rss head <rss version="0.91"> |
|---|
| 584 | rss head <channel> |
|---|
| 585 | rss head <title>$blog_title $path_info_da $path_info_mo $path_info_yr</title> |
|---|
| 586 | rss head <link>$url</link> |
|---|
| 587 | rss head <description>$blog_description</description> |
|---|
| 588 | rss head <language>$blog_language</language> |
|---|
| 589 | |
|---|
| 590 | rss story <item> |
|---|
| 591 | rss story <title>$title</title> |
|---|
| 592 | rss story <link>$url/$yr/$mo_num/$da#$fn</link> |
|---|
| 593 | rss story <description>$body</description> |
|---|
| 594 | rss story </item> |
|---|
| 595 | |
|---|
| 596 | rss date |
|---|
| 597 | |
|---|
| 598 | rss foot </channel> |
|---|
| 599 | rss foot </rss> |
|---|
| 600 | |
|---|
| 601 | error content_type text/html |
|---|
| 602 | |
|---|
| 603 | error head <html> |
|---|
| 604 | error head <body> |
|---|
| 605 | error head <p><font color="red">Error: I'm afraid this is the first I've heard of a "$flavour" flavoured Blosxom. Try dropping the "/+$flavour" bit from the end of the URL.</font> |
|---|
| 606 | |
|---|
| 607 | |
|---|
| 608 | error story <p><b>$title</b><br /> |
|---|
| 609 | error story $body <a href="$url/$yr/$mo_num/$da#fn.$default_flavour">#</a></p> |
|---|
| 610 | |
|---|
| 611 | error date <h3>$dw, $da $mo $yr</h3> |
|---|
| 612 | |
|---|
| 613 | error foot </body> |
|---|
| 614 | error foot </html> |
|---|
| 615 | __END__ |
|---|
| 616 | |
|---|
| 617 | =head1 AUTHOR |
|---|
| 618 | |
|---|
| 619 | Original script by Rael Dornfest |
|---|
| 620 | |
|---|
| 621 | Based on blosxom 2.0.2 in SourceForge.net |
|---|
| 622 | |
|---|
| 623 | Modified by Naoki Okamura (Nyarla) E<lt>thotep@nyarla.netE<gt> |
|---|