root/branches/release-33/lib/MT/WeblogPublisher.pm @ 1779

Revision 1779, 73.1 kB (checked in by bchoate, 20 months ago)

Avoid rebuilding files within the same build operation (particularly helps for date-based archive rebuilds). BugId:69032

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