tagging: Allow using titles in for related stories.
[matthijs/upstream/blosxom-plugins.git] / general / entries_cache
1 # Blosxom Plugin: entries_cache -*- perl -*-
2 # Author(s): Fletcher T. Penney <http://fletcher.freeshell.org/> 
3 # Modified by Steve Schwarz <http://agilitynerd.com/>
4 # Version: 0.93
5 # Based on entries_index by Rael Dornfest
6 # Documentation help contributed by Iain Cheyne
7
8 package entries_cache;
9
10 # --- Configurable variables -----
11
12 $delay = 0 unless defined $delay;
13                         # How many minutes delay before entries are re-indexed?
14                         # Set to 0 to force reindexing everytime - this
15                         # will provide the same behavior as Rael's
16                         # entries_index.  Though, I am not sure why one 
17                         # would do this.  At least use a small cache time to
18                         # improve performance....
19                         # SAS - I set this to zero so comments and new files
20                         # are immediately visible.
21 $indexname = "$blosxom::plugin_state_dir/.entries_cache.index" unless defined $indexname;
22 $others_indexname = "$blosxom::plugin_state_dir/.entries_cache.others" unless defined $others_indexname;
23
24
25 $use_date_tags = 1 unless defined $use_date_tags;
26         # Set to 1 to enable parsing meta- keywords
27         # for date tags
28
29 $use_UK_dates = 0 unless defined $use_UK_dates;
30         # Default is mm/dd/yy (US)
31         # Set to 1 to use dd/mm/yy (UK)
32
33 $update_meta_date = 0 unless defined $update_meta_date;
34         # Add a meta_date tag if it doesn't exist
35         # This will require that perl has write access to your story files
36         # and that you are using the meta plugin.
37         
38         # NOTE: As of version 0.91, you do not NEED the meta plugin...
39
40         # Be sure to save your text files with UNIX line endings
41         # (any decent text editor should be able to do this) or use the blok
42         # plugin (http://www.enilnomi.net/download.html#blok).
43         # There is some logic in entries_cache to fix this, but do not rely
44         # on it.
45
46         # Lastly, make sure there is at least one blank line between the
47         # entry title and the body. Again, there is some logic in
48         # entries_cache to fix this, but do not rely on it.
49         
50         # The default meta-keywords are compatible with Eric Sherman's
51         # entries_index_tagged defaults:
52         
53         # http://primitiveworker.org/blo.g/development/blosxom/entries_index_tagged/
54
55 $meta_timestamp = "meta-creation_timestamp:" unless defined $meta_timestamp;
56         # timestamp_tag is the non-human readable date stamp format
57         # used by entries_index, entries_cache, entries_index_tagged,
58         # and blosxom
59
60 $meta_date = "meta-creation_date:" unless defined $meta_date;
61         # date_tag is a human readable version
62
63 $strip_meta_dates = 1 unless defined $strip_meta_dates; 
64         # Strip meta-tags from story so that 
65         # they are not displayed.  Unnecessary if you are running the 
66         # meta plugin.
67
68 $update_static_rendering = 0 unless defined $update_static_rendering;
69         # Recreate statically rendered files.
70         # When set to 0, Blosxom will not update statically rendered files
71         # even when stories are edited afterwards (Fletcher Penney's default).
72         # Set to 1 if you want to be able to fix typos in statically rendered stories.
73
74 $index_umask = oct(007) unless defined $index_umask;
75         # Mask for index files : default allows Read/Write for owner and group,
76         # nothing for others.
77
78 $debug = 0 unless defined $debug;
79         # Debugging flag
80
81 # --------------------------------
82
83 use File::stat;
84 use File::Find;
85 use CGI qw/:standard/;
86 use Time::Local;
87
88 my $time = time();
89 my $reindex = 0;
90
91 sub start {
92         # Force a reindex
93         $reindex = 1 if (CGI::param('reindex'));
94         return 1;
95 }
96
97
98 sub entries {
99         
100         return sub {
101                 my(%files, %indexes, %others);
102         
103                 # Read cached index
104                 if ( open CACHE, $indexname) {
105                         while ($line = <CACHE>) {
106                                 # Improved backwards compatibility with entries_index
107                                 if ($line =~ /\s*'?(.*?)'?\s*=>\s*(\d*),?/) {
108                                         $files{$1} = $2;
109                                 }
110                         }
111                         close CACHE;
112                         # See if it's time to reindex
113                         $reindex = 1 if ( stat($indexname)->mtime lt ($time - $delay*60) );
114                 } else {
115                         # No index found, so we need to index
116                         $reindex = 1;
117                 }
118
119                 # Read cached others index
120                 if ( open CACHE, $others_indexname) {
121                         while ($line = <CACHE>) {
122                                 # Improved backwards compatibility with entries_index
123                                 if ($line =~ /\s*'?(.*)'?\s*=>\s*(.*),?/) {
124                                         $others{$1} = $2;
125                                 }
126                         }
127                         close CACHE;
128                         # See if it's time to reindex
129                         $reindex = 1 if ( stat($others_indexname)->mtime lt ($time - $delay*60) );
130                 } else {
131                         # No index found, so we need to index
132                         $reindex = 1;
133                 }
134
135
136                 # Perform reindexing if necessary
137                 # This code was originally copied from entries_index by Rael Dornfest
138                 # Check to see if previously indexed files exist, and then rescan
139                 # the datadir for any new files, while preserving the old times
140
141                 # Static mode requires some of the code in this section, and the
142                 # speed hit is unimportant for static blogs
143                 
144                 if ($blosxom::static_or_dynamic eq "static") {
145                         $reindex = 1;
146                 }
147
148
149                 if ($reindex eq 1) {
150                         
151                         # If any files not available, err on side of caution and reindex
152                         for my $file (keys %files) { -f $file or do { $reindex++; delete $files{$file} }; }
153                         for my $other (keys %others) { -f $other or do { $reindex++; delete $others{$other} }; }
154
155
156                         find(
157                                 sub {
158                                         my $d; 
159                                         my $curr_depth = $File::Find::dir =~ tr[/][]; 
160
161                                         #       if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
162                                         #       # We are beyond depth, so remove files
163                                         #       $files{$File::Find::name} and delete $files{$File::Find::name};
164                                         #       $others{$File::Find::name} and delete $others{$File::Find::name};
165                                         #       return;
166                                         #}
167     
168                                 # Adding support for %others
169                                 if (
170                                         $File::Find::name =~ 
171                                                 m!^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$!
172                                                 and $2 ne 'index' and $2 !~ /^\./ and (-r $File::Find::name)
173                                         ) {
174                                     # SAS modified so if meta-creation_date or file timestamp are in the future
175                                     # and we don't want future entries the file won't be included in the file list
176                                     # the index list
177                                     if (!$blosxom::show_future_entries and 
178                                         ((extract_date($File::Find::name, $files{$File::Find::name}) > time) or
179                                          (stat($File::Find::name)->mtime > time))) {
180                                         $files{$File::Find::name} and delete $files{$File::Find::name};
181                                         $others{$File::Find::name} and delete $others{$File::Find::name};
182                                     } else {
183                                                 ( $files{$File::Find::name} || ++$reindex )
184                                                 and ( $files{$File::Find::name} = 
185                                                         extract_date($File::Find::name,$files{$File::Find::name}) ||
186                                                         $files{$File::Find::name} ||
187                                                         stat($File::Find::name)->mtime )
188                                                  
189                                                 # Static
190                                                 and (
191                                                         param('-all') 
192                                                         or !-f "$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0]
193 # Trying to fix for static mode
194                                                         or ( $update_static_rendering && stat("$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0])->mtime < stat($File::Find::name)->mtime )
195                                                         or stat("$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0])->mtime < $files{$File::Find::name}
196                                                 )
197                                                 and $indexes{$1} = 1
198                                                 and $d = join('/', (blosxom::nice_date($files{$File::Find::name}))[5,2,3])
199                                                 and $indexes{$d} = $d
200                                                 and $blosxom::static_entries and $indexes{ ($1 ? "$1/" : '') . "$2.$blosxom::file_extension" } = 1;
201                                             }
202                                         } else {
203                                                 !-d $File::Find::name and -r $File::Find::name and $others{$File::Find::name} = stat($File::Find::name)->mtime
204                                         }
205                                 }, $blosxom::datadir
206                         );
207
208
209                         if ( $reindex ) {
210                                 # The index was recreated, so we should record the new version
211                                 my $savemask = umask $index_umask ;
212                                 if ( open ENTRIES, "> $indexname" ) {
213                                         foreach (sort keys %files) {
214                                                 print ENTRIES "$_=>$files{$_}\n";
215                                         }
216                                         close ENTRIES;
217                                 } else {
218                                         warn "couldn't > $indexname: $!\n";
219                                 }
220
221                                 if ( open ENTRIES, "> $others_indexname" ) {
222                                         foreach (sort keys %others) {
223                                                 print ENTRIES "$_=>$others{$_}\n";
224                                         }
225                                         close ENTRIES;
226                                 } else {
227                                         warn "couldn't > $others_indexname: $!\n";
228                                 }
229                                 umask $savemask ;
230
231                         }
232                 }
233         return (\%files, \%indexes, \%others);
234         }
235 }
236  
237  
238 sub extract_date {
239         my ($file, $indexed_date) = @_;
240         my $new_story = "";
241         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
242         
243         warn "Entries_Cache: Checking $file for meta-tag\n\tComparing to $indexed_date\n" if ($debug == 1);
244         
245         # This is an attempt for compatibility with Eric Sherman's entries_index_tagged
246         # But it does not handle as many date formats, as there are too many additional
247         # necessary modules that I am not willing to require
248         
249         if ( $use_date_tags != 0) {
250         
251                 open (FILE, $file);
252                 $line = <FILE>;         # Read first line ( ie the title)
253                 $new_story .= $line;
254                 
255                 # now, parse the story, and try to correct misformatted stories
256                 while ($line = <FILE>) {
257                         if ($line =~ /^$meta_timestamp\s*(\d+)/) {
258                                 # If present, this format is used
259                                 close File;
260                                 $result = $1;
261                                 warn "Entries_Cache: Found meta_timestamp $result for $file\n" if ($debug == 1);
262                                 return $result;
263                         }
264                         
265                         if ($line =~ /^$meta_date\s*(.*)/) {
266                                 close File;
267                                 $result = $1;
268                                 warn "Entries_Cache: Found meta-date $result for $file\n" if ($debug == 1);
269                                 return parsedate($result);
270                         }
271                         
272                         if ( $line !~ /^meta.*?:/i) {
273                                 # line doesn't start with meta...  (ie story was not formatted
274                                 # for meta-tags), or the meta-tags are finished
275                                 
276                                 if ($update_meta_date eq 1) {
277                                         # Don't mess with stories unless using UNIX line endings.
278                                         
279                                         if (($line =~ /\r/) || ($new_story =~ /\r/)) {
280                                                 warn "Entries_Cache: File $file has non-UNIX line endings; cannot update metatags...\n";
281                                                 close FILE;
282                                                 return 0;
283                                         }
284                                         
285                                         warn "Entries_Cache: Updating meta-tag for $file\n" if ($debug == 1);
286                                         if ($indexed_date eq 0 || $indexed_date == "") {
287                                                 $indexed_date = stat($file)->mtime;
288                                                 warn "Entries_Cache: No date for $file, using $indexed_date\n" if ($debug == 1);
289                                         }
290                                         
291                                         ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($indexed_date);
292                                         $year += 1900;
293                                         $mon += 1;
294                                         $hour = sprintf("%02d",$hour);
295                                         $min = sprintf("%02d",$min);
296                                         $sec = sprintf("%02d",$sec);
297
298                                         warn "Entries_Cache: Adding meta-tag to $file, using $meta_date $mday/$mon/$year $hour:$min:$sec\n" if ($debug == 1);
299                                 
300                                         if ($use_UK_dates eq 1) {
301                                                 $new_story .= "$meta_date $mday/$mon/$year $hour:$min:$sec\n\n";
302                                         } else {
303                                                 $new_story .= "$meta_date $mon/$mday/$year $hour:$min:$sec\n\n";
304                                         }
305                                         
306                                         if ( $line !~ /^\s*$/) {
307                                                 # If this line wasn't empty, then add it to story
308                                                 $new_story .= $line;
309                                         }
310                                         
311                                         while ($line = <FILE>) {
312                                                 # read remainder of story
313                                                 $new_story .= $line;
314                                         }
315                                         
316                                         close FILE;
317                                         open (FILE, "> $file") or warn "Unable to update date meta-tag on $file\n";
318                                         print FILE $new_story;
319                                         close FILE;
320                                         return 0;
321                                 } else {
322                                         close FILE;
323                                         return 0;
324                                 }
325                         }
326
327                         $new_story .= $line;
328                 }
329         }
330         return 0;       
331 }
332
333 sub parsedate {
334         my ($datestring) = @_;
335         #warn "Parsing $datestring\n";
336         
337         # Possible formatting
338         # Month can be 3 letter abbreviation or full name (in English)
339         # Time must be hh:mm or hh:mm:ss  in 24 hour format
340         # Year must be yyyy
341         # The remaining 1 or 2 digits are treated as date
342         # ie: May 25 2003 18:40 
343         # order is not important as long as pieces are there
344                 
345         # Convert the datestring to a time() format
346
347         # Find "Shorthand" Date
348         if ( $datestring =~ /\d\d?\/\d\d?\/\d\d\d?\d?/) {
349                 if ( $use_UK_dates eq 0) {
350                         # Use US Formatting
351                         $datestring =~ s/(\d\d?)\/(\d\d?)\/(\d\d\d?\d?)//;
352                         $mon = $1 - 1;
353                         $day = $2;
354                         $year = $3;
355                 } else {
356                         # Use UK Formatting
357                         $datestring =~ s/(\d\d?)\/(\d\d?)\/(\d\d\d?\d?)//;
358                         $mon = $2 - 1;
359                         $day = $1;
360                         $year = $3;
361                 }
362                 
363                 # Now, clean up year if 2 digit
364                 # You may change the 70 to whatever cutoff you like
365                 $year += 2000 if ($year < 70 );
366                 $year += 1900 if ($year < 100);
367         }
368         
369         # Find Month
370         $mon = 0 if ($datestring =~ s/(Jan|January)//i);
371         $mon = 1 if ($datestring =~ s/(Feb|February)//i);
372         $mon = 2 if ($datestring =~ s/(Mar|March)//i);
373         $mon = 3 if ($datestring =~ s/(Apr|April)//i);
374         $mon = 4 if ($datestring =~ s/(May)//i);
375         $mon = 5 if ($datestring =~ s/(Jun|June)//i);
376         $mon = 6 if ($datestring =~ s/(Jul|July)//i);
377         $mon = 7 if ($datestring =~ s/(Aug|August)//i);
378         $mon = 8 if ($datestring =~ s/(Sep|September)//i);
379         $mon = 9 if ($datestring =~ s/(Oct|October)//i);
380         $mon = 10 if ($datestring =~ s/(Nov|November)//i);
381         $mon = 11 if ($datestring =~ s/(Dec|December)//i);
382
383         # Find Time
384         if ($datestring =~ s/(\d\d?):(\d\d)(:\d\d)?//) {
385                 $hour = $1;
386                 $min = $2;
387                 $sec = $3;
388         }
389         
390         if ($datestring =~ s/(\d\d\d\d)//) {
391                 $year = $1;
392         }
393         
394         if ($datestring =~ s/(\d\d?)//) {
395                 $day = $1;
396         }
397         
398         return timelocal($sec,$min,$hour,$day,$mon,$year);
399         
400 }
401
402 sub story {
403         return 1 if (! $strip_meta_dates);
404         # This code based on Rael's meta plugin
405         my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
406         
407         # Strip date-based meta tags from body
408         $$body_ref =~ s/^$meta_timestamp.*//g;
409         $$body_ref =~ s/^$meta_date.*//g;
410         
411         
412         return 1;
413 }
414
415 1;
416
417 __END__
418
419 =head1 NAME
420
421 Blosxom Plug-in: entries_cache
422
423 =head1 SYNOPSIS
424
425 Purpose: This plugin reserves original creation timestamp on weblog 
426 entries, allowing for editing of entries without altering the original 
427 creation time. It maintains an index ($blosxom::plugin_state_dir/
428 .entries_cache.index) of filenames and their creation times. It also adds 
429 new entries to the index the first time Blosxom come across them. In 
430 addition, it is possible to rely on entries_cache inserting date-based meta 
431 tags automatically into entries - this is enabled by the $use_date_tags 
432 setting. This makes the entries more portable. Note that if this approach 
433 is used, the meta plugin is required: http://www.blosxom.com/downloads/
434 plugins/meta.  (NOTE: As of version 0.91 you can use this without the meta
435 plugin.)
436
437 Replaces the default $blosxom::entries subroutine
438
439 The entries_cache plugin is a "souped-up" version of the entries_index 
440 plugin.  It maintains file modification times in the same manner as the 
441 original plugin, but goes one-step further. It uses the modification time 
442 of the index file to determine whether to rescan the datadir.  If $delay 
443 minutes have not passed, it relies on the cached information.
444
445 You can force a manual scan by appending ?reindex=y to the end of your base 
446 url.
447
448 The reason for this change is that the original blosxom and the 
449 entries_index plugin rescan the datadir each time a page is viewed. This 
450 plugin allows you to cache the information to speed up processing times on 
451 most page views.  According to several posts on the blosxom mailing list, 
452 this is one of the big processor hogs. With a $delay setting of 60 minutes, 
453 there will only be one page view each hour that has to wait for the full 
454 directory scan. To be honest, I have not noticed much of a speed boost 
455 during my testing yet, but I imagine it would only appear for sites with a 
456 large number of files to be indexed.
457
458
459 =head1 VERSION
460
461 0.93
462
463 =head1 VERSION HISTORY
464 0.93    Added the $update_static_rendering and $index_umask
465         configuration variables.
466
467 0.92    Checks if meta-creation_date is in the future and only displays
468         entry if $show_future_entries is set. Otherwise future articles
469         (by meta-creation_date or file modified time) are not displayed.
470         Don't think this breaks static rendering... haven't tested it.
471         
472 0.91    Improved documentation courtesy of Iain Cheyne - THANKS!!!
473                 Now, if you are not running the meta plugin, you will not see any meta tags in your stories
474
475 0.9             Added parser that detects stories that are not properly formatted for meta-tags and reformats them so that they are.
476                 Additionally, it will not update files that have improper line endings ( ie non-UNIX endings).
477  
478 0.8             Fixed typo that caused index to be rebuilt every time...  :)
479
480 0.7             Major revisions - fixed the "Year 1900" bug and an issue with statically generated blogs misbehaving
481
482 0.61    Fixed bug reading old styled cache files
483
484 0.6             Added feature to automatically create meta-tags from indexed
485                                 time/date
486                                 
487 0.52    Fixed a bug where a new index might not be written
488
489 0.51    Added dd/mm/yy(yy) and mm/dd/yy(yy) date formatting
490
491 0.5             Complete rewrite - add support for %others, meta- tags, added backwards compatibility to using the .entries_index.index file from rael's plugin
492
493 0.2             Removed reliance on Data::Dumper, making it suitable for use on Earthlink
494
495 =head1 AUTHOR
496
497 Fletcher T. Penney
498 Modified by Steve Schwarz
499 based on original code by:
500 Rael Dornfest  <rael@oreilly.com>, http://www.raelity.org/
501
502 This plugin is now maintained by the Blosxom Sourceforge Team,
503 <blosxom-devel@lists.sourceforge.net>.
504
505 =head1 SEE ALSO
506
507 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
508
509 Blosxom Plugin Docs: http://blosxom.sourceforge.net/documentation/users/plugins.html
510
511 =head1 BUGS
512
513 None known; please send bug reports and feedback to the Blosxom
514 development mailing list <blosxom-devel@lists.sourceforge.net>.
515
516 =head1 LICENSE
517
518 entries_cache plugin
519 Copyright 2003, Fletcher Penney
520 except for portions copied from entries_index and entries_index_tagged
521
522 Blosxom and original entries_index plugin
523 Copyright 2003, Rael Dornfest 
524
525 Permission is hereby granted, free of charge, to any person obtaining a
526 copy of this software and associated documentation files (the "Software"),
527 to deal in the Software without restriction, including without limitation
528 the rights to use, copy, modify, merge, publish, distribute, sublicense,
529 and/or sell copies of the Software, and to permit persons to whom the
530 Software is furnished to do so, subject to the following conditions:
531
532 The above copyright notice and this permission notice shall be included
533 in all copies or substantial portions of the Software.
534
535 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
536 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
537 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
538 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
539 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
540 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
541 OTHER DEALINGS IN THE SOFTWARE.