root/branches/release-41/lib/MT/WeblogPublisher.pm @ 2737

Revision 2737, 82.9 kB (checked in by takayama, 17 months ago)

Fixed BugId:80382
* Changed to removes fileinfo records when entry was deleted.

  • CMS, Atom and XMLRPC
  • 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' },
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_recip;
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_recip{$at}{ $cat->id }{ $start . $end }
475                              {'Start'} = $start;
476                            $rebuild_recip{$at}{ $cat->id }{ $start . $end }
477                              {'End'} = $end;
478                            $rebuild_recip{$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_recip{$at}{ $cat->id }{id} = $cat->id;
486                            $rebuild_recip{$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_recip{$at}{ $entry->author->id }
525                              { $start . $end }{'Start'} = $start;
526                            $rebuild_recip{$at}{ $entry->author->id }
527                              { $start . $end }{'End'} = $end;
528                            $rebuild_recip{$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_recip{$at}{ $entry->author->id }{id} =
535                              $entry->author->id;
536                            $rebuild_recip{$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_recip{$at}{ $start . $end }{'Start'} = $start;
543                        $rebuild_recip{$at}{ $start . $end }{'End'}   = $end;
544                        $rebuild_recip{$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_recip{Individual}{ $prev->id }{id} = $prev->id;
550                        $rebuild_recip{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_recip{Individual}{ $next->id }{id} = $next->id;
556                        $rebuild_recip{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_recip;
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### Recip 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 $recip = $param{Recip}
792        or return $mt->error(
793            MT->translate( "Parameter '[_1]' is required", 'Recip' ) );
794
795    for my $at (keys %$recip){
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 %{$recip->{$at}}) {
802                my $cat = MT::Category->load($cat_id)
803                    or next;
804                if ($archiver->date_based()) {
805                    for my $key (keys %{$recip->{$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       => $recip->{$at}->{$cat_id}->{$key}->{Start},
813                            End         => $recip->{$at}->{$cat_id}->{$key}->{End},
814                            File        => $recip->{$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        => $recip->{$at}->{$cat_id}->{File}
825                    ) or return;
826                }
827            }
828        } elsif ($archiver->author_based()) {
829            require MT::Author;
830            for my $auth_id (keys %{$recip->{$at}}) {
831                my $author = MT::Author->load($auth_id)
832                    or next;
833                if ($archiver->date_based()) {
834                    for my $key (keys %{$recip->{$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       => $recip->{$at}->{$auth_id}->{$key}->{Start},
842                            End         => $recip->{$at}->{$auth_id}->{$key}->{End},
843                            File        => $recip->{$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        => $recip->{$at}->{$auth_id}->{File}
854                    ) or return;
855                }
856            }
857        } elsif ($archiver->date_based()) {
858            for my $key (keys %{$recip->{$at}}) {
859                $mt->_rebuild_entry_archive_type(
860                    NoStatic    => 0,
861                    Force       => ($param{Force} ? 1 : 0),
862                    Blog        => $blog,
863                    ArchiveType => $at,
864                    Start       => $recip->{$at}->{$key}->{Start},
865                    End         => $recip->{$at}->{$key}->{End},
866                    File        => $recip->{$at}->{$key}->{File}
867                ) or return;
868            }
869        } else {
870            require MT::Entry;
871            for my $entry_id (keys %{$recip->{$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        => $recip->{$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        my $orig_html = $html;
1300        MT->run_callbacks(
1301            'build_page',
1302            Context      => $ctx,
1303            context      => $ctx,
1304            ArchiveType  => $at,
1305            archive_type => $at,
1306            TemplateMap  => $map,
1307            template_map => $map,
1308            Blog         => $blog,
1309            blog         => $blog,
1310            Entry        => $entry,
1311            entry        => $entry,
1312            FileInfo     => $finfo,
1313            file_info    => $finfo,
1314            PeriodStart  => $start,
1315            period_start => $start,
1316            Category     => $category,
1317            category     => $category,
1318            RawContent   => \$orig_html,
1319            raw_content  => \$orig_html,
1320            Content      => \$html,
1321            content      => \$html,
1322            BuildResult  => \$orig_html,
1323            build_result => \$orig_html,
1324            Template     => $tmpl,
1325            template     => $tmpl,
1326            File         => $file,
1327            file         => $file
1328        );
1329        ## First check whether the content is actually
1330        ## changed. If not, we won't update the published
1331        ## file, so as not to modify the mtime.
1332        unless ($fmgr->content_is_updated( $file, \$html )) {
1333            $timer->unpause if $timer;
1334            return 1;
1335        }
1336
1337        ## Determine if we need to build directory structure,
1338        ## and build it if we do. DirUmask determines
1339        ## directory permissions.
1340        require File::Spec;
1341        my $path = dirname($file);
1342        $path =~ s!/$!!
1343          unless $path eq '/'; ## OS X doesn't like / at the end in mkdir().
1344        unless ( $fmgr->exists($path) ) {
1345            if (!$fmgr->mkpath($path)) {
1346                $timer->unpause if $timer;
1347                return $mt->trans_error( "Error making path '[_1]': [_2]",
1348                    $path, $fmgr->errstr );
1349            }
1350        }
1351
1352        ## By default we write all data to temp files, then rename
1353        ## the temp files to the real files (an atomic
1354        ## operation). Some users don't like this (requires too
1355        ## liberal directory permissions). So we have a config
1356        ## option to turn it off (NoTempFiles).
1357        my $use_temp_files = !$mt->{NoTempFiles};
1358        my $temp_file = $use_temp_files ? "$file.new" : $file;
1359        unless ( defined $fmgr->put_data( $html, $temp_file ) ) {
1360            $timer->unpause if $timer;
1361            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1362                $temp_file, $fmgr->errstr );
1363        }
1364        if ($use_temp_files) {
1365            if (!$fmgr->rename( $temp_file, $file )) {
1366                $timer->unpause if $timer;
1367                return $mt->trans_error(
1368                    "Renaming tempfile '[_1]' failed: [_2]",
1369                    $temp_file, $fmgr->errstr );
1370            }
1371        }
1372        MT->run_callbacks(
1373            'build_file',
1374            Context      => $ctx,
1375            context      => $ctx,
1376            ArchiveType  => $at,
1377            archive_type => $at,
1378            TemplateMap  => $map,
1379            template_map => $map,
1380            FileInfo     => $finfo,
1381            file_info    => $finfo,
1382            Blog         => $blog,
1383            blog         => $blog,
1384            Entry        => $entry,
1385            entry        => $entry,
1386            PeriodStart  => $start,
1387            period_start => $start,
1388            RawContent   => \$orig_html,
1389            raw_content  => \$orig_html,
1390            Content      => \$html,
1391            content      => \$html,
1392            BuildResult  => \$orig_html,
1393            build_result => \$orig_html,
1394            Template     => $tmpl,
1395            template     => $tmpl,
1396            Category     => $category,
1397            category     => $category,
1398            File         => $file,
1399            file         => $file
1400        );
1401    }
1402    $timer->mark("total:rebuild_file[template_id:" . $tmpl->id . "]")
1403        if $timer;
1404    1;
1405}
1406
1407sub rebuild_indexes {
1408    my $mt    = shift;
1409    my %param = @_;
1410    require MT::Template;
1411    require MT::Template::Context;
1412    require MT::Entry;
1413    my $blog;
1414    unless ( $blog = $param{Blog} ) {
1415        my $blog_id = $param{BlogID};
1416        $blog = MT::Blog->load($blog_id)
1417          or return $mt->error(
1418            MT->translate(
1419                "Load of blog '[_1]' failed: [_2]", $blog_id,
1420                MT::Blog->errstr
1421            )
1422          );
1423    }
1424    my $tmpl = $param{Template};
1425    unless ($blog) {
1426        $blog = MT::Blog->load( $tmpl->blog_id );
1427    }
1428    return 1 if $blog->is_dynamic;
1429    my $iter;
1430    if ($tmpl) {
1431        my $i = 0;
1432        $iter = sub { $i++ < 1 ? $tmpl : undef };
1433    }
1434    else {
1435        $iter = MT::Template->load_iter(
1436            {
1437                type    => 'index',
1438                blog_id => $blog->id
1439            }
1440        );
1441    }
1442    my $force = $param{Force};
1443
1444    local *FH;
1445    my $site_root = $blog->site_path;
1446    return $mt->error(
1447        MT->translate("You did not set your blog publishing path") )
1448      unless $site_root;
1449    my $fmgr = $blog->file_mgr;
1450    while ( my $tmpl = $iter->() ) {
1451        ## Skip index templates that the user has designated not to be
1452        ## rebuilt automatically. We need to do the defined-ness check
1453        ## because we added the flag in 2.01, and for templates saved
1454        ## before that time, the rebuild_me flag will be undefined. But
1455        ## we assume that these templates should be rebuilt, since that
1456        ## was the previous behavior.
1457        ## Note that dynamic templates do need to be "rebuilt"--the
1458        ## FileInfo table needs to be maintained.
1459        if ( !$tmpl->build_dynamic && !$force ) {
1460            next if ( defined $tmpl->rebuild_me && !$tmpl->rebuild_me );
1461        }
1462        next if ( defined $tmpl->build_type && !$tmpl->build_type );
1463
1464        my $file = $tmpl->outfile;
1465        $file = '' unless defined $file;
1466        if ( $tmpl->build_dynamic && ( $file eq '' ) ) {
1467            next;
1468        }
1469        return $mt->error(
1470            MT->translate(
1471                "Template '[_1]' does not have an Output File.",
1472                $tmpl->name
1473            )
1474        ) unless $file ne '';
1475        my $url = join( '/', $blog->site_url, $file );
1476        unless ( File::Spec->file_name_is_absolute($file) ) {
1477            $file = File::Spec->catfile( $site_root, $file );
1478        }
1479
1480        # Everything from here out is identical with rebuild_file
1481        my ($rel_url) = ( $url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)| );
1482        $rel_url =~ s|//+|/|g;
1483        ## Untaint. We have to assume that we can trust the user's setting of
1484        ## the site_path and the template outfile.
1485        ($file) = $file =~ /(.+)/s;
1486        my $finfo = $param{FileInfo};  # available for single template calls
1487        unless ( $finfo ) {
1488            require MT::FileInfo;
1489            my @finfos = MT::FileInfo->load(
1490                {
1491                    blog_id     => $tmpl->blog_id,
1492                    template_id => $tmpl->id
1493                }
1494            );
1495            if (   ( scalar @finfos == 1 )
1496                && ( $finfos[0]->file_path eq $file )
1497                && ( ( $finfos[0]->url || '' ) eq $rel_url ) )
1498            {
1499                $finfo = $finfos[0];
1500            }
1501            else {
1502                foreach (@finfos) { $_->remove(); }
1503                $finfo = MT::FileInfo->set_info_for_url(
1504                    $rel_url, $file, 'index',
1505                    {
1506                        Blog     => $tmpl->blog_id,
1507                        Template => $tmpl->id,
1508                    }
1509                  )
1510                  || die "Couldn't create FileInfo because " . MT::FileInfo->errstr;
1511            }
1512        }
1513        if ( $tmpl->build_dynamic ) {
1514            rename( $file, $file . ".static" );
1515
1516            ## If the FileInfo is set to static, flip it to virtual.
1517            if ( !$finfo->virtual ) {
1518                $finfo->virtual(1);
1519                $finfo->save();
1520            }
1521        }
1522
1523        next if ( $tmpl->build_dynamic );
1524        next unless ( $tmpl->build_type );
1525
1526        ## We're not building dynamically, so if the FileInfo is currently
1527        ## set as dynamic (virtual), change it to static.
1528        if ( $finfo && $finfo->virtual ) {
1529            $finfo->virtual(0);
1530            $finfo->save();
1531        }
1532
1533        my $timer = MT->get_timer;
1534        if ($timer) {
1535            $timer->pause_partial;
1536        }
1537        local $timer->{elapsed} = 0 if $timer;
1538
1539        my $ctx = MT::Template::Context->new;
1540        next
1541          unless (
1542            MT->run_callbacks(
1543                'build_file_filter',
1544                Context      => $ctx,
1545                context      => $ctx,
1546                ArchiveType  => 'index',
1547                archive_type => 'index',
1548                Blog         => $blog,
1549                blog         => $blog,
1550                FileInfo     => $finfo,
1551                file_info    => $finfo,
1552                Template     => $tmpl,
1553                template     => $tmpl,
1554                File         => $file,
1555                file         => $file,
1556                force        => $force,
1557            )
1558          );
1559        $ctx->stash( 'blog', $blog );
1560
1561        require MT::Request;
1562        MT::Request->instance->cache('build_template', $tmpl);
1563
1564        my $html = $tmpl->build($ctx);
1565        unless (defined $html) {
1566            $timer->unpause if $timer;
1567            return $mt->error( $tmpl->errstr );
1568        }
1569
1570        my $orig_html = $html;
1571        MT->run_callbacks(
1572            'build_page',
1573            Context      => $ctx,
1574            context      => $ctx,
1575            Blog         => $blog,
1576            blog         => $blog,
1577            FileInfo     => $finfo,
1578            file_info    => $finfo,
1579            ArchiveType  => 'index',
1580            archive_type => 'index',
1581            RawContent   => \$orig_html,
1582            raw_content  => \$orig_html,
1583            Content      => \$html,
1584            content      => \$html,
1585            BuildResult  => \$orig_html,
1586            build_result => \$orig_html,
1587            Template     => $tmpl,
1588            template     => $tmpl,
1589            File         => $file,
1590            file         => $file
1591        );
1592
1593        ## First check whether the content is actually changed. If not,
1594        ## we won't update the published file, so as not to modify the mtime.
1595        next unless $fmgr->content_is_updated( $file, \$html );
1596
1597        ## Determine if we need to build directory structure,
1598        ## and build it if we do. DirUmask determines
1599        ## directory permissions.
1600        require File::Spec;
1601        my $path = dirname($file);
1602        $path =~ s!/$!!
1603          unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
1604        unless ( $fmgr->exists($path) ) {
1605            if (! $fmgr->mkpath($path) ) {
1606                $timer->unpause if $timer;
1607                return $mt->trans_error( "Error making path '[_1]': [_2]",
1608                    $path, $fmgr->errstr );
1609            }
1610        }
1611
1612        ## Update the published file.
1613        my $use_temp_files = !$mt->{NoTempFiles};
1614        my $temp_file = $use_temp_files ? "$file.new" : $file;
1615        unless (defined( $fmgr->put_data( $html, $temp_file ) )) {
1616            $timer->unpause if $timer;
1617            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1618                $temp_file, $fmgr->errstr );
1619        }
1620        if ($use_temp_files) {
1621            if (!$fmgr->rename( $temp_file, $file )) {
1622                $timer->unpause if $timer;
1623                return $mt->trans_error( "Renaming tempfile '[_1]' failed: [_2]",
1624                    $temp_file, $fmgr->errstr );
1625            }
1626        }
1627        MT->run_callbacks(
1628            'build_file',
1629            Context      => $ctx,
1630            context      => $ctx,
1631            ArchiveType  => 'index',
1632            archive_type => 'index',
1633            FileInfo     => $finfo,
1634            file_info    => $finfo,
1635            Blog         => $blog,
1636            blog         => $blog,
1637            RawContent   => \$orig_html,
1638            raw_content  => \$orig_html,
1639            Content      => \$html,
1640            content      => \$html,
1641            BuildResult  => \$orig_html,
1642            build_result => \$orig_html,
1643            Template     => $tmpl,
1644            template     => $tmpl,
1645            File         => $file,
1646            file         => $file
1647        );
1648
1649        $timer->mark("total:rebuild_indexes[template_id:" . $tmpl->id . ";file:$file]")
1650            if $timer;
1651    }
1652    1;
1653}
1654
1655sub rebuild_from_fileinfo {
1656    my $pub = shift;
1657    my ($fi) = @_;
1658
1659    require MT::Blog;
1660    require MT::Entry;
1661    require MT::Category;
1662    require MT::Template;
1663    require MT::TemplateMap;
1664    require MT::Template::Context;
1665
1666    my $at = $fi->archive_type
1667      or return $pub->error(
1668        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
1669
1670    # callback for custom archive types
1671    return
1672      unless MT->run_callbacks(
1673        'build_archive_filter',
1674        archive_type => $at,
1675        file_info    => $fi
1676      );
1677
1678    if ( $at eq 'index' ) {
1679        $pub->rebuild_indexes(
1680            BlogID   => $fi->blog_id,
1681            Template => MT::Template->load( $fi->template_id ),
1682            FileInfo => $fi,
1683            Force    => 1,
1684        ) or return;
1685        return 1;
1686    }
1687
1688    return 1 if $at eq 'None';
1689
1690    my ( $start, $end );
1691    my $blog = MT::Blog->load( $fi->blog_id )
1692      if $fi->blog_id;
1693    my $entry = MT::Entry->load( $fi->entry_id )
1694      or return $pub->error(
1695        MT->translate( "Parameter '[_1]' is required", 'Entry' ) )
1696      if $fi->entry_id;
1697    if ( $fi->startdate ) {
1698        my $archiver = $pub->archiver($at);
1699
1700        if ( ( $start, $end ) = $archiver->date_range( $fi->startdate ) ) {
1701            $entry = MT::Entry->load( { authored_on => [ $start, $end ] },
1702                { range_incl => { authored_on => 1 }, limit => 1 } )
1703              or return $pub->error(
1704                MT->translate( "Parameter '[_1]' is required", 'Entry' ) );
1705        }
1706    }
1707    my $cat = MT::Category->load( $fi->category_id )
1708      if $fi->category_id;
1709    my $author = MT::Author->load( $fi->author_id )
1710      if $fi->author_id;
1711
1712    ## Load the template-archive-type map entries for this blog and
1713    ## archive type. We do this before we load the list of entries, because
1714    ## we will run through the files and check if we even need to rebuild
1715    ## anything. If there is nothing to rebuild at all for this entry,
1716    ## we save some time by not loading the list of entries.
1717    my $map = MT::TemplateMap->load( $fi->templatemap_id );
1718    my $file = $pub->archive_file_for( $entry, $blog, $at, $cat, $map,
1719        undef, $author );
1720    if ( !defined($file) ) {
1721        return $pub->error( $blog->errstr() );
1722    }
1723    $map->{__saved_output_file} = $file;
1724
1725    my $ctx = MT::Template::Context->new;
1726    $ctx->{current_archive_type} = $at;
1727    if ( $start && $end ) {
1728        $ctx->{current_timestamp} = $start;
1729        $ctx->{current_timestamp_end} = $end;
1730    }
1731
1732    my $arch_root =
1733      ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1734    return $pub->error(
1735        MT->translate("You did not set your blog publishing path") )
1736      unless $arch_root;
1737
1738    my %cond;
1739    $pub->rebuild_file( $blog, $arch_root, $map, $at, $ctx, \%cond, 1,
1740        FileInfo => $fi, )
1741      or return;
1742
1743    1;
1744}
1745
1746sub trans_error {
1747    my $this = shift;
1748    return $this->error( MT->translate(@_) );
1749}
1750
1751sub publish_future_posts {
1752    my $this = shift;
1753
1754    require MT::Blog;
1755    require MT::Entry;
1756    require MT::Util;
1757    my $mt            = MT->instance;
1758    my $total_changed = 0;
1759    my @blogs = MT::Blog->load(undef, {
1760        join => MT::Entry->join_on('blog_id', {
1761            status => MT::Entry::FUTURE(),
1762        }, { unique => 1 })
1763    });
1764    foreach my $blog (@blogs) {
1765        my @ts = MT::Util::offset_time_list( time, $blog );
1766        my $now = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1,
1767          @ts[ 3, 2, 1, 0 ];
1768        my $iter = MT::Entry->load_iter(
1769            {
1770                blog_id => $blog->id,
1771                status  => MT::Entry::FUTURE(),
1772                class   => '*'
1773            },
1774            {
1775                'sort'    => 'authored_on',
1776                direction => 'descend'
1777            }
1778        );
1779        my @queue;
1780        while ( my $entry = $iter->() ) {
1781            push @queue, $entry->id if $entry->authored_on le $now;
1782        }
1783
1784        my $changed = 0;
1785        my @results;
1786        my %rebuild_queue;
1787        my %ping_queue;
1788        foreach my $entry_id (@queue) {
1789            my $entry = MT::Entry->load($entry_id)
1790                or next;
1791            $entry->status( MT::Entry::RELEASE() );
1792            $entry->save
1793              or die $entry->errstr;
1794
1795            MT->run_callbacks( 'scheduled_post_published', $mt, $entry );
1796
1797            $rebuild_queue{ $entry->id } = $entry;
1798            $ping_queue{ $entry->id }    = 1;
1799            my $n = $entry->next(1);
1800            $rebuild_queue{ $n->id } = $n if $n;
1801            my $p = $entry->previous(1);
1802            $rebuild_queue{ $p->id } = $p if $p;
1803            $changed++;
1804            $total_changed++;
1805        }
1806        if ($changed) {
1807            my %rebuilt_okay;
1808            my $rebuilt;
1809            eval {
1810                foreach my $id ( keys %rebuild_queue )
1811                {
1812                    my $entry = $rebuild_queue{$id};
1813                    $mt->rebuild_entry( Entry => $entry, Blog => $blog )
1814                      or die $mt->errstr;
1815                    $rebuilt_okay{$id} = 1;
1816                    if ( $ping_queue{$id} ) {
1817                        $mt->ping_and_save( Entry => $entry, Blog => $blog );
1818                    }
1819                    $rebuilt++;
1820                }
1821                $mt->rebuild_indexes( Blog => $blog )
1822                  or die $mt->errstr;
1823            };
1824            if ( my $err = $@ ) {
1825
1826                # a fatal error occured while processing the rebuild
1827                # step. LOG the error and revert the entry/entries:
1828                require MT::Log;
1829                $mt->log(
1830                    {
1831                        message => $mt->translate(
1832"An error occurred while publishing scheduled entries: [_1]",
1833                            $err
1834                        ),
1835                        class   => "system",
1836                        blog_id => $blog->id,
1837                        level   => MT::Log::ERROR()
1838                    }
1839                );
1840                foreach my $id (@queue) {
1841                    next if exists $rebuilt_okay{$id};
1842                    my $e = $rebuild_queue{$id};
1843                    next unless $e;
1844                    $e->status( MT::Entry::FUTURE() );
1845                    $e->save or die $e->errstr;
1846                }
1847            }
1848        }
1849    }
1850    $total_changed > 0 ? 1 : 0;
1851}
1852
1853sub remove_entry_archive_file {
1854    my $mt    = shift;
1855    my %param = @_;
1856
1857    my $entry = $param{Entry};
1858    my $at    = $param{ArchiveType} || 'Individual';
1859    my $cat   = $param{Category};
1860    my $auth  = $param{Author};
1861
1862    require MT::TemplateMap;
1863    my $blog = $param{Blog};
1864    unless ($blog) {
1865        if ($entry) {
1866            $blog = $entry->blog;
1867        }
1868        elsif ($cat) {
1869            require MT::Blog;
1870            $blog = MT::Blog->load( $cat->blog_id )
1871                or return;
1872        }
1873    }
1874    my @map = MT::TemplateMap->load(
1875        {
1876            archive_type => $at,
1877            blog_id      => $blog->id,
1878            $param{TemplateID} ? ( template_id => $param{TemplateID} ) : (),
1879        }
1880    );
1881    return 1 unless @map;
1882
1883    my $fmgr = $blog->file_mgr;
1884    my $arch_root = ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1885
1886    require File::Spec;
1887    for my $map (@map) {
1888        my $file =
1889          $mt->archive_file_for( $entry, $blog, $at, $cat, $map, undef, $auth );
1890        $file = File::Spec->catfile( $arch_root, $file );
1891        if ( !defined($file) ) {
1892            die MT->translate( $blog->errstr() );
1893            return $mt->error( MT->translate( $blog->errstr() ) );
1894        }
1895
1896        # Run callbacks
1897        MT->run_callbacks( 'pre_delete_archive_file', $file, $at, $entry);
1898
1899        $fmgr->delete($file);
1900
1901        # Run callbacks
1902        MT->run_callbacks( 'post_delete_archive_file', $file, $at, $entry);
1903    }
1904    1;
1905}
1906
1907##
1908## archive_file_for takes an entry to determine the timestamps,
1909## but if the entry is not available it uses the time_start
1910## and time_end values
1911##
1912sub archive_file_for {
1913    my $mt = shift;
1914    init_archive_types() unless %ArchiveTypes;
1915
1916    my ( $entry, $blog, $at, $cat, $map, $timestamp, $author ) = @_;
1917    return if $at eq 'None';
1918    my $archiver = $mt->archiver($at);
1919    return '' unless $archiver;
1920
1921    my $file;
1922    if ( $blog->is_dynamic ) {
1923        require MT::TemplateMap;
1924        $map = MT::TemplateMap->new;
1925        $map->file_template( $archiver->dynamic_template );
1926    }
1927    unless ($map) {
1928        my $cache = MT::Request->instance->cache('maps');
1929        unless ($cache) {
1930            MT::Request->instance->cache( 'maps', $cache = {} );
1931        }
1932        unless ( $map = $cache->{ $blog->id . $at } ) {
1933            require MT::TemplateMap;
1934            $map = MT::TemplateMap->load(
1935                {
1936                    blog_id      => $blog->id,
1937                    archive_type => $at,
1938                    is_preferred => 1
1939                }
1940            );
1941            $cache->{ $blog->id . $at } = $map if $map;
1942        }
1943    }
1944    my $file_tmpl = $map->file_template if $map;
1945    unless ($file_tmpl) {
1946        if ( my $tmpls = $archiver->default_archive_templates ) {
1947            my ($default) = grep { $_->{default} } @$tmpls;
1948            $file_tmpl = $default->{template} if $default;
1949        }
1950    }
1951    $file_tmpl ||= '';
1952    my ($ctx);
1953    if ( $file_tmpl =~ m/\%[_-]?[A-Za-z]/ ) {
1954        if ( $file_tmpl =~ m/<\$?MT/i ) {
1955            $file_tmpl =~
1956s!(<\$?MT[^>]+?>)|(%[_-]?[A-Za-z])!$1 ? $1 : '<MTFileTemplate format="'. $2 . '">'!gie;
1957        }
1958        else {
1959            $file_tmpl = qq{<MTFileTemplate format="$file_tmpl">};
1960        }
1961    }
1962    if ($file_tmpl) {
1963        require MT::Template::Context;
1964        $ctx = MT::Template::Context->new;
1965        $ctx->stash( 'blog', $blog );
1966    }
1967    local $ctx->{__stash}{category}         = $cat if $cat;
1968    local $ctx->{__stash}{archive_category} = $cat if $cat;
1969    $timestamp = $entry->authored_on() if $entry;
1970    local $ctx->{__stash}{entry} = $entry if $entry;
1971    local $ctx->{__stash}{author} =
1972      $author ? $author : $entry ? $entry->author : undef;
1973
1974    my %blog_at = map { $_ => 1 } split /,/, $blog->archive_type;
1975    return '' unless $blog_at{$at};
1976
1977    $file = $archiver->archive_file(
1978        $ctx,
1979        Timestamp => $timestamp,
1980        Template  => $file_tmpl
1981    );
1982    if ( $file_tmpl && !$file ) {
1983        local $ctx->{archive_type} = $at;
1984        require MT::Builder;
1985        my $build = MT::Builder->new;
1986        my $tokens = $build->compile( $ctx, $file_tmpl )
1987          or return $blog->error( $build->errstr() );
1988        defined( $file = $build->build( $ctx, $tokens ) )
1989          or return $blog->error( $build->errstr() );
1990    }
1991    else {
1992        my $ext = $blog->file_extension;
1993        $file .= '.' . $ext if $ext;
1994    }
1995    $file;
1996}
1997
1998# Adds an element to the rebuild queue when the plugin is enabled.
1999sub queue_build_file_filter {
2000    my $mt = shift;
2001    my ( $cb, %args ) = @_;
2002
2003    my $fi = $args{file_info};
2004    return 1 if $fi->{from_queue};
2005
2006    require MT::PublishOption;
2007    my $throttle = MT::PublishOption::get_throttle($fi);
2008
2009    # Prevent building of disabled templates if they get this far
2010    return 0 if $throttle->{type} == MT::PublishOption::DISABLED();
2011
2012    # Check for 'force' flag for 'manual' publish option, which
2013    # forces the template to build; used for 'rebuild' list actions
2014    # and publish site operations
2015    if ($throttle->{type} == MT::PublishOption::MANUALLY()) {
2016        return $args{force} ? 1 : 0;
2017    }
2018
2019    # From here on, we're committed to publishing this file via TheSchwartz
2020    return 1 if $throttle->{type} != MT::PublishOption::ASYNC();
2021
2022    return 1 if $args{force}; # if async, but force is used, publish
2023
2024    require MT::TheSchwartz;
2025    require TheSchwartz::Job;
2026    my $job = TheSchwartz::Job->new();
2027    $job->funcname('MT::Worker::Publish');
2028    $job->uniqkey( $fi->id );
2029
2030    my $priority = 0;
2031
2032    my $at = $fi->archive_type || '';
2033    # Default priority assignment....
2034    if (($at eq 'Individual') || ($at eq 'Page')) {
2035        require MT::TemplateMap;
2036        my $map = MT::TemplateMap->load($fi->templatemap_id);
2037        # Individual/Page archive pages that are the 'permalink' pages
2038        # should have highest build priority.
2039        if ($map && $map->is_preferred) {
2040            $priority = 10;
2041        } else {
2042            $priority = 5;
2043        }
2044    } elsif ($at eq 'index') {
2045        # Index pages are second in priority, if they are named 'index'
2046        # or 'default'
2047        if ($fi->file_path =~ m!/(index|default|atom|feed)!i) {
2048            $priority = 9;
2049        } else {
2050            $priority = 8;
2051        }
2052    } elsif ($at =~ m/Category|Author/) {
2053        $priority = 1;
2054    } elsif ($at =~ m/Yearly/) {
2055        $priority = 1;
2056    } elsif ($at =~ m/Monthly/) {
2057        $priority = 2;
2058    } elsif ($at =~ m/Weekly/) {
2059        $priority = 3;
2060    } elsif ($at =~ m/Daily/) {
2061        $priority = 4;
2062    }
2063
2064    $job->priority( $priority );
2065    $job->coalesce( ( $fi->blog_id || 0 ) . ':' . $$ . ':' . $priority . ':' . ( time - ( time % 10 ) ) );
2066
2067    MT::TheSchwartz->insert($job);
2068
2069    return 0;
2070}
2071
20721;
2073
2074__END__
2075
2076=head1 NAME
2077
2078MT::WeblogPublisher - Express weblog templates and content into a specific URL structure
2079
2080=head1 SYNOPSIS
2081
2082    use MT::WeblogPublisher;
2083    my $pub = MT::WeblogPublisher->new;
2084    $pub->rebuild(Blog => $blog, ArchiveType => "Individual");
2085
2086=head1 METHODS
2087
2088=head2 MT::WeblogPublisher->new()
2089
2090Return a new C<MT::WeblogPublisher>. Additionally, call
2091L<MT::ConfigMgr/instance> and set the I<NoTempFiles> and
2092I<PublishCommenterIcon> attributes, if not already set.
2093
2094=head2 $mt->rebuild( %args )
2095
2096Rebuilds your entire blog, indexes and archives; or some subset of your blog,
2097as specified in the arguments.
2098
2099I<%args> can contain:
2100
2101=over 4
2102
2103=item * Blog
2104
2105An I<MT::Blog> object corresponding to the blog that you would like to
2106rebuild.
2107
2108Either this or C<BlogID> is required.
2109
2110=item * BlogID
2111
2112The ID of the blog that you would like to rebuild.
2113
2114Either this or C<Blog> is required.
2115
2116=item * ArchiveType
2117
2118The archive type that you would like to rebuild. This should be one of
2119the following string values: C<Individual>, C<Daily>, C<Weekly>,
2120C<Monthly>, or C<Category>.
2121
2122This argument is optional; if not provided, all archive types will be rebuilt.
2123
2124=item * EntryCallback
2125
2126A callback that will be called for each entry that is rebuilt. If provided,
2127the value should be a subroutine reference; the subroutine will be handed
2128the I<MT::Entry> object for the entry that is about to be rebuilt. You could
2129use this to keep a running log of which entry is being rebuilt, for example:
2130
2131    $mt->rebuild(
2132              BlogID => $blog_id,
2133              EntryCallback => sub { print $_[0]->title, "\n" },
2134          );
2135
2136Or to provide a status indicator:
2137
2138    use MT::Entry;
2139    my $total = MT::Entry->count({ blog_id => $blog_id });
2140    my $i = 0;
2141    local $| = 1;
2142    $mt->rebuild(
2143              BlogID => $blog_id,
2144              EntryCallback => sub { printf "%d/%d\r", ++$i, $total },
2145          );
2146    print "\n";
2147
2148This argument is optional; by default no callbacks are executed.
2149
2150=item * NoIndexes
2151
2152By default I<rebuild> will rebuild the index templates after rebuilding all
2153of the entries; if you do not want to rebuild the index templates, set the
2154value for this argument to a true value.
2155
2156This argument is optional.
2157
2158=item * Limit
2159
2160Limit the number of entries to be rebuilt to the last C<N> entries in the
2161blog. For example, if you set this to C<20> and do not provide an offset (see
2162L<Offset>, below), the 20 most recent entries in the blog will be rebuilt.
2163
2164This is only useful if you are rebuilding C<Individual> archives.
2165
2166This argument is optional; by default all entries will be rebuilt.
2167
2168=item * Offset
2169
2170When used with C<Limit>, specifies the entry at which to start rebuilding
2171your individual entry archives. For example, if you set this to C<10>, and
2172set a C<Limit> of C<5> (see L<Limit>, above), entries 10-14 (inclusive) will
2173be rebuilt. The offset starts at C<0>, and the ordering is reverse
2174chronological.
2175
2176This is only useful if you are rebuilding C<Individual> archives, and if you
2177are using C<Limit>.
2178
2179This argument is optional; by default all entries will be rebuilt, starting
2180at the first entry.
2181
2182=back
2183
2184=head2 $mt->rebuild_entry( %args )
2185
2186Rebuilds a particular entry in your blog (and its dependencies, if specified).
2187
2188I<%args> can contain:
2189
2190=over 4
2191
2192=item * Entry
2193
2194An I<MT::Entry> object corresponding to the object you would like to rebuild.
2195
2196This argument is required.
2197
2198=item * Blog
2199
2200An I<MT::Blog> object corresponding to the blog to which the I<Entry> belongs.
2201
2202This argument is optional; if not provided, the I<MT::Blog> object will be
2203loaded in I<rebuild_entry> from the I<$entry-E<gt>blog_id> column of the
2204I<MT::Entry> object passed in. If you already have the I<MT::Blog> object
2205loaded, however, it makes sense to pass it in yourself, as it will skip one
2206small step in I<rebuild_entry> (loading the object).
2207
2208=item * BuildDependencies
2209
2210Saving an entry can have effects on other entries; so after saving, it is
2211often necessary to rebuild other entries, to reflect the changes onto all
2212of the affected archive pages, indexes, etc.
2213
2214If you supply this parameter with a true value, I<rebuild_indexes> will
2215rebuild: the archives for the next and previous entries, chronologically;
2216all of the index templates; the archives for the next and previous daily,
2217weekly, and monthly archives.
2218
2219=item * Previous, Next, OldPrevious, OldNext
2220
2221These values identify entries which may need to be updated now that
2222"this" entry has changed. When the authored_on field of an entry is
2223changed, its new neighbors (Previous and Next) need to be rebuilt as
2224well as its former neighbors (OldPrevious and OldNext).
2225
2226=item * NoStatic
2227
2228When this value is true, it acts as a hint to the rebuilding routine
2229that static output files need not be rebuilt; the "rebuild" operation
2230is just to update the bookkeeping that supports dynamic rebuilds.
2231
2232=back
2233
2234=head2 $mt->rebuild_file($blog, $archive_root, $map, $archive_type, $ctx, \%cond, $build_static, %args)
2235
2236Method responsible for building a single archive page from a template and
2237writing it to the file management layer.
2238
2239I<$blog> refers to the target weblog. I<$archive_root> is the target archive
2240path to publish the file. I<$map> is a L<MT::TemplateMap> object that
2241relates to publishing the file. I<$archive_type> is one of "Daily",
2242"Weekly", "Monthly", "Category" or "Individual". I<$ctx> is a handle to
2243the L<MT::Template::Context> object to use to build the file. I<\%cond>
2244is a hashref to conditional arguments used to drive the build process.
2245I<$build_static> is a boolean flag that controls whether static files are
2246created (otherwise, the records necessary for serving dynamic pages are
2247created and that is all).
2248
2249I<%args> is a hash that uniquely identifies the specific instance
2250of the given archive type. That is, for a category archive page it
2251identifies the category; for a date-based archive page it identifies
2252which time period is covered by the page; for an individual archive it
2253identifies the entry. I<%args> should contain just one of these
2254keys:
2255
2256=over 4
2257
2258=item * Category
2259
2260A category ID or L<MT::Category> instance of the category archive page to
2261be built.
2262
2263=item * Entry
2264
2265An entry ID or L<MT::Entry> instance of the entry archive page to be
2266built.
2267
2268=item * StartDate
2269
2270The starting timestamp of the date-based archive to be built.
2271
2272=back
2273
2274=head2 $mt->rebuild_indexes( %args )
2275
2276Rebuilds all of the index templates in your blog, or just one, if you use
2277the I<Template> argument (below). Only rebuilds templates that are set to
2278be rebuilt automatically, unless you use the I<Force> (below).
2279
2280I<%args> can contain:
2281
2282=over 4
2283
2284=item * Blog
2285
2286An I<MT::Blog> object corresponding to the blog whose indexes you would like
2287to rebuild.
2288
2289Either this or C<BlogID> is required.
2290
2291=item * BlogID
2292
2293The ID of the blog whose indexes you would like to rebuild.
2294
2295Either this or C<Blog> is required.
2296
2297=item * Template
2298
2299An I<MT::Template> object specifying the index template to rebuild; if you use
2300this argument, I<only> this index template will be rebuilt.
2301
2302Note that if the template that you specify here is set to not rebuild
2303automatically, you I<must> specify the I<Force> argument in order to force it
2304to be rebuilt.
2305
2306=item * Force
2307
2308A boolean flag specifying whether or not to rebuild index templates who have
2309been marked not to be rebuilt automatically.
2310
2311The default is C<0> (do not rebuild such templates).
2312
2313=back
2314
2315=head2 $mt->trans_error
2316
2317Call L<MT/translate>.
2318
2319=head2 $mt->remove_entry_archive_file(%param)
2320
2321Delete the archive files for an entry based on the following
2322parameters.  One of a I<Blog>, I<Entry> or I<Category> are required.
2323
2324=over 4
2325
2326=item * Blog
2327=item * Entry
2328=item * Category
2329=item * ArchiveType (Default 'Individual')
2330=item * TemplateID
2331
2332=back
2333
2334=head2 $mt->rebuild_categories(%param)
2335
2336Rebuild category archives based on the following parameters:
2337
2338=over 4
2339
2340=item * Blog
2341=item * BlogID
2342=item * Limit
2343=item * Offset
2344=item * Limit
2345=item * NoStatic
2346
2347=back
2348
2349=head2 $mt->publish_future_posts
2350
2351Build and publish all scheduled entries with a I<authored_on> timestamp
2352that is less than the current time.
2353
2354=head1 CALLBACKS
2355
2356=over 4
2357
2358=item BuildFileFilter
2359
2360This filter is called when Movable Type wants to rebuild a file, but
2361before doing so. This gives plugins the chance to determine whether a
2362file should actually be rebuild in particular situations.
2363
2364A BuildFileFilter callback routine is called as follows:
2365
2366    sub build_file_filter($eh, %args)
2367    {
2368        ...
2369        return $boolean;
2370    }
2371
2372As with other callback funcions, the first parameter is an
2373C<MT::ErrorHandler> object. This can be used by the callback to
2374propagate an error message to the surrounding context.
2375
2376The C<%args> parameters identify the page to be built. See
2377L<MT::FileInfo> for more information on how a page is determined by
2378these parameters. Elements in C<%args> are as follows:
2379
2380=over 4
2381
2382=item C<Context>
2383
2384Holds the template context that has been constructed for building (see
2385C<MT::Template::Context>).
2386
2387=item C<ArchiveType>
2388
2389The archive type of the file, usually one of C<'Index'>,
2390C<'Individual'>, C<'Category'>, C<'Daily'>, C<'Monthly'>, or
2391C<'Weekly'>.
2392
2393=item C<Templatemap>
2394
2395An C<MT::TemplateMap> object; this singles out which template is being
2396built, and the filesystem path of the file to be written.
2397
2398=item C<Blog>
2399
2400The C<MT::Blog> object representing the blog whose pages are being
2401rebuilt.
2402
2403=item C<Entry>
2404
2405In the case of an individual archive page, this points to the
2406C<MT::Entry> object whose page is being rebuilt. In the case of an
2407archive page other than an individual page, this parameter is not
2408necessarily undefined. It is best to rely on the C<$at> parameter to
2409determine whether a single entry is on deck to be built.
2410
2411=item C<PeriodStart>
2412
2413In the case of a date-based archive page, this is a timestamp at the
2414beginning of the period from which entries will be included on this
2415page, in Movable Type's standard 14-digit "timestamp" format. For
2416example, if the page is a Daily archive for April 17, 1796, this value
2417would be 17960417000000. If the page were a Monthly archive for March,
24182003, C<$start> would be 20030301000000. Again, this parameter may be
2419defined even when the page on deck is not a date-based archive page.
2420
2421=item C<Category>
2422
2423In the case of a Category archive, this parameter identifies the
2424category which will be built on the page.
2425
2426=item C<FileInfo>
2427
2428If defined, an L<MT::FileInfo> object which contains information about the
2429file. See L<MT::FileInfo> for more information about what a C<MT::FileInfo>
2430contains. Chief amongst all the members of C<MT::FileInfo>, for these
2431purposes, will be the C<virtual> member. This is a boolean value which will be
2432false if a page was actually created on disk for this "page," and false if no
2433page was created (because the corresponding template is set to be
2434built dynamically).
2435
2436It is possible for the FileInfo parameter to be undefined, namely if the blog has not been configured to publish anything dynamically, or if the
2437installation is using a data driver that does not support dynamic publishing.
2438
2439=back
2440
2441=item BuildPage
2442
2443BuildPage callbacks are invoked just after a page has been built, but
2444before the content has been written to the file system.
2445
2446    sub build_page($eh, %args)
2447    {
2448    }
2449
2450The parameters given are include those sent to the BuildFileFilter callback.
2451In addition, the following parameters are also given:
2452
2453=over 4
2454
2455=item C<Content>
2456
2457This is a scalar reference to the content that will eventually be
2458published.
2459
2460=item C<BuildResult> / (or C<RawContent>, deprecated)
2461
2462This is a scalar reference to the content originally produced by building
2463the page. This value is provided mainly for reference; modifications to it
2464will be ignored.
2465
2466=back
2467
2468=item BuildFile
2469
2470BuildFile callbacks are invoked just after a file has been built.
2471
2472    sub build_file($eh, %args)
2473    {
2474    }
2475
2476Parameters in %args are as with BuildPage.
2477
2478=back
2479
2480=head1 AUTHOR & COPYRIGHT
2481
2482Please see L<MT/AUTHOR & COPYRIGHT>.
2483
2484=cut
Note: See TracBrowser for help on using the browser.