root/trunk/lib/MT/WeblogPublisher.pm @ 3082

Revision 3082, 83.6 kB (checked in by bchoate, 14 months ago)

Merging fireball branch changes to-date to trunk: svn merge -r2974:3081 http://code.sixapart.com/svn/movabletype/branches/fireball .

  • Property svn:keywords set to Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::WeblogPublisher;
8
9use strict;
10use base qw( MT::ErrorHandler Exporter );
11our @EXPORT = qw(ArchiveFileTemplate ArchiveType);
12
13use MT::ArchiveType;
14use File::Basename;
15
16our %ArchiveTypes;
17
18sub ArchiveFileTemplate {
19    my %param = @_;
20    \%param;
21}
22
23sub ArchiveType {
24    new MT::ArchiveType(@_);
25}
26
27sub new {
28    my $class = shift;
29    my $this  = {@_};
30    my $cfg   = MT->config;
31    if ( !exists $this->{start_time} ) {
32        $this->{start_time} = time;
33    }
34    if ( !exists $this->{NoTempFiles} ) {
35        $this->{NoTempFiles} = $cfg->NoTempFiles;
36    }
37    if ( !exists $this->{PublishCommenterIcon} ) {
38        $this->{PublishCommenterIcon} = $cfg->PublishCommenterIcon;
39    }
40    bless $this, $class;
41    $this->init();
42    $this;
43}
44
45sub init_archive_types {
46    my $types = MT->registry("archive_types") || {};
47    my $mt = MT->instance;
48    while (my ($type, $typedata) = each %$types) {
49        if ('HASH' eq ref $typedata) {
50            $typedata = MT::ArchiveType->new( %$typedata );
51        }
52        $ArchiveTypes{$type} = $typedata;
53    }
54}
55
56sub archive_types {
57    init_archive_types() unless %ArchiveTypes;
58    keys %ArchiveTypes;
59}
60
61sub archiver {
62    my $mt = shift;
63    my ($at) = @_;
64    init_archive_types() unless %ArchiveTypes;
65    my $archiver = $at ? $ArchiveTypes{$at} : undef;
66    if ($archiver && !ref($archiver)) {
67        # A package name-- load package and instantiate Archiver object
68        if ($archiver =~ m/::/) {
69            eval("require $archiver; 1;");
70            die "Invalid archive type package '$archiver': $@"
71                if $@; # fatal error here
72            my $inst = $archiver->new();
73            $archiver = $ArchiveTypes{$at} = $inst;
74        }
75    }
76    return $archiver;
77}
78
79sub init {
80    init_archive_types() unless %ArchiveTypes;
81}
82
83sub core_archive_types {
84    return {
85        'Yearly' => 'MT::ArchiveType::Yearly',
86        'Monthly' => 'MT::ArchiveType::Monthly',
87        'Weekly' => 'MT::ArchiveType::Weekly',
88        'Individual' => 'MT::ArchiveType::Individual',
89        'Page' => 'MT::ArchiveType::Page',
90        'Daily' => 'MT::ArchiveType::Daily',
91        'Category' => 'MT::ArchiveType::Category',
92        'Author' => 'MT::ArchiveType::Author',
93        'Author-Yearly' => 'MT::ArchiveType::AuthorYearly',
94        'Author-Monthly' => 'MT::ArchiveType::AuthorMonthly',
95        'Author-Weekly' => 'MT::ArchiveType::AuthorWeekly',
96        'Author-Daily' => 'MT::ArchiveType::AuthorDaily',
97        'Category-Yearly' => 'MT::ArchiveType::CategoryYearly',
98        'Category-Monthly' => 'MT::ArchiveType::CategoryMonthly',
99        'Category-Daily' => 'MT::ArchiveType::CategoryDaily',
100        'Category-Weekly' => 'MT::ArchiveType::CategoryWeekly',
101    };
102
103}
104
105sub start_time {
106    my $pub = shift;
107    $pub->{start_time} = shift if @_;
108    return $pub->{start_time};
109}
110
111sub rebuild {
112    my $mt    = shift;
113    my %param = @_;
114    my $blog;
115    unless ( $blog = $param{Blog} ) {
116        my $blog_id = $param{BlogID};
117        $blog = MT::Blog->load($blog_id)
118          or return $mt->error(
119            MT->translate(
120                "Load of blog '[_1]' failed: [_2]", $blog_id,
121                MT::Blog->errstr
122            )
123          );
124    }
125    return 1 if $blog->is_dynamic;
126    my $at = $blog->archive_type || '';
127    my @at = split /,/, $at;
128    my $entry_class;
129    if ( my $set_at = $param{ArchiveType} ) {
130        my %at = map { $_ => 1 } @at;
131        return $mt->error(
132            MT->translate(
133                "Archive type '[_1]' is not a chosen archive type", $set_at
134            )
135        ) unless $at{$set_at};
136
137        @at = ($set_at);
138        my $archiver = $mt->archiver($set_at);
139        $entry_class = $archiver->entry_class || "entry";
140    }
141    else {
142        $entry_class = '*';
143    }
144
145    if (   $param{ArchiveType}
146        && ( !$param{Entry} )
147        && ( $param{ArchiveType} eq 'Category' ) )
148    {
149
150        # Pass to full category rebuild
151        return $mt->rebuild_categories(%param);
152    }
153
154    if (   $param{ArchiveType}
155        && ( !$param{Author} )
156        && ( $param{ArchiveType} eq 'Author' ) )
157    {
158        return $mt->rebuild_authors(%param);
159    }
160
161    if (@at) {
162        require MT::Entry;
163        my %arg = ( 'sort' => 'authored_on', direction => 'descend' );
164        $arg{offset} = $param{Offset} if $param{Offset};
165        $arg{limit}  = $param{Limit}  if $param{Limit};
166        my $pre_iter = MT::Entry->load_iter(
167            {
168                blog_id => $blog->id,
169                class   => $entry_class,
170                status  => MT::Entry::RELEASE()
171            },
172            \%arg
173        );
174        my ( $next, $curr );
175        my $prev = $pre_iter->();
176        my $iter = sub {
177            ( $next, $curr ) = ( $curr, $prev );
178            if ($curr) {
179                $prev = $pre_iter->();
180            }
181            $curr;
182        };
183        my $cb  = $param{EntryCallback};
184        my $fcb = $param{FilterCallback};
185        while ( my $entry = $iter->() ) {
186            if ($cb) {
187                $cb->($entry) || $mt->log( $cb->errstr() );
188            }
189            if ($fcb) {
190                $fcb->($entry) or last;
191            }
192            for my $at (@at) {
193                my $archiver = $mt->archiver($at);
194
195                # Skip this archive type if the archive type doesn't
196                # match the kind of entry we've loaded
197                next unless $archiver;
198                next if $entry->class ne $archiver->entry_class;
199                if ( $archiver->category_based ) {
200                    my $cats = $entry->categories;
201                    CATEGORY: for my $cat (@$cats) {
202                        next CATEGORY if $archiver->category_class ne $cat->class_type;
203                        $mt->_rebuild_entry_archive_type(
204                            Entry       => $entry,
205                            Blog        => $blog,
206                            Category    => $cat,
207                            ArchiveType => $at,
208                            NoStatic    => $param{NoStatic},
209                            Force       => ($param{Force} ? 1 : 0),
210                            $param{TemplateMap}
211                            ? ( TemplateMap => $param{TemplateMap} )
212                            : (),
213                            $param{TemplateID}
214                            ? ( TemplateID =>
215                                  $param{TemplateID} )
216                            : (),
217                        ) or return;
218                    }
219                }
220                elsif ( $archiver->author_based ) {
221                    if ( $entry->author ) {
222                        $mt->_rebuild_entry_archive_type(
223                            Entry       => $entry,
224                            Blog        => $blog,
225                            ArchiveType => $at,
226                            $param{TemplateMap}
227                            ? ( TemplateMap => $param{TemplateMap} )
228                            : (),
229                            $param{TemplateID}
230                            ? ( TemplateID =>
231                                  $param{TemplateID} )
232                            : (),
233                            NoStatic => $param{NoStatic},
234                            Force    => ($param{Force} ? 1 : 0),
235                            Author   => $entry->author,
236                        ) or return;
237                    }
238                }
239                else {
240                    $mt->_rebuild_entry_archive_type(
241                        Entry       => $entry,
242                        Blog        => $blog,
243                        ArchiveType => $at,
244                        $param{TemplateMap}
245                        ? ( TemplateMap => $param{TemplateMap} )
246                        : (),
247                        $param{TemplateID}
248                        ? ( TemplateID =>
249                              $param{TemplateID} )
250                        : (),
251                        NoStatic => $param{NoStatic},
252                        Force    => ($param{Force} ? 1 : 0),
253                    ) or return;
254                }
255            }
256        }
257    }
258    unless ( $param{NoIndexes} ) {
259        $mt->rebuild_indexes( Blog => $blog ) or return;
260    }
261    1;
262}
263
264sub rebuild_categories {
265    my $mt    = shift;
266    my %param = @_;
267    my $blog;
268    unless ( $blog = $param{Blog} ) {
269        my $blog_id = $param{BlogID};
270        $blog = MT::Blog->load($blog_id)
271          or return $mt->error(
272            MT->translate(
273                "Load of blog '[_1]' failed: [_2]", $blog_id,
274                MT::Blog->errstr
275            )
276          );
277    }
278    my %arg;
279    $arg{'sort'} = 'id';
280    $arg{direction} = 'ascend';
281    $arg{offset} = $param{Offset} if $param{Offset};
282    $arg{limit}  = $param{Limit}  if $param{Limit};
283    my $cat_iter = MT::Category->load_iter( { blog_id => $blog->id }, \%arg );
284    my $fcb = $param{FilterCallback};
285
286    while ( my $cat = $cat_iter->() ) {
287        if ($fcb) {
288            $fcb->($cat) or last;
289        }
290        $mt->_rebuild_entry_archive_type(
291            Blog        => $blog,
292            Category    => $cat,
293            ArchiveType => 'Category',
294            $param{TemplateMap}
295            ? ( TemplateMap => $param{TemplateMap} )
296            : (),
297            NoStatic => $param{NoStatic},
298            Force    => ($param{Force} ? 1 : 0),
299        ) or return;
300    }
301    1;
302}
303
304sub rebuild_authors {
305    my $mt    = shift;
306    my %param = @_;
307    my $blog;
308    unless ( $blog = $param{Blog} ) {
309        my $blog_id = $param{BlogID};
310        $blog = MT::Blog->load($blog_id)
311          or return $mt->error(
312            MT->translate(
313                "Load of blog '[_1]' failed: [_2]", $blog_id,
314                MT::Blog->errstr
315            )
316          );
317    }
318    my %arg;
319    $arg{'sort'} = 'id';
320    $arg{direction} = 'ascend';
321    $arg{offset} = $param{Offset} if $param{Offset};
322    $arg{limit}  = $param{Limit}  if $param{Limit};
323    $arg{unique} = 1;
324    my %terms;
325    $terms{status} = MT::Author::ACTIVE();
326    require MT::Entry;
327    $arg{join} = MT::Entry->join_on(
328        'author_id',
329        { blog_id => $blog->id, class => 'entry', status => MT::Entry::RELEASE() },
330        { unique  => 1 }
331    );
332    my $auth_iter = MT::Author->load_iter( \%terms, \%arg );
333    my $fcb = $param{FilterCallback};
334
335    while ( my $a = $auth_iter->() ) {
336        if ($fcb) {
337            $fcb->($a) or last;
338        }
339        $mt->_rebuild_entry_archive_type(
340            Blog        => $blog,
341            Author      => $a,
342            ArchiveType => 'Author',
343            $param{TemplateMap}
344            ? ( TemplateMap => $param{TemplateMap} )
345            : (),
346            NoStatic => $param{NoStatic},
347            Force    => ($param{Force} ? 1 : 0),
348        ) or return;
349    }
350    1;
351}
352
353sub remove_fileinfo {
354    my $mt    = shift;
355    my %param = @_;
356    my $at    = $param{ArchiveType}
357      or return $mt->error(
358        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
359    my $blog_id = $param{Blog}
360      or return $mt->error(
361        MT->translate( "Parameter '[_1]' is required", 'Blog' ) );
362    my $entry_id = $param{Entry}, my $author_id = $param{Author};
363    my $start    = $param{StartDate};
364    my $cat_id   = $param{Category};
365
366    require MT::FileInfo;
367    my @finfo = MT::FileInfo->load(
368        {
369            archive_type => $at,
370            blog_id      => $blog_id,
371            ( $entry_id ? ( entry_id    => $entry_id ) : () ),
372            ( $cat_id   ? ( category_id => $cat_id )   : () ),
373            ( $start    ? ( startdate   => $start )    : () ),
374        }
375    );
376
377    for my $f (@finfo) {
378        $f->remove;
379    }
380    1;
381}
382
383# rebuild_deleted_entry
384#
385# $mt->rebuild_deleted_entry(
386#                    Entry => $entry | $entry_id,
387#                    Blog => [ $blog | $blog_id ],
388#                    );
389sub rebuild_deleted_entry {
390    my $mt    = shift;
391    my $app   = MT->instance;
392    my %param = @_;
393    my $entry = $param{Entry}
394      or return $mt->error(
395        MT->translate( "Parameter '[_1]' is required", 'Entry' ) );
396    require MT::Entry;
397    $entry = MT::Entry->load($entry) unless ref $entry;
398    return unless $entry;
399
400    my $blog;
401    unless ( $blog = $param{Blog} ) {
402        require MT::Blog;
403        my $blog_id = $entry->blog_id;
404        $blog = MT::Blog->load($blog_id)
405          or return $mt->error(
406            MT->translate(
407                "Load of blog '[_1]' failed: [_2]", $blog_id,
408                MT::Blog->errstr
409            )
410          );
411    }
412
413    my %rebuild_recipe;
414    my $at = $blog->archive_type;
415    my @at;
416    if ( $at && $at ne 'None' ) {
417        my @at_orig = split( /,/, $at );
418        @at = grep { $_ ne 'Individual' && $_ ne 'Page' } @at_orig;
419    }
420
421    # Remove Individual archive file.
422    if ( $app->config('DeleteFilesAtRebuild') ) {
423        $mt->remove_entry_archive_file( Entry => $entry, );
424    }
425
426    # Remove Individual fileinfo records.
427    $mt->remove_fileinfo(
428        ArchiveType => 'Individual',
429        Blog        => $blog->id,
430        Entry       => $entry->id
431    );
432
433    require MT::Util;
434    for my $at (@at) {
435        my $archiver = $mt->archiver($at);
436        next unless $archiver;
437
438        my ( $start, $end ) = $archiver->date_range( $entry->authored_on )
439          if $archiver->date_based() && $archiver->can('date_range');
440
441        # Remove archive file if archive file has not entries.
442        if ( $archiver->category_based() ) {
443            my $categories = $entry->categories();
444            for my $cat (@$categories) {
445                if (
446                    ( $archiver->can('archive_entries_count') )
447                    && (
448                        $archiver->archive_entries_count( $blog, $at, $entry,
449                            $cat ) == 1
450                    )
451                  )
452                {
453                    $mt->remove_fileinfo(
454                        ArchiveType => $at,
455                        Blog        => $blog->id,
456                        Category    => $cat->id,
457                        (
458                            $archiver->date_based()
459                            ? ( startdate => $start )
460                            : ()
461                        ),
462                    );
463                    if ( $app->config('DeleteFilesAtRebuild') ) {
464                        $mt->remove_entry_archive_file(
465                            Entry       => $entry,
466                            ArchiveType => $at,
467                            Category    => $cat,
468                        );
469                    }
470                }
471                else {
472                    if ( $app->config('RebuildAtDelete') ) {
473                        if ( $archiver->date_based() ) {
474                            $rebuild_recipe{$at}{ $cat->id }{ $start . $end }
475                              {'Start'} = $start;
476                            $rebuild_recipe{$at}{ $cat->id }{ $start . $end }
477                              {'End'} = $end;
478                            $rebuild_recipe{$at}{ $cat->id }{ $start . $end }
479                              {'File'} = MT::Util::archive_file_for(
480                                $entry, $blog, $at, $cat,
481                                undef,  undef, undef
482                              );
483                        }
484                        else {
485                            $rebuild_recipe{$at}{ $cat->id }{id} = $cat->id;
486                            $rebuild_recipe{$at}{ $cat->id }{'File'} =
487                              MT::Util::archive_file_for(
488                                $entry, $blog, $at, $cat,
489                                undef,  undef, undef
490                              );
491                        }
492                    }
493                }
494            }
495        }
496        else {
497            if ( ( $archiver->can('archive_entries_count') )
498                && ( $archiver->archive_entries_count( $blog, $at, $entry ) ==
499                    1 ) )
500            {
501
502                # Remove archives fileinfo records.
503                $mt->remove_fileinfo(
504                    ArchiveType => $at,
505                    Blog        => $blog->id,
506                    (
507                        $archiver->author_based()
508                        ? ( author_id => $entry->author->id )
509                        : ()
510                    ),
511                    ( $archiver->date_based() ? ( startdate => $start ) : () ),
512                );
513                if ( $app->config('DeleteFilesAtRebuild') ) {
514                    $mt->remove_entry_archive_file(
515                        Entry       => $entry,
516                        ArchiveType => $at
517                    );
518                }
519            }
520            else {
521                if ( $app->config('RebuildAtDelete') ) {
522                    if ( $archiver->author_based() ) {
523                        if ( $archiver->date_based() ) {
524                            $rebuild_recipe{$at}{ $entry->author->id }
525                              { $start . $end }{'Start'} = $start;
526                            $rebuild_recipe{$at}{ $entry->author->id }
527                              { $start . $end }{'End'} = $end;
528                            $rebuild_recipe{$at}{ $entry->author->id }
529                              { $start . $end }{'File'} =
530                              MT::Util::archive_file_for( $entry, $blog, $at,
531                                undef, undef, undef, $entry->author );
532                        }
533                        else {
534                            $rebuild_recipe{$at}{ $entry->author->id }{id} =
535                              $entry->author->id;
536                            $rebuild_recipe{$at}{ $entry->author->id }{'File'} =
537                              MT::Util::archive_file_for( $entry, $blog, $at,
538                                undef, undef, undef, $entry->author );
539                        }
540                    }
541                    elsif ( $archiver->date_based() ) {
542                        $rebuild_recipe{$at}{ $start . $end }{'Start'} = $start;
543                        $rebuild_recipe{$at}{ $start . $end }{'End'}   = $end;
544                        $rebuild_recipe{$at}{ $start . $end }{'File'} =
545                          MT::Util::archive_file_for( $entry, $blog, $at, undef,
546                            undef, undef, undef );
547                    }
548                    if ( my $prev = $entry->previous(1) ) {
549                        $rebuild_recipe{Individual}{ $prev->id }{id} = $prev->id;
550                        $rebuild_recipe{Individual}{ $prev->id }{'File'} =
551                          MT::Util::archive_file_for( $prev, $blog,
552                            'Individual', undef, undef, undef, undef );
553                    }
554                    if ( my $next = $entry->next(1) ) {
555                        $rebuild_recipe{Individual}{ $next->id }{id} = $next->id;
556                        $rebuild_recipe{Individual}{ $next->id }{'File'} =
557                          MT::Util::archive_file_for( $next, $blog,
558                            'Individual', undef, undef, undef, undef );
559                    }
560                }
561            }
562        }
563    }
564
565    return %rebuild_recipe;
566}
567
568#   rebuild_entry
569#
570# $mt->rebuild_entry(Entry => $entry_id,
571#                    Blog => [ $blog | $blog_id ],
572#                    [ BuildDependencies => (0 | 1), ]
573#                    [ OldPrevious => $old_previous_entry_id,
574#                      OldNext => $old_next_entry_id, ]
575#                    [ NoStatic => (0 | 1), ]
576#                    );
577sub rebuild_entry {
578    my $mt    = shift;
579    my %param = @_;
580    my $entry = $param{Entry}
581      or return $mt->error(
582        MT->translate( "Parameter '[_1]' is required", 'Entry' ) );
583    require MT::Entry;
584    $entry = MT::Entry->load($entry) unless ref $entry;
585    return unless $entry;
586    my $blog;
587    unless ( $blog = $param{Blog} ) {
588        my $blog_id = $entry->blog_id;
589        $blog = MT::Blog->load($blog_id)
590          or return $mt->error(
591            MT->translate(
592                "Load of blog '[_1]' failed: [_2]", $blog_id,
593                MT::Blog->errstr
594            )
595          );
596    }
597    return 1 if $blog->is_dynamic;
598
599    my $at = $param{PreferredArchiveOnly} ? $blog->archive_type_preferred : $blog->archive_type;
600    if ( $at && $at ne 'None' ) {
601        my @at = split /,/, $at;
602        for my $at (@at) {
603            my $archiver = $mt->archiver($at);
604            next unless $archiver;    # invalid archive type
605            next if $entry->class ne $archiver->entry_class;
606            if ( $archiver->category_based ) {
607                my $cats = $entry->categories;    # (ancestors => 1)
608                for my $cat (@$cats) {
609                    $mt->_rebuild_entry_archive_type(
610                        Entry       => $entry,
611                        Blog        => $blog,
612                        ArchiveType => $at,
613                        Category    => $cat,
614                        NoStatic    => $param{NoStatic},
615                        # Force       => ($param{Force} ? 1 : 0),
616                        $param{TemplateMap}
617                        ? ( TemplateMap => $param{TemplateMap} )
618                        : (),
619                    ) or return;
620                }
621            }
622            else {
623                $mt->_rebuild_entry_archive_type(
624                    Entry       => $entry,
625                    Blog        => $blog,
626                    ArchiveType => $at,
627                    $param{TemplateMap}
628                    ? ( TemplateMap => $param{TemplateMap} )
629                    : (),
630                    NoStatic => $param{NoStatic},
631                    Force    => ($param{Force} ? 1 : 0),
632                    Author   => $entry->author,
633                ) or return;
634            }
635        }
636    }
637
638    ## The above will just rebuild the archive pages for this particular
639    ## entry. If we want to rebuild all of the entries/archives/indexes
640    ## on which this entry could be featured etc., however, we need to
641    ## rebuild all of the entry's dependencies. Note that all of these
642    ## are not *necessarily* dependencies, depending on the usage of tags,
643    ## etc. There is not a good way to determine exact dependencies; it is
644    ## easier to just rebuild, rebuild, rebuild.
645
646    return 1
647      unless $param{BuildDependencies}
648      || $param{BuildIndexes}
649      || $param{BuildArchives};
650
651    if ( $param{BuildDependencies} ) {
652        ## Rebuild previous and next entry archive pages.
653        if ( my $prev = $entry->previous(1) ) {
654            $mt->rebuild_entry( Entry => $prev, PreferredArchiveOnly => 1 ) or return;
655            ## Rebuild the old previous and next entries, if we have some.
656            if ( $param{OldPrevious}
657                && ( $param{OldPrevious} != $prev->id )
658                && ( my $old_prev = MT::Entry->load( $param{OldPrevious} ) ) )
659            {
660                $mt->rebuild_entry( Entry => $old_prev, PreferredArchiveOnly => 1 ) or return;
661            }
662        }
663        if ( my $next = $entry->next(1) ) {
664            $mt->rebuild_entry( Entry => $next, PreferredArchiveOnly => 1 ) or return;
665
666            if ( $param{OldNext}
667                && ( $param{OldNext} != $next->id )
668                && ( my $old_next = MT::Entry->load( $param{OldNext} ) ) )
669            {
670                $mt->rebuild_entry( Entry => $old_next, PreferredArchiveOnly => 1 ) or return;
671            }
672        }
673    }
674
675    if ( $param{BuildDependencies} || $param{BuildIndexes} ) {
676        ## Rebuild all indexes, in case this entry is on an index.
677        if ( !( exists $param{BuildIndexes} ) || $param{BuildIndexes} ) {
678            $mt->rebuild_indexes( Blog => $blog ) or return;
679        }
680    }
681
682    if ( $param{BuildDependencies} || $param{BuildArchives} ) {
683        ## Rebuild previous and next daily, weekly, and monthly archives;
684        ## adding a new entry could cause changes to the intra-archive
685        ## navigation.
686        my %at = map { $_ => 1 } split /,/, $blog->archive_type;
687        my @db_at = grep { my $archiver = $mt->archiver($_); $archiver && $archiver->date_based } $mt->archive_types;
688        for my $at (@db_at) {
689            if ( $at{$at} ) {
690                my $archiver = $mt->archiver($at);
691                if ( $archiver->category_based ) {
692                    my $cats = $entry->categories;
693                    for my $cat (@$cats) {
694                        if ( my $prev_arch = $archiver->previous_archive_entry({
695                            entry    => $entry,
696                            category => $cat,
697                        }) ) {
698                            $mt->_rebuild_entry_archive_type(
699                                NoStatic => $param{NoStatic},
700                                # Force    => ($param{Force} ? 1 : 0),
701                                Entry    => $prev_arch,
702                                Blog     => $blog,
703                                Category => $cat,
704                                $param{TemplateMap}
705                                ? ( TemplateMap => $param{TemplateMap} )
706                                : (),
707                                ArchiveType => $at
708                            ) or return;
709                        }
710                        if ( my $next_arch = $archiver->next_archive_entry({
711                            entry    => $entry,
712                            category => $cat,
713                        }) ) {
714                            $mt->_rebuild_entry_archive_type(
715                                NoStatic => $param{NoStatic},
716                                # Force    => ($param{Force} ? 1 : 0),
717                                Entry    => $next_arch,
718                                Blog     => $blog,
719                                Category => $cat,
720                                $param{TemplateMap}
721                                ? ( TemplateMap => $param{TemplateMap} )
722                                : (),
723                                ArchiveType => $at
724                            ) or return;
725                        }
726                    }
727                } else {
728                    if ( my $prev_arch = $archiver->previous_archive_entry({
729                        entry => $entry,
730                        $archiver->author_based ? (author => $entry->author) : (),
731                    }) ) {
732                        $mt->_rebuild_entry_archive_type(
733                            NoStatic    => $param{NoStatic},
734                            # Force       => ($param{Force} ? 1 : 0),
735                            Entry       => $prev_arch,
736                            Blog        => $blog,
737                            ArchiveType => $at,
738                            $param{TemplateMap}
739                            ? ( TemplateMap => $param{TemplateMap} )
740                            : (),
741                            $archiver->author_based ? (Author => $entry->author) : (),
742                        ) or return;
743                    }
744                    if ( my $next_arch = $archiver->next_archive_entry({
745                        entry => $entry,
746                        $archiver->author_based ? (author => $entry->author) : (),
747                    }) ) {
748                        $mt->_rebuild_entry_archive_type(
749                            NoStatic    => $param{NoStatic},
750                            # Force       => ($param{Force} ? 1 : 0),
751                            Entry       => $next_arch,
752                            Blog        => $blog,
753                            ArchiveType => $at,
754                            $param{TemplateMap}
755                            ? ( TemplateMap => $param{TemplateMap} )
756                            : (),
757                            $archiver->author_based ? (Author => $entry->author) : (),
758                        ) or return;
759                    }
760                }
761            }
762        }
763    }
764
765    1;
766}
767
768### Recipe hash
769### {ArchiveType} - {Category-id} - {Date key} - {Start}
770###                                            - {End}
771###                                            - {File}
772###                               - {File}
773###               - {Author-id}   - {Date key} - {Start}
774###                                            - {End}
775###                                            - {File}
776###                               - {File}
777###               - {Date key}    - {Start}
778###                               - {End}
779###                               - {File}
780###               - {entry-id}    - {id}
781###                               - {File}
782###
783sub rebuild_archives {
784    my $mt = shift;
785    my %param = @_;
786    my $blog = $param{Blog}
787      or return $mt->error(
788        MT->translate( "Parameter '[_1]' is required", 'Blog' ) );
789    return 1 if $blog->is_dynamic;
790
791    my $recipe = $param{Recipe}
792        or return $mt->error(
793            MT->translate( "Parameter '[_1]' is required", 'Recipe' ) );
794
795    for my $at (keys %$recipe){
796        my $archiver = $mt->archiver($at);
797        next unless $archiver;
798
799        if ($archiver->category_based()) {
800            require MT::Category;
801            for my $cat_id (keys %{$recipe->{$at}}) {
802                my $cat = MT::Category->load($cat_id)
803                    or next;
804                if ($archiver->date_based()) {
805                    for my $key (keys %{$recipe->{$at}->{$cat_id}}) {
806                        $mt->_rebuild_entry_archive_type(
807                            NoStatic    => 0,
808                            Force       => ($param{Force} ? 1 : 0),
809                            Blog        => $blog,
810                            Category    => $cat,
811                            ArchiveType => $at,
812                            Start       => $recipe->{$at}->{$cat_id}->{$key}->{Start},
813                            End         => $recipe->{$at}->{$cat_id}->{$key}->{End},
814                            File        => $recipe->{$at}->{$cat_id}->{$key}->{File}
815                        ) or return;
816                    }
817                } else {
818                    $mt->_rebuild_entry_archive_type(
819                        NoStatic    => 0,
820                        Force       => ($param{Force} ? 1 : 0),
821                        Blog        => $blog,
822                        Category    => $cat,
823                        ArchiveType => $at,
824                        File        => $recipe->{$at}->{$cat_id}->{File}
825                    ) or return;
826                }
827            }
828        } elsif ($archiver->author_based()) {
829            require MT::Author;
830            for my $auth_id (keys %{$recipe->{$at}}) {
831                my $author = MT::Author->load($auth_id)
832                    or next;
833                if ($archiver->date_based()) {
834                    for my $key (keys %{$recipe->{$at}->{$auth_id}}) {
835                        $mt->_rebuild_entry_archive_type(
836                            NoStatic    => 0,
837                            Force       => ($param{Force} ? 1 : 0),
838                            Blog        => $blog,
839                            Author      => $author,
840                            ArchiveType => $at,
841                            Start       => $recipe->{$at}->{$auth_id}->{$key}->{Start},
842                            End         => $recipe->{$at}->{$auth_id}->{$key}->{End},
843                            File        => $recipe->{$at}->{$auth_id}->{$key}->{File}
844                        ) or return;
845                    }
846                } else {
847                    $mt->_rebuild_entry_archive_type(
848                        NoStatic    => 0,
849                        Force       => ($param{Force} ? 1 : 0),
850                        Blog        => $blog,
851                        Author      => $author,
852                        ArchiveType => $at,
853                        File        => $recipe->{$at}->{$auth_id}->{File}
854                    ) or return;
855                }
856            }
857        } elsif ($archiver->date_based()) {
858            for my $key (keys %{$recipe->{$at}}) {
859                $mt->_rebuild_entry_archive_type(
860                    NoStatic    => 0,
861                    Force       => ($param{Force} ? 1 : 0),
862                    Blog        => $blog,
863                    ArchiveType => $at,
864                    Start       => $recipe->{$at}->{$key}->{Start},
865                    End         => $recipe->{$at}->{$key}->{End},
866                    File        => $recipe->{$at}->{$key}->{File}
867                ) or return;
868            }
869        } else {
870            require MT::Entry;
871            for my $entry_id (keys %{$recipe->{$at}}) {
872                my $entry = MT::Entry->load($entry_id)
873                    or next;
874                $mt->_rebuild_entry_archive_type(
875                    NoStatic    => 0,
876                    Force       => ($param{Force} ? 1 : 0),
877                    Entry       => $entry,
878                    Blog        => $blog,
879                    ArchiveType => $at,
880                    File        => $recipe->{$at}->{$entry_id}->{File}
881                ) or return;
882            }
883        }
884    }
885
886    1;
887}
888
889sub _rebuild_entry_archive_type {
890    my $mt    = shift;
891    my %param = @_;
892
893    my $at    = $param{ArchiveType}
894      or return $mt->error(
895        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
896    return 1 if $at eq 'None';
897    my $entry =
898      ( $param{ArchiveType} ne 'Category' && $param{ArchiveType} ne 'Author' &&
899        !exists $param{Start} && !exists $param{End} )
900      ? (
901        $param{Entry}
902          or return $mt->error(
903            MT->translate( "Parameter '[_1]' is required", 'Entry' )
904          )
905      )
906      : undef;
907
908    my $blog;
909    unless ( $blog = $param{Blog} ) {
910        my $blog_id = $entry->blog_id;
911        $blog = MT::Blog->load($blog_id)
912          or return $mt->error(
913            MT->translate(
914                "Load of blog '[_1]' failed: [_2]", $blog_id,
915                MT::Blog->errstr
916            )
917          );
918    }
919
920    ## Load the template-archive-type map entries for this blog and
921    ## archive type. We do this before we load the list of entries, because
922    ## we will run through the files and check if we even need to rebuild
923    ## anything. If there is nothing to rebuild at all for this entry,
924    ## we save some time by not loading the list of entries.
925    require MT::TemplateMap;
926    my @map;
927    if ( $param{TemplateMap} ) {
928        @map = ( $param{TemplateMap} );
929    }
930    else {
931        my $cached_maps = MT->instance->request('__cached_maps')
932          || MT->instance->request( '__cached_maps', {} );
933        if ( my $maps = $cached_maps->{ $at . $blog->id } ) {
934            @map = @$maps;
935        }
936        else {
937            @map = MT::TemplateMap->load(
938                {
939                    archive_type => $at,
940                    blog_id      => $blog->id,
941                    $param{TemplateID}
942                    ? ( template_id => $param{TemplateID} )
943                    : ()
944                }
945            );
946            $cached_maps->{ $at . $blog->id } = \@map;
947        }
948    }
949    return 1 unless @map;
950    my @map_build;
951
952    my $done = MT->instance->request('__published:'.$blog->id)
953      || MT->instance->request( '__published:'.$blog->id, {} );
954    for my $map (@map) {
955        my $file = exists $param{File}
956            ? $param{File}
957            : $mt->archive_file_for( $entry, $blog, $at, $param{Category}, $map,
958            undef, $param{Author} );
959        if ( $file eq '' ) {
960
961            # np
962        }
963        elsif ( !defined($file) ) {
964            return $mt->error( MT->translate( $blog->errstr() ) );
965        }
966        else {
967            push @map_build, $map unless $done->{$file};
968            $map->{__saved_output_file} = $file;
969        }
970    }
971    return 1 unless @map_build;
972    @map = @map_build;
973
974    $at ||= "";
975
976    my $archiver = $mt->archiver($at);
977    return unless $archiver;
978
979    # Special handling for pages-- they are always published to the
980    # 'site' path instead of the 'archive' path, which is reserved for blog
981    # content.
982    my $arch_root = ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
983    return $mt->error(
984        MT->translate("You did not set your blog publishing path") )
985      unless $arch_root;
986
987    my ($start, $end);
988    if (exists $param{Start} && exists $param{End}) {
989        $start = $param{Start};
990        $end   = $param{End};
991    } else {
992        if ($archiver->date_based() && $archiver->can('date_range')) {
993            ( $start, $end ) = $archiver->date_range( $entry->authored_on );
994        }
995    }
996
997    ## For each mapping, we need to rebuild the entries we loaded above in
998    ## the particular template map, and write it to the specified archive
999    ## file template.
1000    require MT::Template;
1001    require MT::Template::Context;
1002    for my $map (@map) {
1003        next unless $map->build_type; # ignore disabled template maps
1004
1005        my $ctx = MT::Template::Context->new;
1006        $ctx->{current_archive_type} = $at;
1007        $ctx->{archive_type}         = $at;
1008        $mt->rebuild_file(
1009            $blog, $arch_root, $map, $at, $ctx, \my %cond,
1010            !$param{NoStatic},
1011            Category  => $param{Category},
1012            Entry     => $entry,
1013            Author    => $param{Author},
1014            StartDate => $start,
1015            EndDate   => $end,
1016            Force     => $param{Force} ? 1 : 0,
1017        ) or return;
1018        $done->{ $map->{__saved_output_file} }++;
1019    }
1020    1;
1021}
1022
1023sub rebuild_file {
1024    my $mt = shift;
1025    my ( $blog, $root_path, $map, $at, $ctx, $cond, $build_static, %args )
1026      = @_;
1027    my $finfo;
1028    my $archiver = $mt->archiver($at);
1029    my ( $entry, $start, $end, $category, $author );
1030
1031    if ( $finfo = $args{FileInfo} ) {
1032        $args{Author}   = $finfo->author_id   if $finfo->author_id;
1033        $args{Category} = $finfo->category_id if $finfo->category_id;
1034        $args{Entry}    = $finfo->entry_id    if $finfo->entry_id;
1035        $map ||= MT::TemplateMap->load( $finfo->templatemap_id );
1036        $at  ||= $finfo->archive_type;
1037        if ( $finfo->startdate ) {
1038            if ( ( $start, $end ) = $archiver->date_range($finfo->startdate) ) {
1039                $args{StartDate} = $start;
1040                $args{EndDate}   = $end;
1041            }
1042        }
1043    }
1044
1045    # Calculate file path and URL for the new entry.
1046    my $file = File::Spec->catfile( $root_path, $map->{__saved_output_file} );
1047
1048    ## Untaint. We have to assume that we can trust the user's setting of
1049    ## the archive_path, and nothing else is based on user input.
1050    ($file) = $file =~ /(.+)/s;
1051
1052    # compare file modification time to start of build process. if it
1053    # is greater than the start_time, then we shouldn't need to build this
1054    # file again
1055    my $fmgr = $blog->file_mgr;
1056    if (my $mod_time = $fmgr->file_mod_time($file)) {
1057        return 1 if $mod_time >= $mt->start_time;
1058    }
1059
1060    if ( $archiver->category_based ) {
1061        $category = $args{Category};
1062        die "Category archive type requires Category parameter"
1063          unless $args{Category};
1064        $category = MT::Category->load($category)
1065          unless ref $category;
1066        $ctx->var( 'category_archive', 1 );
1067        $ctx->{__stash}{archive_category} = $category;
1068    }
1069    if ( $archiver->entry_based ) {
1070        $entry = $args{Entry};
1071        die "$at archive type requires Entry parameter"
1072          unless $entry;
1073        require MT::Entry;
1074        $entry = MT::Entry->load($entry) if !ref $entry;
1075        $ctx->var( 'entry_archive', 1 );
1076        $ctx->{__stash}{entry} = $entry;
1077    }
1078    if ( $archiver->date_based ) {
1079        # Date-based archive type
1080        $start = $args{StartDate};
1081        $end   = $args{EndDate};
1082        Carp::confess("Date-based archive types require StartDate parameter")
1083          unless $args{StartDate};
1084        $ctx->var( 'datebased_archive', 1 );
1085    }
1086    if ( $archiver->author_based ) {
1087
1088        # author based archive type
1089        $author = $args{Author};
1090        die "Author-based archive type requires Author parameter"
1091          unless $args{Author};
1092        require MT::Author;
1093        $author = MT::Author->load($author)
1094          unless ref $author;
1095        $ctx->var( 'author_archive', 1 );
1096        $ctx->{__stash}{author} = $author;
1097    }
1098    local $ctx->{current_timestamp}     = $start if $start;
1099    local $ctx->{current_timestamp_end} = $end   if $end;
1100
1101    $ctx->{__stash}{blog} = $blog;
1102
1103    require MT::FileInfo;
1104
1105# This kind of testing should be done at the time we save a post,
1106# not during publishing!!!
1107# if ($archiver->entry_based) {
1108#     my $fcount = MT::FileInfo->count({
1109#         blog_id => $blog->id,
1110#         entry_id => $entry->id,
1111#         file_path => $file},
1112#         { not => { entry_id => 1 } });
1113#     die MT->translate('The same archive file exists. You should change the basename or the archive path. ([_1])', $file) if $fcount > 0;
1114# }
1115
1116    my $url = $blog->archive_url;
1117    $url = $blog->site_url
1118      if $archiver->entry_based && $archiver->entry_class eq 'page';
1119    $url .= '/' unless $url =~ m|/$|;
1120    $url .= $map->{__saved_output_file};
1121
1122    my $tmpl_id = $map->template_id;
1123
1124    # template specific for this entry (or page, as the case may be)
1125    if ( $entry && $entry->template_id ) {
1126
1127        # allow entry to override *if* we're publishing an individual
1128        # page, and this is the 'preferred' one...
1129        if ( $archiver->entry_based ) {
1130            if ( $map->is_preferred ) {
1131                $tmpl_id = $entry->template_id;
1132            }
1133        }
1134    }
1135
1136    my $tmpl = MT::Template->load($tmpl_id);
1137    $tmpl->context($ctx);
1138
1139    # From Here
1140    if ( my $tmpl_param = $archiver->template_params ) {
1141        $tmpl->param($tmpl_param);
1142    }
1143
1144    my ($rel_url) = ( $url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)| );
1145    $rel_url =~ s|//+|/|g;
1146
1147    # Clear out all the FileInfo records that might point at the page
1148    # we're about to create
1149    # FYI: if it's an individual entry, we don't use the date as a
1150    #      criterion, since this could actually have changed since
1151    #      the FileInfo was last built. When the date does change,
1152    #      the old date-based archive doesn't necessarily get fixed,
1153    #      but if another comes along it will get corrected
1154    unless ($finfo) {
1155        my %terms;
1156        $terms{blog_id}     = $blog->id;
1157        $terms{category_id} = $category->id if $archiver->category_based;
1158        $terms{author_id}   = $author->id if $archiver->author_based;
1159        $terms{entry_id}    = $entry->id if $archiver->entry_based;
1160        $terms{startdate}   = $start
1161          if $archiver->date_based && ( !$archiver->entry_based );
1162        $terms{archive_type}   = $at;
1163        $terms{templatemap_id} = $map->id;
1164        my @finfos = MT::FileInfo->load( \%terms );
1165
1166        if (   ( scalar @finfos == 1 )
1167            && ( $finfos[0]->file_path eq $file )
1168            && ( ( $finfos[0]->url || '' ) eq $rel_url )
1169            && ( $finfos[0]->template_id == $tmpl_id ) )
1170        {
1171
1172            # if the shoe fits, wear it
1173            $finfo = $finfos[0];
1174        }
1175        else {
1176
1177           # if the shoe don't fit, remove all shoes and create the perfect shoe
1178            foreach (@finfos) { $_->remove(); }
1179
1180            $finfo = MT::FileInfo->set_info_for_url(
1181                $rel_url, $file, $at,
1182                {
1183                    Blog        => $blog->id,
1184                    TemplateMap => $map->id,
1185                    Template    => $tmpl_id,
1186                    ( $archiver->entry_based && $entry )
1187                    ? ( Entry => $entry->id )
1188                    : (),
1189                    StartDate => $start,
1190                    ( $archiver->category_based && $category )
1191                    ? ( Category => $category->id )
1192                    : (),
1193                    ( $archiver->author_based )
1194                    ? ( Author => $author->id )
1195                    : (),
1196                }
1197              )
1198              || die "Couldn't create FileInfo because "
1199              . MT::FileInfo->errstr();
1200        }
1201    }
1202
1203    # If you rebuild when you've just switched to dynamic pages,
1204    # we move the file that might be there so that the custom
1205    # 404 will be triggered.
1206    require MT::PublishOption;
1207    if ( $map->build_type == MT::PublishOption::DYNAMIC() ) 
1208    {
1209        rename(
1210            $finfo->file_path,    # is this just $file ?
1211            $finfo->file_path . '.static'
1212        );
1213
1214        ## If the FileInfo is set to static, flip it to virtual.
1215        if ( !$finfo->virtual ) {
1216            $finfo->virtual(1);
1217            $finfo->save();
1218        }
1219    }
1220
1221    return 1 if ( $map->build_type == MT::PublishOption::DYNAMIC() );
1222    return 1 if ( $entry && $entry->status != MT::Entry::RELEASE() );
1223    return 1 unless ( $map->build_type );
1224
1225    my $timer = MT->get_timer;
1226    if ($timer) {
1227        $timer->pause_partial;
1228    }
1229    local $timer->{elapsed} = 0 if $timer;
1230
1231    if (
1232        $build_static
1233        && MT->run_callbacks(
1234            'build_file_filter',
1235            Context      => $ctx,
1236            context      => $ctx,
1237            ArchiveType  => $at,
1238            archive_type => $at,
1239            TemplateMap  => $map,
1240            template_map => $map,
1241            Blog         => $blog,
1242            blog         => $blog,
1243            Entry        => $entry,
1244            entry        => $entry,
1245            FileInfo     => $finfo,
1246            file_info    => $finfo,
1247            File         => $file,
1248            file         => $file,
1249            Template     => $tmpl,
1250            template     => $tmpl,
1251            PeriodStart  => $start,
1252            period_start => $start,
1253            Category     => $category,
1254            category     => $category,
1255            force        => ($args{Force} ? 1 : 0),
1256        )
1257      )
1258    {
1259
1260        if ( $archiver->group_based ) {
1261            require MT::Promise;
1262            my $entries = sub { $archiver->archive_group_entries($ctx) };
1263            $ctx->stash( 'entries', MT::Promise::delay($entries) );
1264        }
1265
1266        my $html = undef;
1267        $ctx->stash( 'blog', $blog );
1268        $ctx->stash( 'entry', $entry ) if $entry;
1269
1270        require MT::Request;
1271        MT::Request->instance->cache('build_template', $tmpl);
1272
1273        $html = $tmpl->build( $ctx, $cond );
1274        unless (defined($html)) {
1275            $timer->unpause if $timer;
1276            require MT::I18N;
1277            return $mt->error(
1278            (
1279                $category ? MT->translate(
1280                    "An error occurred publishing [_1] '[_2]': [_3]",
1281                    MT::I18N::lowercase( $category->class_label ),
1282                    $category->id,
1283                    $tmpl->errstr
1284                  )
1285                : $entry ? MT->translate(
1286                    "An error occurred publishing [_1] '[_2]': [_3]",
1287                    MT::I18N::lowercase( $entry->class_label ),
1288                    $entry->title,
1289                    $tmpl->errstr
1290                  )
1291                : MT->translate(
1292"An error occurred publishing date-based archive '[_1]': [_2]",
1293                    $at . $start,
1294                    $tmpl->errstr
1295                )
1296            )
1297          );
1298        }
1299
1300        # Some browsers throw you to quirks mode if the doctype isn't
1301        # up front and leading whitespace makes a feed invalid.
1302        $html =~ s/\A\s+(<(?:\?xml|!DOCTYPE))/$1/s;
1303
1304        my $orig_html = $html;
1305        MT->run_callbacks(
1306            'build_page',
1307            Context      => $ctx,
1308            context      => $ctx,
1309            ArchiveType  => $at,
1310            archive_type => $at,
1311            TemplateMap  => $map,
1312            template_map => $map,
1313            Blog         => $blog,
1314            blog         => $blog,
1315            Entry        => $entry,
1316            entry        => $entry,
1317            FileInfo     => $finfo,
1318            file_info    => $finfo,
1319            PeriodStart  => $start,
1320            period_start => $start,
1321            Category     => $category,
1322            category     => $category,
1323            RawContent   => \$orig_html,
1324            raw_content  => \$orig_html,
1325            Content      => \$html,
1326            content      => \$html,
1327            BuildResult  => \$orig_html,
1328            build_result => \$orig_html,
1329            Template     => $tmpl,
1330            template     => $tmpl,
1331            File         => $file,
1332            file         => $file
1333        );
1334        ## First check whether the content is actually
1335        ## changed. If not, we won't update the published
1336        ## file, so as not to modify the mtime.
1337        unless ($fmgr->content_is_updated( $file, \$html )) {
1338            $timer->unpause if $timer;
1339            return 1;
1340        }
1341
1342        ## Determine if we need to build directory structure,
1343        ## and build it if we do. DirUmask determines
1344        ## directory permissions.
1345        require File::Spec;
1346        my $path = dirname($file);
1347        $path =~ s!/$!!
1348          unless $path eq '/'; ## OS X doesn't like / at the end in mkdir().
1349        unless ( $fmgr->exists($path) ) {
1350            if (!$fmgr->mkpath($path)) {
1351                $timer->unpause if $timer;
1352                return $mt->trans_error( "Error making path '[_1]': [_2]",
1353                    $path, $fmgr->errstr );
1354            }
1355        }
1356
1357        ## By default we write all data to temp files, then rename
1358        ## the temp files to the real files (an atomic
1359        ## operation). Some users don't like this (requires too
1360        ## liberal directory permissions). So we have a config
1361        ## option to turn it off (NoTempFiles).
1362        my $use_temp_files = !$mt->{NoTempFiles};
1363        my $temp_file = $use_temp_files ? "$file.new" : $file;
1364        unless ( defined $fmgr->put_data( $html, $temp_file ) ) {
1365            $timer->unpause if $timer;
1366            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1367                $temp_file, $fmgr->errstr );
1368        }
1369        if ($use_temp_files) {
1370            if (!$fmgr->rename( $temp_file, $file )) {
1371                $timer->unpause if $timer;
1372                return $mt->trans_error(
1373                    "Renaming tempfile '[_1]' failed: [_2]",
1374                    $temp_file, $fmgr->errstr );
1375            }
1376        }
1377        MT->run_callbacks(
1378            'build_file',
1379            Context      => $ctx,
1380            context      => $ctx,
1381            ArchiveType  => $at,
1382            archive_type => $at,
1383            TemplateMap  => $map,
1384            template_map => $map,
1385            FileInfo     => $finfo,
1386            file_info    => $finfo,
1387            Blog         => $blog,
1388            blog         => $blog,
1389            Entry        => $entry,
1390            entry        => $entry,
1391            PeriodStart  => $start,
1392            period_start => $start,
1393            RawContent   => \$orig_html,
1394            raw_content  => \$orig_html,
1395            Content      => \$html,
1396            content      => \$html,
1397            BuildResult  => \$orig_html,
1398            build_result => \$orig_html,
1399            Template     => $tmpl,
1400            template     => $tmpl,
1401            Category     => $category,
1402            category     => $category,
1403            File         => $file,
1404            file         => $file
1405        );
1406    }
1407    $timer->mark("total:rebuild_file[template_id:" . $tmpl->id . "]")
1408        if $timer;
1409    1;
1410}
1411
1412sub rebuild_indexes {
1413    my $mt    = shift;
1414    my %param = @_;
1415    require MT::Template;
1416    require MT::Template::Context;
1417    require MT::Entry;
1418
1419    my $blog;
1420    $blog = $param{Blog}
1421        if defined $param{Blog};
1422    if (!$blog && defined $param{BlogID}) {
1423        my $blog_id = $param{BlogID};
1424        $blog = MT::Blog->load($blog_id)
1425          or return $mt->error(
1426            MT->translate(
1427                "Load of blog '[_1]' failed: [_2]", $blog_id,
1428                MT::Blog->errstr
1429            )
1430          );
1431    }
1432    my $tmpl = $param{Template};
1433    if ($tmpl && (!$blog || $blog->id != $tmpl->blog_id)) {
1434        $blog = MT::Blog->load( $tmpl->blog_id );
1435    }
1436
1437    return $mt->error(
1438        MT->translate(
1439            "Blog, BlogID or Template param must be specified.")
1440    ) unless $blog;
1441
1442    return 1 if $blog->is_dynamic;
1443    my $iter;
1444    if ($tmpl) {
1445        my $i = 0;
1446        $iter = sub { $i++ < 1 ? $tmpl : undef };
1447    }
1448    else {
1449        $iter = MT::Template->load_iter(
1450            {
1451                type    => 'index',
1452                blog_id => $blog->id
1453            }
1454        );
1455    }
1456    my $force = $param{Force};
1457
1458    local *FH;
1459    my $site_root = $blog->site_path;
1460    return $mt->error(
1461        MT->translate("You did not set your blog publishing path") )
1462      unless $site_root;
1463    my $fmgr = $blog->file_mgr;
1464    while ( my $tmpl = $iter->() ) {
1465        ## Skip index templates that the user has designated not to be
1466        ## rebuilt automatically. We need to do the defined-ness check
1467        ## because we added the flag in 2.01, and for templates saved
1468        ## before that time, the rebuild_me flag will be undefined. But
1469        ## we assume that these templates should be rebuilt, since that
1470        ## was the previous behavior.
1471        ## Note that dynamic templates do need to be "rebuilt"--the
1472        ## FileInfo table needs to be maintained.
1473        if ( !$tmpl->build_dynamic && !$force ) {
1474            next if ( defined $tmpl->rebuild_me && !$tmpl->rebuild_me );
1475        }
1476        next if ( defined $tmpl->build_type && !$tmpl->build_type );
1477
1478        my $file = $tmpl->outfile;
1479        $file = '' unless defined $file;
1480        if ( $tmpl->build_dynamic && ( $file eq '' ) ) {
1481            next;
1482        }
1483        return $mt->error(
1484            MT->translate(
1485                "Template '[_1]' does not have an Output File.",
1486                $tmpl->name
1487            )
1488        ) unless $file ne '';
1489        my $url = join( '/', $blog->site_url, $file );
1490        unless ( File::Spec->file_name_is_absolute($file) ) {
1491            $file = File::Spec->catfile( $site_root, $file );
1492        }
1493
1494        # Everything from here out is identical with rebuild_file
1495        my ($rel_url) = ( $url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)| );
1496        $rel_url =~ s|//+|/|g;
1497        ## Untaint. We have to assume that we can trust the user's setting of
1498        ## the site_path and the template outfile.
1499        ($file) = $file =~ /(.+)/s;
1500        my $finfo = $param{FileInfo};  # available for single template calls
1501        unless ( $finfo ) {
1502            require MT::FileInfo;
1503            my @finfos = MT::FileInfo->load(
1504                {
1505                    blog_id     => $tmpl->blog_id,
1506                    template_id => $tmpl->id
1507                }
1508            );
1509            if (   ( scalar @finfos == 1 )
1510                && ( $finfos[0]->file_path eq $file )
1511                && ( ( $finfos[0]->url || '' ) eq $rel_url ) )
1512            {
1513                $finfo = $finfos[0];
1514            }
1515            else {
1516                foreach (@finfos) { $_->remove(); }
1517                $finfo = MT::FileInfo->set_info_for_url(
1518                    $rel_url, $file, 'index',
1519                    {
1520                        Blog     => $tmpl->blog_id,
1521                        Template => $tmpl->id,
1522                    }
1523                  )
1524                  || die "Couldn't create FileInfo because " . MT::FileInfo->errstr;
1525            }
1526        }
1527        if ( $tmpl->build_dynamic ) {
1528            rename( $file, $file . ".static" );
1529
1530            ## If the FileInfo is set to static, flip it to virtual.
1531            if ( !$finfo->virtual ) {
1532                $finfo->virtual(1);
1533                $finfo->save();
1534            }
1535        }
1536
1537        next if ( $tmpl->build_dynamic );
1538        next unless ( $tmpl->build_type );
1539
1540        ## We're not building dynamically, so if the FileInfo is currently
1541        ## set as dynamic (virtual), change it to static.
1542        if ( $finfo && $finfo->virtual ) {
1543            $finfo->virtual(0);
1544            $finfo->save();
1545        }
1546
1547        my $timer = MT->get_timer;
1548        if ($timer) {
1549            $timer->pause_partial;
1550        }
1551        local $timer->{elapsed} = 0 if $timer;
1552
1553        my $ctx = MT::Template::Context->new;
1554        next
1555          unless (
1556            MT->run_callbacks(
1557                'build_file_filter',
1558                Context      => $ctx,
1559                context      => $ctx,
1560                ArchiveType  => 'index',
1561                archive_type => 'index',
1562                Blog         => $blog,
1563                blog         => $blog,
1564                FileInfo     => $finfo,
1565                file_info    => $finfo,
1566                Template     => $tmpl,
1567                template     => $tmpl,
1568                File         => $file,
1569                file         => $file,
1570                force        => $force,
1571            )
1572          );
1573        $ctx->stash( 'blog', $blog );
1574
1575        require MT::Request;
1576        MT::Request->instance->cache('build_template', $tmpl);
1577
1578        my $html = $tmpl->build($ctx);
1579        unless (defined $html) {
1580            $timer->unpause if $timer;
1581            return $mt->error( $tmpl->errstr );
1582        }
1583
1584        # Some browsers throw you to quirks mode if the doctype isn't
1585        # up front and leading whitespace makes a feed invalid.
1586        $html =~ s/\A\s+(<(?:\?xml|!DOCTYPE))/$1/s;
1587
1588        my $orig_html = $html;
1589        MT->run_callbacks(
1590            'build_page',
1591            Context      => $ctx,
1592            context      => $ctx,
1593            Blog         => $blog,
1594            blog         => $blog,
1595            FileInfo     => $finfo,
1596            file_info    => $finfo,
1597            ArchiveType  => 'index',
1598            archive_type => 'index',
1599            RawContent   => \$orig_html,
1600            raw_content  => \$orig_html,
1601            Content      => \$html,
1602            content      => \$html,
1603            BuildResult  => \$orig_html,
1604            build_result => \$orig_html,
1605            Template     => $tmpl,
1606            template     => $tmpl,
1607            File         => $file,
1608            file         => $file
1609        );
1610
1611        ## First check whether the content is actually changed. If not,
1612        ## we won't update the published file, so as not to modify the mtime.
1613        next unless $fmgr->content_is_updated( $file, \$html );
1614
1615        ## Determine if we need to build directory structure,
1616        ## and build it if we do. DirUmask determines
1617        ## directory permissions.
1618        require File::Spec;
1619        my $path = dirname($file);
1620        $path =~ s!/$!!
1621          unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
1622        unless ( $fmgr->exists($path) ) {
1623            if (! $fmgr->mkpath($path) ) {
1624                $timer->unpause if $timer;
1625                return $mt->trans_error( "Error making path '[_1]': [_2]",
1626                    $path, $fmgr->errstr );
1627            }
1628        }
1629
1630        ## Update the published file.
1631        my $use_temp_files = !$mt->{NoTempFiles};
1632        my $temp_file = $use_temp_files ? "$file.new" : $file;
1633        unless (defined( $fmgr->put_data( $html, $temp_file ) )) {
1634            $timer->unpause if $timer;
1635            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1636                $temp_file, $fmgr->errstr );
1637        }
1638        if ($use_temp_files) {
1639            if (!$fmgr->rename( $temp_file, $file )) {
1640                $timer->unpause if $timer;
1641                return $mt->trans_error( "Renaming tempfile '[_1]' failed: [_2]",
1642                    $temp_file, $fmgr->errstr );
1643            }
1644        }
1645        MT->run_callbacks(
1646            'build_file',
1647            Context      => $ctx,
1648            context      => $ctx,
1649            ArchiveType  => 'index',
1650            archive_type => 'index',
1651            FileInfo     => $finfo,
1652            file_info    => $finfo,
1653            Blog         => $blog,
1654            blog         => $blog,
1655            RawContent   => \$orig_html,
1656            raw_content  => \$orig_html,
1657            Content      => \$html,
1658            content      => \$html,
1659            BuildResult  => \$orig_html,
1660            build_result => \$orig_html,
1661            Template     => $tmpl,
1662            template     => $tmpl,
1663            File         => $file,
1664            file         => $file
1665        );
1666
1667        $timer->mark("total:rebuild_indexes[template_id:" . $tmpl->id . ";file:$file]")
1668            if $timer;
1669    }
1670    1;
1671}
1672
1673sub rebuild_from_fileinfo {
1674    my $pub = shift;
1675    my ($fi) = @_;
1676
1677    require MT::Blog;
1678    require MT::Entry;
1679    require MT::Category;
1680    require MT::Template;
1681    require MT::TemplateMap;
1682    require MT::Template::Context;
1683
1684    my $at = $fi->archive_type
1685      or return $pub->error(
1686        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
1687
1688    # callback for custom archive types
1689    return
1690      unless MT->run_callbacks(
1691        'build_archive_filter',
1692        archive_type => $at,
1693        file_info    => $fi
1694      );
1695
1696    if ( $at eq 'index' ) {
1697        $pub->rebuild_indexes(
1698            BlogID   => $fi->blog_id,
1699            Template => MT::Template->load( $fi->template_id ),
1700            FileInfo => $fi,
1701            Force    => 1,
1702        ) or return;
1703        return 1;
1704    }
1705
1706    return 1 if $at eq 'None';
1707
1708    my ( $start, $end );
1709    my $blog = MT::Blog->load( $fi->blog_id )
1710      if $fi->blog_id;
1711    my $entry = MT::Entry->load( $fi->entry_id )
1712      or return $pub->error(
1713        MT->translate( "Parameter '[_1]' is required", 'Entry' ) )
1714      if $fi->entry_id;
1715    if ( $fi->startdate ) {
1716        my $archiver = $pub->archiver($at);
1717
1718        if ( ( $start, $end ) = $archiver->date_range( $fi->startdate ) ) {
1719            $entry = MT::Entry->load( { authored_on => [ $start, $end ] },
1720                { range_incl => { authored_on => 1 }, limit => 1 } )
1721              or return $pub->error(
1722                MT->translate( "Parameter '[_1]' is required", 'Entry' ) );
1723        }
1724    }
1725    my $cat = MT::Category->load( $fi->category_id )
1726      if $fi->category_id;
1727    my $author = MT::Author->load( $fi->author_id )
1728      if $fi->author_id;
1729
1730    ## Load the template-archive-type map entries for this blog and
1731    ## archive type. We do this before we load the list of entries, because
1732    ## we will run through the files and check if we even need to rebuild
1733    ## anything. If there is nothing to rebuild at all for this entry,
1734    ## we save some time by not loading the list of entries.
1735    my $map = MT::TemplateMap->load( $fi->templatemap_id );
1736    my $file = $pub->archive_file_for( $entry, $blog, $at, $cat, $map,
1737        undef, $author );
1738    if ( !defined($file) ) {
1739        return $pub->error( $blog->errstr() );
1740    }
1741    $map->{__saved_output_file} = $file;
1742
1743    my $ctx = MT::Template::Context->new;
1744    $ctx->{current_archive_type} = $at;
1745    if ( $start && $end ) {
1746        $ctx->{current_timestamp} = $start;
1747        $ctx->{current_timestamp_end} = $end;
1748    }
1749
1750    my $arch_root =
1751      ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1752    return $pub->error(
1753        MT->translate("You did not set your blog publishing path") )
1754      unless $arch_root;
1755
1756    my %cond;
1757    $pub->rebuild_file( $blog, $arch_root, $map, $at, $ctx, \%cond, 1,
1758        FileInfo => $fi, )
1759      or return;
1760
1761    1;
1762}
1763
1764sub trans_error {
1765    my $this = shift;
1766    return $this->error( MT->translate(@_) );
1767}
1768
1769sub publish_future_posts {
1770    my $this = shift;
1771
1772    require MT::Blog;
1773    require MT::Entry;
1774    require MT::Util;
1775    my $mt            = MT->instance;
1776    my $total_changed = 0;
1777    my @blogs = MT::Blog->load(undef, {
1778        join => MT::Entry->join_on('blog_id', {
1779            status => MT::Entry::FUTURE(),
1780        }, { unique => 1 })
1781    });
1782    foreach my $blog (@blogs) {
1783        my @ts = MT::Util::offset_time_list( time, $blog );
1784        my $now = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1,
1785          @ts[ 3, 2, 1, 0 ];
1786        my $iter = MT::Entry->load_iter(
1787            {
1788                blog_id => $blog->id,
1789                status  => MT::Entry::FUTURE(),
1790                class   => '*'
1791            },
1792            {
1793                'sort'    => 'authored_on',
1794                direction => 'descend'
1795            }
1796        );
1797        my @queue;
1798        while ( my $entry = $iter->() ) {
1799            push @queue, $entry->id if $entry->authored_on le $now;
1800        }
1801
1802        my $changed = 0;
1803        my @results;
1804        my %rebuild_queue;
1805        my %ping_queue;
1806        foreach my $entry_id (@queue) {
1807            my $entry = MT::Entry->load($entry_id)
1808                or next;
1809            $entry->status( MT::Entry::RELEASE() );
1810            $entry->save
1811              or die $entry->errstr;
1812
1813            MT->run_callbacks( 'scheduled_post_published', $mt, $entry );
1814
1815            $rebuild_queue{ $entry->id } = $entry;
1816            $ping_queue{ $entry->id }    = 1;
1817            my $n = $entry->next(1);
1818            $rebuild_queue{ $n->id } = $n if $n;
1819            my $p = $entry->previous(1);
1820            $rebuild_queue{ $p->id } = $p if $p;
1821            $changed++;
1822            $total_changed++;
1823        }
1824        if ($changed) {
1825            my %rebuilt_okay;
1826            my $rebuilt;
1827            eval {
1828                foreach my $id ( keys %rebuild_queue )
1829                {
1830                    my $entry = $rebuild_queue{$id};
1831                    $mt->rebuild_entry( Entry => $entry, Blog => $blog )
1832                      or die $mt->errstr;
1833                    $rebuilt_okay{$id} = 1;
1834                    if ( $ping_queue{$id} ) {
1835                        $mt->ping_and_save( Entry => $entry, Blog => $blog );
1836                    }
1837                    $rebuilt++;
1838                }
1839                $mt->rebuild_indexes( Blog => $blog )
1840                  or die $mt->errstr;
1841            };
1842            if ( my $err = $@ ) {
1843
1844                # a fatal error occured while processing the rebuild
1845                # step. LOG the error and revert the entry/entries:
1846                require MT::Log;
1847                $mt->log(
1848                    {
1849                        message => $mt->translate(
1850"An error occurred while publishing scheduled entries: [_1]",
1851                            $err
1852                        ),
1853                        class   => "system",
1854                        blog_id => $blog->id,
1855                        level   => MT::Log::ERROR()
1856                    }
1857                );
1858                foreach my $id (@queue) {
1859                    next if exists $rebuilt_okay{$id};
1860                    my $e = $rebuild_queue{$id};
1861                    next unless $e;
1862                    $e->status( MT::Entry::FUTURE() );
1863                    $e->save or die $e->errstr;
1864                }
1865            }
1866        }
1867    }
1868    $total_changed > 0 ? 1 : 0;
1869}
1870
1871sub remove_entry_archive_file {
1872    my $mt    = shift;
1873    my %param = @_;
1874
1875    my $entry = $param{Entry};
1876    my $at    = $param{ArchiveType} || 'Individual';
1877    my $cat   = $param{Category};
1878    my $auth  = $param{Author};
1879
1880    require MT::TemplateMap;
1881    my $blog = $param{Blog};
1882    unless ($blog) {
1883        if ($entry) {
1884            $blog = $entry->blog;
1885        }
1886        elsif ($cat) {
1887            require MT::Blog;
1888            $blog = MT::Blog->load( $cat->blog_id )
1889                or return;
1890        }
1891    }
1892    my @map = MT::TemplateMap->load(
1893        {
1894            archive_type => $at,
1895            blog_id      => $blog->id,
1896            $param{TemplateID} ? ( template_id => $param{TemplateID} ) : (),
1897        }
1898    );
1899    return 1 unless @map;
1900
1901    my $fmgr = $blog->file_mgr;
1902    my $arch_root = ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1903
1904    require File::Spec;
1905    for my $map (@map) {
1906        my $file =
1907          $mt->archive_file_for( $entry, $blog, $at, $cat, $map, undef, $auth );
1908        $file = File::Spec->catfile( $arch_root, $file );
1909        if ( !defined($file) ) {
1910            die MT->translate( $blog->errstr() );
1911            return $mt->error( MT->translate( $blog->errstr() ) );
1912        }
1913
1914        # Run callbacks
1915        MT->run_callbacks( 'pre_delete_archive_file', $file, $at, $entry);
1916
1917        $fmgr->delete($file);
1918
1919        # Run callbacks
1920        MT->run_callbacks( 'post_delete_archive_file', $file, $at, $entry);
1921    }
1922    1;
1923}
1924
1925##
1926## archive_file_for takes an entry to determine the timestamps,
1927## but if the entry is not available it uses the time_start
1928## and time_end values
1929##
1930sub archive_file_for {
1931    my $mt = shift;
1932    init_archive_types() unless %ArchiveTypes;
1933
1934    my ( $entry, $blog, $at, $cat, $map, $timestamp, $author ) = @_;
1935    return if $at eq 'None';
1936    my $archiver = $mt->archiver($at);
1937    return '' unless $archiver;
1938
1939    my $file;
1940    if ( $blog->is_dynamic ) {
1941        require MT::TemplateMap;
1942        $map = MT::TemplateMap->new;
1943        $map->file_template( $archiver->dynamic_template );
1944    }
1945    unless ($map) {
1946        my $cache = MT::Request->instance->cache('maps');
1947        unless ($cache) {
1948            MT::Request->instance->cache( 'maps', $cache = {} );
1949        }
1950        unless ( $map = $cache->{ $blog->id . $at } ) {
1951            require MT::TemplateMap;
1952            $map = MT::TemplateMap->load(
1953                {
1954                    blog_id      => $blog->id,
1955                    archive_type => $at,
1956                    is_preferred => 1
1957                }
1958            );
1959            $cache->{ $blog->id . $at } = $map if $map;
1960        }
1961    }
1962    my $file_tmpl = $map->file_template if $map;
1963    unless ($file_tmpl) {
1964        if ( my $tmpls = $archiver->default_archive_templates ) {
1965            my ($default) = grep { $_->{default} } @$tmpls;
1966            $file_tmpl = $default->{template} if $default;
1967        }
1968    }
1969    $file_tmpl ||= '';
1970    my ($ctx);
1971    if ( $file_tmpl =~ m/\%[_-]?[A-Za-z]/ ) {
1972        if ( $file_tmpl =~ m/<\$?MT/i ) {
1973            $file_tmpl =~
1974s!(<\$?MT[^>]+?>)|(%[_-]?[A-Za-z])!$1 ? $1 : '<MTFileTemplate format="'. $2 . '">'!gie;
1975        }
1976        else {
1977            $file_tmpl = qq{<MTFileTemplate format="$file_tmpl">};
1978        }
1979    }
1980    if ($file_tmpl) {
1981        require MT::Template::Context;
1982        $ctx = MT::Template::Context->new;
1983        $ctx->stash( 'blog', $blog );
1984    }
1985    local $ctx->{__stash}{category}         = $cat if $cat;
1986    local $ctx->{__stash}{archive_category} = $cat if $cat;
1987    $timestamp = $entry->authored_on() if $entry;
1988    local $ctx->{__stash}{entry} = $entry if $entry;
1989    local $ctx->{__stash}{author} =
1990      $author ? $author : $entry ? $entry->author : undef;
1991
1992    my %blog_at = map { $_ => 1 } split /,/, $blog->archive_type;
1993    return '' unless $blog_at{$at};
1994
1995    $file = $archiver->archive_file(
1996        $ctx,
1997        Timestamp => $timestamp,
1998        Template  => $file_tmpl
1999    );
2000    if ( $file_tmpl && !$file ) {
2001        local $ctx->{archive_type} = $at;
2002        require MT::Builder;
2003        my $build = MT::Builder->new;
2004        my $tokens = $build->compile( $ctx, $file_tmpl )
2005          or return $blog->error( $build->errstr() );
2006        defined( $file = $build->build( $ctx, $tokens ) )
2007          or return $blog->error( $build->errstr() );
2008    }
2009    else {
2010        my $ext = $blog->file_extension;
2011        $file .= '.' . $ext if $ext;
2012    }
2013    $file;
2014}
2015
2016# Adds an element to the rebuild queue when the plugin is enabled.
2017sub queue_build_file_filter {
2018    my $mt = shift;
2019    my ( $cb, %args ) = @_;
2020
2021    my $fi = $args{file_info};
2022    return 1 if $fi->{from_queue};
2023
2024    require MT::PublishOption;
2025    my $throttle = MT::PublishOption::get_throttle($fi);
2026
2027    # Prevent building of disabled templates if they get this far
2028    return 0 if $throttle->{type} == MT::PublishOption::DISABLED();
2029
2030    # Check for 'force' flag for 'manual' publish option, which
2031    # forces the template to build; used for 'rebuild' list actions
2032    # and publish site operations
2033    if ($throttle->{type} == MT::PublishOption::MANUALLY()) {
2034        return $args{force} ? 1 : 0;
2035    }
2036
2037    # From here on, we're committed to publishing this file via TheSchwartz
2038    return 1 if $throttle->{type} != MT::PublishOption::ASYNC();
2039
2040    return 1 if $args{force}; # if async, but force is used, publish
2041
2042    require MT::TheSchwartz;
2043    require TheSchwartz::Job;
2044    my $job = TheSchwartz::Job->new();
2045    $job->funcname('MT::Worker::Publish');
2046    $job->uniqkey( $fi->id );
2047
2048    my $priority = 0;
2049
2050    my $at = $fi->archive_type || '';
2051    # Default priority assignment....
2052    if (($at eq 'Individual') || ($at eq 'Page')) {
2053        require MT::TemplateMap;
2054        my $map = MT::TemplateMap->load($fi->templatemap_id);
2055        # Individual/Page archive pages that are the 'permalink' pages
2056        # should have highest build priority.
2057        if ($map && $map->is_preferred) {
2058            $priority = 10;
2059        } else {
2060            $priority = 5;
2061        }
2062    } elsif ($at eq 'index') {
2063        # Index pages are second in priority, if they are named 'index'
2064        # or 'default'
2065        if ($fi->file_path =~ m!/(index|default|atom|feed)!i) {
2066            $priority = 9;
2067        } else {
2068            $priority = 8;
2069        }
2070    } elsif ($at =~ m/Category|Author/) {
2071        $priority = 1;
2072    } elsif ($at =~ m/Yearly/) {
2073        $priority = 1;
2074    } elsif ($at =~ m/Monthly/) {
2075        $priority = 2;
2076    } elsif ($at =~ m/Weekly/) {
2077        $priority = 3;
2078    } elsif ($at =~ m/Daily/) {
2079        $priority = 4;
2080    }
2081
2082    $job->priority( $priority );
2083    $job->coalesce( ( $fi->blog_id || 0 ) . ':' . $$ . ':' . $priority . ':' . ( time - ( time % 10 ) ) );
2084
2085    MT::TheSchwartz->insert($job);
2086
2087    return 0;
2088}
2089
20901;
2091
2092__END__
2093
2094=head1 NAME
2095
2096MT::WeblogPublisher - Express weblog templates and content into a specific URL structure
2097
2098=head1 SYNOPSIS
2099
2100    use MT::WeblogPublisher;
2101    my $pub = MT::WeblogPublisher->new;
2102    $pub->rebuild(Blog => $blog, ArchiveType => "Individual");
2103
2104=head1 METHODS
2105
2106=head2 MT::WeblogPublisher->new()
2107
2108Return a new C<MT::WeblogPublisher>. Additionally, call
2109L<MT::ConfigMgr/instance> and set the I<NoTempFiles> and
2110I<PublishCommenterIcon> attributes, if not already set.
2111
2112=head2 $mt->rebuild( %args )
2113
2114Rebuilds your entire blog, indexes and archives; or some subset of your blog,
2115as specified in the arguments.
2116
2117I<%args> can contain:
2118
2119=over 4
2120
2121=item * Blog
2122
2123An I<MT::Blog> object corresponding to the blog that you would like to
2124rebuild.
2125
2126Either this or C<BlogID> is required.
2127
2128=item * BlogID
2129
2130The ID of the blog that you would like to rebuild.
2131
2132Either this or C<Blog> is required.
2133
2134=item * ArchiveType
2135
2136The archive type that you would like to rebuild. This should be one of
2137the following string values: C<Individual>, C<Daily>, C<Weekly>,
2138C<Monthly>, or C<Category>.
2139
2140This argument is optional; if not provided, all archive types will be rebuilt.
2141
2142=item * EntryCallback
2143
2144A callback that will be called for each entry that is rebuilt. If provided,
2145the value should be a subroutine reference; the subroutine will be handed
2146the I<MT::Entry> object for the entry that is about to be rebuilt. You could
2147use this to keep a running log of which entry is being rebuilt, for example:
2148
2149    $mt->rebuild(
2150              BlogID => $blog_id,
2151              EntryCallback => sub { print $_[0]->title, "\n" },
2152          );
2153
2154Or to provide a status indicator:
2155
2156    use MT::Entry;
2157    my $total = MT::Entry->count({ blog_id => $blog_id });
2158    my $i = 0;
2159    local $| = 1;
2160    $mt->rebuild(
2161              BlogID => $blog_id,
2162              EntryCallback => sub { printf "%d/%d\r", ++$i, $total },
2163          );
2164    print "\n";
2165
2166This argument is optional; by default no callbacks are executed.
2167
2168=item * NoIndexes
2169
2170By default I<rebuild> will rebuild the index templates after rebuilding all
2171of the entries; if you do not want to rebuild the index templates, set the
2172value for this argument to a true value.
2173
2174This argument is optional.
2175
2176=item * Limit
2177
2178Limit the number of entries to be rebuilt to the last C<N> entries in the
2179blog. For example, if you set this to C<20> and do not provide an offset (see
2180L<Offset>, below), the 20 most recent entries in the blog will be rebuilt.
2181
2182This is only useful if you are rebuilding C<Individual> archives.
2183
2184This argument is optional; by default all entries will be rebuilt.
2185
2186=item * Offset
2187
2188When used with C<Limit>, specifies the entry at which to start rebuilding
2189your individual entry archives. For example, if you set this to C<10>, and
2190set a C<Limit> of C<5> (see L<Limit>, above), entries 10-14 (inclusive) will
2191be rebuilt. The offset starts at C<0>, and the ordering is reverse
2192chronological.
2193
2194This is only useful if you are rebuilding C<Individual> archives, and if you
2195are using C<Limit>.
2196
2197This argument is optional; by default all entries will be rebuilt, starting
2198at the first entry.
2199
2200=back
2201
2202=head2 $mt->rebuild_entry( %args )
2203
2204Rebuilds a particular entry in your blog (and its dependencies, if specified).
2205
2206I<%args> can contain:
2207
2208=over 4
2209
2210=item * Entry
2211
2212An I<MT::Entry> object corresponding to the object you would like to rebuild.
2213
2214This argument is required.
2215
2216=item * Blog
2217
2218An I<MT::Blog> object corresponding to the blog to which the I<Entry> belongs.
2219
2220This argument is optional; if not provided, the I<MT::Blog> object will be
2221loaded in I<rebuild_entry> from the I<$entry-E<gt>blog_id> column of the
2222I<MT::Entry> object passed in. If you already have the I<MT::Blog> object
2223loaded, however, it makes sense to pass it in yourself, as it will skip one
2224small step in I<rebuild_entry> (loading the object).
2225
2226=item * BuildDependencies
2227
2228Saving an entry can have effects on other entries; so after saving, it is
2229often necessary to rebuild other entries, to reflect the changes onto all
2230of the affected archive pages, indexes, etc.
2231
2232If you supply this parameter with a true value, I<rebuild_indexes> will
2233rebuild: the archives for the next and previous entries, chronologically;
2234all of the index templates; the archives for the next and previous daily,
2235weekly, and monthly archives.
2236
2237=item * Previous, Next, OldPrevious, OldNext
2238
2239These values identify entries which may need to be updated now that
2240"this" entry has changed. When the authored_on field of an entry is
2241changed, its new neighbors (Previous and Next) need to be rebuilt as
2242well as its former neighbors (OldPrevious and OldNext).
2243
2244=item * NoStatic
2245
2246When this value is true, it acts as a hint to the rebuilding routine
2247that static output files need not be rebuilt; the "rebuild" operation
2248is just to update the bookkeeping that supports dynamic rebuilds.
2249
2250=back
2251
2252=head2 $mt->rebuild_file($blog, $archive_root, $map, $archive_type, $ctx, \%cond, $build_static, %args)
2253
2254Method responsible for building a single archive page from a template and
2255writing it to the file management layer.
2256
2257I<$blog> refers to the target weblog. I<$archive_root> is the target archive
2258path to publish the file. I<$map> is a L<MT::TemplateMap> object that
2259relates to publishing the file. I<$archive_type> is one of "Daily",
2260"Weekly", "Monthly", "Category" or "Individual". I<$ctx> is a handle to
2261the L<MT::Template::Context> object to use to build the file. I<\%cond>
2262is a hashref to conditional arguments used to drive the build process.
2263I<$build_static> is a boolean flag that controls whether static files are
2264created (otherwise, the records necessary for serving dynamic pages are
2265created and that is all).
2266
2267I<%args> is a hash that uniquely identifies the specific instance
2268of the given archive type. That is, for a category archive page it
2269identifies the category; for a date-based archive page it identifies
2270which time period is covered by the page; for an individual archive it
2271identifies the entry. I<%args> should contain just one of these
2272keys:
2273
2274=over 4
2275
2276=item * Category
2277
2278A category ID or L<MT::Category> instance of the category archive page to
2279be built.
2280
2281=item * Entry
2282
2283An entry ID or L<MT::Entry> instance of the entry archive page to be
2284built.
2285
2286=item * StartDate
2287
2288The starting timestamp of the date-based archive to be built.
2289
2290=back
2291
2292=head2 $mt->rebuild_indexes( %args )
2293
2294Rebuilds all of the index templates in your blog, or just one, if you use
2295the I<Template> argument (below). Only rebuilds templates that are set to
2296be rebuilt automatically, unless you use the I<Force> (below).
2297
2298I<%args> can contain:
2299
2300=over 4
2301
2302=item * Blog
2303
2304An I<MT::Blog> object corresponding to the blog whose indexes you would like
2305to rebuild.
2306
2307Either this or C<BlogID> is required.
2308
2309=item * BlogID
2310
2311The ID of the blog whose indexes you would like to rebuild.
2312
2313Either this or C<Blog> is required.
2314
2315=item * Template
2316
2317An I<MT::Template> object specifying the index template to rebuild; if you use
2318this argument, I<only> this index template will be rebuilt.
2319
2320Note that if the template that you specify here is set to not rebuild
2321automatically, you I<must> specify the I<Force> argument in order to force it
2322to be rebuilt.
2323
2324=item * Force
2325
2326A boolean flag specifying whether or not to rebuild index templates who have
2327been marked not to be rebuilt automatically.
2328
2329The default is C<0> (do not rebuild such templates).
2330
2331=back
2332
2333=head2 $mt->trans_error
2334
2335Call L<MT/translate>.
2336
2337=head2 $mt->remove_entry_archive_file(%param)
2338
2339Delete the archive files for an entry based on the following
2340parameters.  One of a I<Blog>, I<Entry> or I<Category> are required.
2341
2342=over 4
2343
2344=item * Blog
2345=item * Entry
2346=item * Category
2347=item * ArchiveType (Default 'Individual')
2348=item * TemplateID
2349
2350=back
2351
2352=head2 $mt->rebuild_categories(%param)
2353
2354Rebuild category archives based on the following parameters:
2355
2356=over 4
2357
2358=item * Blog
2359=item * BlogID
2360=item * Limit
2361=item * Offset
2362=item * Limit
2363=item * NoStatic
2364
2365=back
2366
2367=head2 $mt->publish_future_posts
2368
2369Build and publish all scheduled entries with a I<authored_on> timestamp
2370that is less than the current time.
2371
2372=head1 CALLBACKS
2373
2374=over 4
2375
2376=item BuildFileFilter
2377
2378This filter is called when Movable Type wants to rebuild a file, but
2379before doing so. This gives plugins the chance to determine whether a
2380file should actually be rebuild in particular situations.
2381
2382A BuildFileFilter callback routine is called as follows:
2383
2384    sub build_file_filter($eh, %args)
2385    {
2386        ...
2387        return $boolean;
2388    }
2389
2390As with other callback funcions, the first parameter is an
2391C<MT::ErrorHandler> object. This can be used by the callback to
2392propagate an error message to the surrounding context.
2393
2394The C<%args> parameters identify the page to be built. See
2395L<MT::FileInfo> for more information on how a page is determined by
2396these parameters. Elements in C<%args> are as follows:
2397
2398=over 4
2399
2400=item C<Context>
2401
2402Holds the template context that has been constructed for building (see
2403C<MT::Template::Context>).
2404
2405=item C<ArchiveType>
2406
2407The archive type of the file, usually one of C<'Index'>,
2408C<'Individual'>, C<'Category'>, C<'Daily'>, C<'Monthly'>, or
2409C<'Weekly'>.
2410
2411=item C<Templatemap>
2412
2413An C<MT::TemplateMap> object; this singles out which template is being
2414built, and the filesystem path of the file to be written.
2415
2416=item C<Blog>
2417
2418The C<MT::Blog> object representing the blog whose pages are being
2419rebuilt.
2420
2421=item C<Entry>
2422
2423In the case of an individual archive page, this points to the
2424C<MT::Entry> object whose page is being rebuilt. In the case of an
2425archive page other than an individual page, this parameter is not
2426necessarily undefined. It is best to rely on the C<$at> parameter to
2427determine whether a single entry is on deck to be built.
2428
2429=item C<PeriodStart>
2430
2431In the case of a date-based archive page, this is a timestamp at the
2432beginning of the period from which entries will be included on this
2433page, in Movable Type's standard 14-digit "timestamp" format. For
2434example, if the page is a Daily archive for April 17, 1796, this value
2435would be 17960417000000. If the page were a Monthly archive for March,
24362003, C<$start> would be 20030301000000. Again, this parameter may be
2437defined even when the page on deck is not a date-based archive page.
2438
2439=item C<Category>
2440
2441In the case of a Category archive, this parameter identifies the
2442category which will be built on the page.
2443
2444=item C<FileInfo>
2445
2446If defined, an L<MT::FileInfo> object which contains information about the
2447file. See L<MT::FileInfo> for more information about what a C<MT::FileInfo>
2448contains. Chief amongst all the members of C<MT::FileInfo>, for these
2449purposes, will be the C<virtual> member. This is a boolean value which will be
2450false if a page was actually created on disk for this "page," and false if no
2451page was created (because the corresponding template is set to be
2452built dynamically).
2453
2454It is possible for the FileInfo parameter to be undefined, namely if the blog has not been configured to publish anything dynamically, or if the
2455installation is using a data driver that does not support dynamic publishing.
2456
2457=back
2458
2459=item BuildPage
2460
2461BuildPage callbacks are invoked just after a page has been built, but
2462before the content has been written to the file system.
2463
2464    sub build_page($eh, %args)
2465    {
2466    }
2467
2468The parameters given are include those sent to the BuildFileFilter callback.
2469In addition, the following parameters are also given:
2470
2471=over 4
2472
2473=item C<Content>
2474
2475This is a scalar reference to the content that will eventually be
2476published.
2477
2478=item C<BuildResult> / (or C<RawContent>, deprecated)
2479
2480This is a scalar reference to the content originally produced by building
2481the page. This value is provided mainly for reference; modifications to it
2482will be ignored.
2483
2484=back
2485
2486=item BuildFile
2487
2488BuildFile callbacks are invoked just after a file has been built.
2489
2490    sub build_file($eh, %args)
2491    {
2492    }
2493
2494Parameters in %args are as with BuildPage.
2495
2496=back
2497
2498=head1 AUTHOR & COPYRIGHT
2499
2500Please see L<MT/AUTHOR & COPYRIGHT>.
2501
2502=cut
Note: See TracBrowser for help on using the browser.