root/branches/release-35/lib/MT/WeblogPublisher.pm @ 1977

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

Fixed test for using publish queue for a given template/templatemap. BugId:79384

  • 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    return unless $entry;
377    my $blog;
378    unless ( $blog = $param{Blog} ) {
379        my $blog_id = $entry->blog_id;
380        $blog = MT::Blog->load($blog_id)
381          or return $mt->error(
382            MT->translate(
383                "Load of blog '[_1]' failed: [_2]", $blog_id,
384                MT::Blog->errstr
385            )
386          );
387    }
388    return 1 if $blog->is_dynamic;
389
390    my $at = $param{PreferredArchiveOnly} ? $blog->archive_type_preferred : $blog->archive_type;
391    if ( $at && $at ne 'None' ) {
392        my @at = split /,/, $at;
393        for my $at (@at) {
394            my $archiver = $mt->archiver($at);
395            next unless $archiver;    # invalid archive type
396            next if $entry->class ne $archiver->entry_class;
397            if ( $archiver->category_based ) {
398                my $cats = $entry->categories;    # (ancestors => 1)
399                for my $cat (@$cats) {
400                    $mt->_rebuild_entry_archive_type(
401                        Entry       => $entry,
402                        Blog        => $blog,
403                        ArchiveType => $at,
404                        Category    => $cat,
405                        NoStatic    => $param{NoStatic},
406                        $param{TemplateMap}
407                        ? ( TemplateMap => $param{TemplateMap} )
408                        : (),
409                    ) or return;
410                }
411            }
412            else {
413                $mt->_rebuild_entry_archive_type(
414                    Entry       => $entry,
415                    Blog        => $blog,
416                    ArchiveType => $at,
417                    $param{TemplateMap}
418                    ? ( TemplateMap => $param{TemplateMap} )
419                    : (),
420                    NoStatic => $param{NoStatic},
421                    Author   => $entry->author,
422                ) or return;
423            }
424        }
425    }
426
427    ## The above will just rebuild the archive pages for this particular
428    ## entry. If we want to rebuild all of the entries/archives/indexes
429    ## on which this entry could be featured etc., however, we need to
430    ## rebuild all of the entry's dependencies. Note that all of these
431    ## are not *necessarily* dependencies, depending on the usage of tags,
432    ## etc. There is not a good way to determine exact dependencies; it is
433    ## easier to just rebuild, rebuild, rebuild.
434
435    return 1
436      unless $param{BuildDependencies}
437      || $param{BuildIndexes}
438      || $param{BuildArchives};
439
440    if ( $param{BuildDependencies} ) {
441        ## Rebuild previous and next entry archive pages.
442        if ( my $prev = $entry->previous(1) ) {
443            $mt->rebuild_entry( Entry => $prev, PreferredArchiveOnly => 1 ) or return;
444            ## Rebuild the old previous and next entries, if we have some.
445            if ( $param{OldPrevious}
446                && ( $param{OldPrevious} != $prev->id )
447                && ( my $old_prev = MT::Entry->load( $param{OldPrevious} ) ) )
448            {
449                $mt->rebuild_entry( Entry => $old_prev, PreferredArchiveOnly => 1 ) or return;
450            }
451        }
452        if ( my $next = $entry->next(1) ) {
453            $mt->rebuild_entry( Entry => $next, PreferredArchiveOnly => 1 ) or return;
454
455            if ( $param{OldNext}
456                && ( $param{OldNext} != $next->id )
457                && ( my $old_next = MT::Entry->load( $param{OldNext} ) ) )
458            {
459                $mt->rebuild_entry( Entry => $old_next, PreferredArchiveOnly => 1 ) or return;
460            }
461        }
462    }
463
464    if ( $param{BuildDependencies} || $param{BuildIndexes} ) {
465        ## Rebuild all indexes, in case this entry is on an index.
466        if ( !( exists $param{BuildIndexes} ) || $param{BuildIndexes} ) {
467            $mt->rebuild_indexes( Blog => $blog ) or return;
468        }
469    }
470
471    if ( $param{BuildDependencies} || $param{BuildArchives} ) {
472        ## Rebuild previous and next daily, weekly, and monthly archives;
473        ## adding a new entry could cause changes to the intra-archive
474        ## navigation.
475        my %at = map { $_ => 1 } split /,/, $blog->archive_type;
476        my @db_at = grep { my $archiver = $mt->archiver($_); $archiver && $archiver->date_based } $mt->archive_types;
477        for my $at (@db_at) {
478            if ( $at{$at} ) {
479                my $archiver = $mt->archiver($at);
480                if ( $archiver->category_based ) {
481                    my $cats = $entry->categories;
482                    for my $cat (@$cats) {
483                        if ( my $prev_arch = $archiver->previous_archive_entry({
484                            entry    => $entry,
485                            category => $cat,
486                        }) ) {
487                            $mt->_rebuild_entry_archive_type(
488                                NoStatic => $param{NoStatic},
489                                Entry    => $prev_arch,
490                                Blog     => $blog,
491                                Category => $cat,
492                                $param{TemplateMap}
493                                ? ( TemplateMap => $param{TemplateMap} )
494                                : (),
495                                ArchiveType => $at
496                            ) or return;
497                        }
498                        if ( my $next_arch = $archiver->next_archive_entry({
499                            entry    => $entry,
500                            category => $cat,
501                        }) ) {
502                            $mt->_rebuild_entry_archive_type(
503                                NoStatic => $param{NoStatic},
504                                Entry    => $next_arch,
505                                Blog     => $blog,
506                                Category => $cat,
507                                $param{TemplateMap}
508                                ? ( TemplateMap => $param{TemplateMap} )
509                                : (),
510                                ArchiveType => $at
511                            ) or return;
512                        }
513                    }
514                } else {
515                    if ( my $prev_arch = $archiver->previous_archive_entry({
516                        entry => $entry,
517                        $archiver->author_based ? (author => $entry->author) : (),
518                    }) ) {
519                        $mt->_rebuild_entry_archive_type(
520                            NoStatic    => $param{NoStatic},
521                            Entry       => $prev_arch,
522                            Blog        => $blog,
523                            ArchiveType => $at,
524                            $param{TemplateMap}
525                            ? ( TemplateMap => $param{TemplateMap} )
526                            : (),
527                            $archiver->author_based ? (Author => $entry->author) : (),
528                        ) or return;
529                    }
530                    if ( my $next_arch = $archiver->next_archive_entry({
531                        entry => $entry,
532                        $archiver->author_based ? (author => $entry->author) : (),
533                    }) ) {
534                        $mt->_rebuild_entry_archive_type(
535                            NoStatic    => $param{NoStatic},
536                            Entry       => $next_arch,
537                            Blog        => $blog,
538                            ArchiveType => $at,
539                            $param{TemplateMap}
540                            ? ( TemplateMap => $param{TemplateMap} )
541                            : (),
542                            $archiver->author_based ? (Author => $entry->author) : (),
543                        ) or return;
544                    }
545                }
546            }
547        }
548    }
549
550    1;
551}
552
553### Recip hash
554### {ArchiveType - {Category-id} - {Date key} - {Start}
555###                                           - {End}
556###                                           - {File}
557###                              - {File}
558###              - {Author-id}   - {Date key} - {Start}
559###                                           - {End}
560###                                           - {File}
561###                              - {File}
562###              - {Date key}    - {Start}
563###                              - {End}
564###                              - {File}
565###
566sub rebuild_archives {
567    my $mt = shift;
568    my %param = @_;
569    my $blog = $param{Blog}
570      or return $mt->error(
571        MT->translate( "Parameter '[_1]' is required", 'Blog' ) );
572    return 1 if $blog->is_dynamic;
573
574    my $recip = $param{Recip}
575        or return $mt->error(
576            MT->translate( "Parameter '[_1]' is required", 'Recip' ) );
577
578    for my $at (keys %$recip){
579        my $archiver = $mt->archiver($at);
580        next unless $archiver;
581
582        if ($archiver->category_based()) {
583            require MT::Category;
584            for my $cat_id (keys %{$recip->{$at}}) {
585                my $cat = MT::Category->load($cat_id)
586                    or next;
587                if ($archiver->date_based()) {
588                    for my $key (keys %{$recip->{$at}->{$cat_id}}) {
589                        $mt->_rebuild_entry_archive_type(
590                            NoStatic    => 0,
591                            Blog        => $blog,
592                            Category    => $cat,
593                            ArchiveType => $at,
594                            Start       => $recip->{$at}->{$cat_id}->{$key}->{Start},
595                            End         => $recip->{$at}->{$cat_id}->{$key}->{End},
596                            File        => $recip->{$at}->{$cat_id}->{$key}->{File}
597                        ) or return;
598                    }
599                } else {
600                    $mt->_rebuild_entry_archive_type(
601                        NoStatic    => 0,
602                        Blog        => $blog,
603                        Category    => $cat,
604                        ArchiveType => $at,
605                        File        => $recip->{$at}->{$cat_id}->{File}
606                    ) or return;
607                }
608            }
609        } elsif ($archiver->author_based()) {
610            require MT::Author;
611            for my $auth_id (keys %{$recip->{$at}}) {
612                my $author = MT::Author->load($auth_id)
613                    or next;
614                if ($archiver->date_based()) {
615                    for my $key (keys %{$recip->{$at}->{$auth_id}}) {
616                        $mt->_rebuild_entry_archive_type(
617                            NoStatic    => 0,
618                            Blog        => $blog,
619                            Author      => $author,
620                            ArchiveType => $at,
621                            Start       => $recip->{$at}->{$auth_id}->{$key}->{Start},
622                            End         => $recip->{$at}->{$auth_id}->{$key}->{End},
623                            File        => $recip->{$at}->{$auth_id}->{$key}->{File}
624                        ) or return;
625                    }
626                } else {
627                    $mt->_rebuild_entry_archive_type(
628                        NoStatic    => 0,
629                        Blog        => $blog,
630                        Author      => $author,
631                        ArchiveType => $at,
632                        File        => $recip->{$at}->{$auth_id}->{File}
633                    ) or return;
634                }
635            }
636        } elsif ($archiver->date_based()) {
637            for my $key (keys %{$recip->{$at}}) {
638                $mt->_rebuild_entry_archive_type(
639                    NoStatic    => 0,
640                    Blog        => $blog,
641                    ArchiveType => $at,
642                    Start       => $recip->{$at}->{$key}->{Start},
643                    End         => $recip->{$at}->{$key}->{End},
644                    File        => $recip->{$at}->{$key}->{File}
645                ) or return;
646            }
647        } else {
648            require MT::Entry;
649            for my $entry_id (keys %{$recip->{$at}}) {
650                my $entry = MT::Entry->load($entry_id)
651                    or next;
652                $mt->_rebuild_entry_archive_type(
653                    NoStatic    => 0,
654                    Entry       => $entry,
655                    Blog        => $blog,
656                    ArchiveType => $at,
657                    File        => $recip->{$at}->{$entry_id}->{File}
658                ) or return;
659            }
660        }
661    }
662
663    1;
664}
665
666sub _rebuild_entry_archive_type {
667    my $mt    = shift;
668    my %param = @_;
669
670    my $at    = $param{ArchiveType}
671      or return $mt->error(
672        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
673    return 1 if $at eq 'None';
674    my $entry =
675      ( $param{ArchiveType} ne 'Category' && $param{ArchiveType} ne 'Author' &&
676        !exists $param{Start} && !exists $param{End} )
677      ? (
678        $param{Entry}
679          or return $mt->error(
680            MT->translate( "Parameter '[_1]' is required", 'Entry' )
681          )
682      )
683      : undef;
684
685    my $blog;
686    unless ( $blog = $param{Blog} ) {
687        my $blog_id = $entry->blog_id;
688        $blog = MT::Blog->load($blog_id)
689          or return $mt->error(
690            MT->translate(
691                "Load of blog '[_1]' failed: [_2]", $blog_id,
692                MT::Blog->errstr
693            )
694          );
695    }
696
697    ## Load the template-archive-type map entries for this blog and
698    ## archive type. We do this before we load the list of entries, because
699    ## we will run through the files and check if we even need to rebuild
700    ## anything. If there is nothing to rebuild at all for this entry,
701    ## we save some time by not loading the list of entries.
702    require MT::TemplateMap;
703    my @map;
704    if ( $param{TemplateMap} ) {
705        @map = ( $param{TemplateMap} );
706    }
707    else {
708        my $cached_maps = MT->instance->request('__cached_maps')
709          || MT->instance->request( '__cached_maps', {} );
710        if ( my $maps = $cached_maps->{ $at . $blog->id } ) {
711            @map = @$maps;
712        }
713        else {
714            @map = MT::TemplateMap->load(
715                {
716                    archive_type => $at,
717                    blog_id      => $blog->id,
718                    $param{TemplateID}
719                    ? ( template_id => $param{TemplateID} )
720                    : ()
721                }
722            );
723            $cached_maps->{ $at . $blog->id } = \@map;
724        }
725    }
726    return 1 unless @map;
727    my @map_build;
728
729    my $done = MT->instance->request('__published:'.$blog->id)
730      || MT->instance->request( '__published:'.$blog->id, {} );
731    for my $map (@map) {
732        my $file = exists $param{File}
733            ? $param{File}
734            : $mt->archive_file_for( $entry, $blog, $at, $param{Category}, $map,
735            undef, $param{Author} );
736        if ( $file eq '' ) {
737
738            # np
739        }
740        elsif ( !defined($file) ) {
741            return $mt->error( MT->translate( $blog->errstr() ) );
742        }
743        else {
744            push @map_build, $map unless $done->{$file};
745            $map->{__saved_output_file} = $file;
746        }
747    }
748    return 1 unless @map_build;
749    @map = @map_build;
750
751    my (%cond);
752    require MT::Template::Context;
753    my $ctx = MT::Template::Context->new;
754    $ctx->{current_archive_type} = $at;
755    $ctx->{archive_type}         = $at;
756    $at ||= "";
757
758    my $archiver = $mt->archiver($at);
759    return unless $archiver;
760
761    # Special handling for pages-- they are always published to the
762    # 'site' path instead of the 'archive' path, which is reserved for blog
763    # content.
764    my $arch_root = ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
765    return $mt->error(
766        MT->translate("You did not set your blog publishing path") )
767      unless $arch_root;
768
769    my ($start, $end);
770    if (exists $param{Start} && exists $param{End}) {
771        $start = $param{Start};
772        $end   = $param{End};
773    } else {
774        if ($archiver->can('date_range')) {
775            ( $start, $end ) = $archiver->date_range( $entry->authored_on );
776        }
777    }
778
779    ## For each mapping, we need to rebuild the entries we loaded above in
780    ## the particular template map, and write it to the specified archive
781    ## file template.
782    require MT::Template;
783    for my $map (@map) {
784        $mt->rebuild_file(
785            $blog, $arch_root, $map, $at, $ctx, \%cond,
786            !$param{NoStatic},
787            Category  => $param{Category},
788            Entry     => $entry,
789            Author    => $param{Author},
790            StartDate => $start,
791            EndDate   => $end,
792        ) or return;
793        $done->{ $map->{__saved_output_file} }++;
794    }
795    1;
796}
797
798sub rebuild_file {
799    my $mt = shift;
800    my ( $blog, $root_path, $map, $at, $ctx, $cond, $build_static, %specifier )
801      = @_;
802
803    my $finfo;
804    my $archiver = $mt->archiver($at);
805    my ( $entry, $start, $end, $category, $author );
806
807    if ( $finfo = $specifier{FileInfo} ) {
808        $specifier{Author}   = $finfo->author_id   if $finfo->author_id;
809        $specifier{Category} = $finfo->category_id if $finfo->category_id;
810        $specifier{Entry}    = $finfo->entry_id    if $finfo->entry_id;
811        $map ||= MT::TemplateMap->load( $finfo->templatemap_id );
812        $at  ||= $finfo->archive_type;
813        if ( $finfo->startdate ) {
814            if ( my ( $start, $end ) = $archiver->date_range($finfo->startdate) ) {
815                $specifier{StartDate} = $start;
816                $specifier{EndDate}   = $end;
817            }
818        }
819    }
820
821    # Calculate file path and URL for the new entry.
822    my $file = File::Spec->catfile( $root_path, $map->{__saved_output_file} );
823
824    ## Untaint. We have to assume that we can trust the user's setting of
825    ## the archive_path, and nothing else is based on user input.
826    ($file) = $file =~ /(.+)/s;
827
828    # compare file modification time to start of build process. if it
829    # is greater than the start_time, then we shouldn't need to build this
830    # file again
831    my $fmgr = $blog->file_mgr;
832    if (my $mod_time = $fmgr->file_mod_time($file)) {
833        return 1 if $mod_time >= $mt->start_time;
834    }
835
836    if ( $archiver->category_based ) {
837        $category = $specifier{Category};
838        die "Category archive type requires Category parameter"
839          unless $specifier{Category};
840        $category = MT::Category->load($category)
841          unless ref $category;
842        $ctx->var( 'category_archive', 1 );
843        $ctx->{__stash}{archive_category} = $category;
844    }
845    if ( $archiver->entry_based ) {
846        $entry = $specifier{Entry};
847        die "$at archive type requires Entry parameter"
848          unless $entry;
849        require MT::Entry;
850        $entry = MT::Entry->load($entry) if !ref $entry;
851        $ctx->var( 'entry_archive', 1 );
852        $ctx->{__stash}{entry} = $entry;
853    }
854    if ( $archiver->date_based ) {
855        # Date-based archive type
856        $start = $specifier{StartDate};
857        $end   = $specifier{EndDate};
858        Carp::confess("Date-based archive types require StartDate parameter")
859          unless $specifier{StartDate};
860        $ctx->var( 'datebased_archive', 1 );
861    }
862    if ( $archiver->author_based ) {
863
864        # author based archive type
865        $author = $specifier{Author};
866        die "Author-based archive type requires Author parameter"
867          unless $specifier{Author};
868        require MT::Author;
869        $author = MT::Author->load($author)
870          unless ref $author;
871        $ctx->var( 'author_archive', 1 );
872        $ctx->{__stash}{author} = $author;
873    }
874    local $ctx->{current_timestamp}     = $start if $start;
875    local $ctx->{current_timestamp_end} = $end   if $end;
876
877    $ctx->{__stash}{blog} = $blog;
878
879    require MT::FileInfo;
880
881# This kind of testing should be done at the time we save a post,
882# not during publishing!!!
883# if ($archiver->entry_based) {
884#     my $fcount = MT::FileInfo->count({
885#         blog_id => $blog->id,
886#         entry_id => $entry->id,
887#         file_path => $file},
888#         { not => { entry_id => 1 } });
889#     die MT->translate('The same archive file exists. You should change the basename or the archive path. ([_1])', $file) if $fcount > 0;
890# }
891
892    my $url = $blog->archive_url;
893    $url = $blog->site_url
894      if $archiver->entry_based && $archiver->entry_class eq 'page';
895    $url .= '/' unless $url =~ m|/$|;
896    $url .= $map->{__saved_output_file};
897
898    my $cached_tmpl = MT->instance->request('__cached_templates')
899      || MT->instance->request( '__cached_templates', {} );
900    my $tmpl_id = $map->template_id;
901
902    # template specific for this entry (or page, as the case may be)
903    if ( $entry && $entry->template_id ) {
904
905        # allow entry to override *if* we're publishing an individual
906        # page, and this is the 'preferred' one...
907        if ( $archiver->entry_based ) {
908            if ( $map->is_preferred ) {
909                $tmpl_id = $entry->template_id;
910            }
911        }
912    }
913
914    my $tmpl = $cached_tmpl->{$tmpl_id};
915    unless ($tmpl) {
916        $tmpl = MT::Template->load($tmpl_id);
917        if ($cached_tmpl) {
918            $cached_tmpl->{$tmpl_id} = $tmpl;
919        }
920    }
921
922    $tmpl->context($ctx);
923
924    # From Here
925    if ( my $tmpl_param = $archiver->template_params ) {
926        $tmpl->param($tmpl_param);
927    }
928
929    my ($rel_url) = ( $url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)| );
930    $rel_url =~ s|//+|/|g;
931
932    # Clear out all the FileInfo records that might point at the page
933    # we're about to create
934    # FYI: if it's an individual entry, we don't use the date as a
935    #      criterion, since this could actually have changed since
936    #      the FileInfo was last built. When the date does change,
937    #      the old date-based archive doesn't necessarily get fixed,
938    #      but if another comes along it will get corrected
939    unless ($finfo) {
940        my %terms;
941        $terms{blog_id}     = $blog->id;
942        $terms{category_id} = $category->id if $archiver->category_based;
943        $terms{author_id}   = $author->id if $archiver->author_based;
944        $terms{entry_id}    = $entry->id if $archiver->entry_based;
945        $terms{startdate}   = $start
946          if $archiver->date_based && ( !$archiver->entry_based );
947        $terms{archive_type}   = $at;
948        $terms{templatemap_id} = $map->id;
949        my @finfos = MT::FileInfo->load( \%terms );
950
951        if (   ( scalar @finfos == 1 )
952            && ( $finfos[0]->file_path eq $file )
953            && ( ( $finfos[0]->url || '' ) eq $rel_url )
954            && ( $finfos[0]->template_id == $tmpl_id ) )
955        {
956
957            # if the shoe fits, wear it
958            $finfo = $finfos[0];
959        }
960        else {
961
962           # if the shoe don't fit, remove all shoes and create the perfect shoe
963            foreach (@finfos) { $_->remove(); }
964
965            $finfo = MT::FileInfo->set_info_for_url(
966                $rel_url, $file, $at,
967                {
968                    Blog        => $blog->id,
969                    TemplateMap => $map->id,
970                    Template    => $tmpl_id,
971                    ( $archiver->entry_based && $entry )
972                    ? ( Entry => $entry->id )
973                    : (),
974                    StartDate => $start,
975                    ( $archiver->category_based && $category )
976                    ? ( Category => $category->id )
977                    : (),
978                    ( $archiver->author_based )
979                    ? ( Author => $author->id )
980                    : (),
981                }
982              )
983              || die "Couldn't create FileInfo because "
984              . MT::FileInfo->errstr();
985        }
986    }
987
988    # If you rebuild when you've just switched to dynamic pages,
989    # we move the file that might be there so that the custom
990    # 404 will be triggered.
991    if ( $tmpl->build_dynamic ) {
992        rename(
993            $finfo->file_path,    # is this just $file ?
994            $finfo->file_path . '.static'
995        );
996
997        ## If the FileInfo is set to static, flip it to virtual.
998        if ( !$finfo->virtual ) {
999            $finfo->virtual(1);
1000            $finfo->save();
1001        }
1002    }
1003
1004    return 1 if ( $tmpl->build_dynamic );
1005    return 1 if ( $entry && $entry->status != MT::Entry::RELEASE() );
1006
1007    my $timer = MT->get_timer;
1008    if ($timer) {
1009        $timer->pause_partial;
1010    }
1011    local $timer->{elapsed} = 0 if $timer;
1012
1013    if (
1014        $build_static
1015        && MT->run_callbacks(
1016            'build_file_filter',
1017            Context      => $ctx,
1018            context      => $ctx,
1019            ArchiveType  => $at,
1020            archive_type => $at,
1021            TemplateMap  => $map,
1022            template_map => $map,
1023            Blog         => $blog,
1024            blog         => $blog,
1025            Entry        => $entry,
1026            entry        => $entry,
1027            FileInfo     => $finfo,
1028            file_info    => $finfo,
1029            File         => $file,
1030            file         => $file,
1031            Template     => $tmpl,
1032            template     => $tmpl,
1033            PeriodStart  => $start,
1034            period_start => $start,
1035            Category     => $category,
1036            category     => $category,
1037        )
1038      )
1039    {
1040        if ( $archiver->group_based ) {
1041            require MT::Promise;
1042            my $entries = sub { $archiver->archive_group_entries($ctx) };
1043            $ctx->stash( 'entries', MT::Promise::delay($entries) );
1044        }
1045
1046        my $html = undef;
1047        $ctx->stash( 'blog', $blog );
1048        $ctx->stash( 'entry', $entry ) if $entry;
1049
1050        require MT::Request;
1051        MT::Request->instance->cache('build_template', $tmpl);
1052
1053        $html = $tmpl->build( $ctx, $cond );
1054        unless (defined($html)) {
1055            $timer->unpause if $timer;
1056            require MT::I18N;
1057            return $mt->error(
1058            (
1059                $category ? MT->translate(
1060                    "An error occurred publishing [_1] '[_2]': [_3]",
1061                    MT::I18N::lowercase( $category->class_label ),
1062                    $category->id,
1063                    $tmpl->errstr
1064                  )
1065                : $entry ? MT->translate(
1066                    "An error occurred publishing [_1] '[_2]': [_3]",
1067                    MT::I18N::lowercase( $entry->class_label ),
1068                    $entry->title,
1069                    $tmpl->errstr
1070                  )
1071                : MT->translate(
1072"An error occurred publishing date-based archive '[_1]': [_2]",
1073                    $at . $start,
1074                    $tmpl->errstr
1075                )
1076            )
1077          );
1078        }
1079        my $orig_html = $html;
1080        MT->run_callbacks(
1081            'build_page',
1082            Context      => $ctx,
1083            context      => $ctx,
1084            ArchiveType  => $at,
1085            archive_type => $at,
1086            TemplateMap  => $map,
1087            template_map => $map,
1088            Blog         => $blog,
1089            blog         => $blog,
1090            Entry        => $entry,
1091            entry        => $entry,
1092            FileInfo     => $finfo,
1093            file_info    => $finfo,
1094            PeriodStart  => $start,
1095            period_start => $start,
1096            Category     => $category,
1097            category     => $category,
1098            RawContent   => \$orig_html,
1099            raw_content  => \$orig_html,
1100            Content      => \$html,
1101            content      => \$html,
1102            BuildResult  => \$orig_html,
1103            build_result => \$orig_html,
1104            Template     => $tmpl,
1105            template     => $tmpl,
1106            File         => $file,
1107            file         => $file
1108        );
1109        ## First check whether the content is actually
1110        ## changed. If not, we won't update the published
1111        ## file, so as not to modify the mtime.
1112        unless ($fmgr->content_is_updated( $file, \$html )) {
1113            $timer->unpause if $timer;
1114            return 1;
1115        }
1116
1117        ## Determine if we need to build directory structure,
1118        ## and build it if we do. DirUmask determines
1119        ## directory permissions.
1120        require File::Spec;
1121        my $path = dirname($file);
1122        $path =~ s!/$!!
1123          unless $path eq '/'; ## OS X doesn't like / at the end in mkdir().
1124        unless ( $fmgr->exists($path) ) {
1125            if (!$fmgr->mkpath($path)) {
1126                $timer->unpause if $timer;
1127                return $mt->trans_error( "Error making path '[_1]': [_2]",
1128                    $path, $fmgr->errstr );
1129            }
1130        }
1131
1132        ## By default we write all data to temp files, then rename
1133        ## the temp files to the real files (an atomic
1134        ## operation). Some users don't like this (requires too
1135        ## liberal directory permissions). So we have a config
1136        ## option to turn it off (NoTempFiles).
1137        my $use_temp_files = !$mt->{NoTempFiles};
1138        my $temp_file = $use_temp_files ? "$file.new" : $file;
1139        unless ( defined $fmgr->put_data( $html, $temp_file ) ) {
1140            $timer->unpause if $timer;
1141            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1142                $temp_file, $fmgr->errstr );
1143        }
1144        if ($use_temp_files) {
1145            if (!$fmgr->rename( $temp_file, $file )) {
1146                $timer->unpause if $timer;
1147                return $mt->trans_error(
1148                    "Renaming tempfile '[_1]' failed: [_2]",
1149                    $temp_file, $fmgr->errstr );
1150            }
1151        }
1152        MT->run_callbacks(
1153            'build_file',
1154            Context      => $ctx,
1155            context      => $ctx,
1156            ArchiveType  => $at,
1157            archive_type => $at,
1158            TemplateMap  => $map,
1159            template_map => $map,
1160            FileInfo     => $finfo,
1161            file_info    => $finfo,
1162            Blog         => $blog,
1163            blog         => $blog,
1164            Entry        => $entry,
1165            entry        => $entry,
1166            PeriodStart  => $start,
1167            period_start => $start,
1168            RawContent   => \$orig_html,
1169            raw_content  => \$orig_html,
1170            Content      => \$html,
1171            content      => \$html,
1172            BuildResult  => \$orig_html,
1173            build_result => \$orig_html,
1174            Template     => $tmpl,
1175            template     => $tmpl,
1176            Category     => $category,
1177            category     => $category,
1178            File         => $file,
1179            file         => $file
1180        );
1181    }
1182    $timer->mark("total:rebuild_file[template_id:" . $tmpl->id . "]")
1183        if $timer;
1184    1;
1185}
1186
1187sub rebuild_indexes {
1188    my $mt    = shift;
1189    my %param = @_;
1190    require MT::Template;
1191    require MT::Template::Context;
1192    require MT::Entry;
1193    my $blog;
1194    unless ( $blog = $param{Blog} ) {
1195        my $blog_id = $param{BlogID};
1196        $blog = MT::Blog->load($blog_id)
1197          or return $mt->error(
1198            MT->translate(
1199                "Load of blog '[_1]' failed: [_2]", $blog_id,
1200                MT::Blog->errstr
1201            )
1202          );
1203    }
1204    my $tmpl = $param{Template};
1205    unless ($blog) {
1206        $blog = MT::Blog->load( $tmpl->blog_id );
1207    }
1208    return 1 if $blog->is_dynamic;
1209    my $iter;
1210    if ($tmpl) {
1211        my $i = 0;
1212        $iter = sub { $i++ < 1 ? $tmpl : undef };
1213    }
1214    else {
1215        $iter = MT::Template->load_iter(
1216            {
1217                type    => 'index',
1218                blog_id => $blog->id
1219            }
1220        );
1221    }
1222    local *FH;
1223    my $site_root = $blog->site_path;
1224    return $mt->error(
1225        MT->translate("You did not set your blog publishing path") )
1226      unless $site_root;
1227    my $fmgr = $blog->file_mgr;
1228    while ( my $tmpl = $iter->() ) {
1229        ## Skip index templates that the user has designated not to be
1230        ## rebuilt automatically. We need to do the defined-ness check
1231        ## because we added the flag in 2.01, and for templates saved
1232        ## before that time, the rebuild_me flag will be undefined. But
1233        ## we assume that these templates should be rebuilt, since that
1234        ## was the previous behavior.
1235        ## Note that dynamic templates do need to be "rebuilt"--the
1236        ## FileInfo table needs to be maintained.
1237        if ( !$tmpl->build_dynamic && !$param{Force} ) {
1238            next if ( defined $tmpl->rebuild_me && !$tmpl->rebuild_me );
1239        }
1240        my $file = $tmpl->outfile;
1241        $file = '' unless defined $file;
1242        if ( $tmpl->build_dynamic && ( $file eq '' ) ) {
1243            next;
1244        }
1245        return $mt->error(
1246            MT->translate(
1247                "Template '[_1]' does not have an Output File.",
1248                $tmpl->name
1249            )
1250        ) unless $file ne '';
1251        my $url = join( '/', $blog->site_url, $file );
1252        unless ( File::Spec->file_name_is_absolute($file) ) {
1253            $file = File::Spec->catfile( $site_root, $file );
1254        }
1255
1256        # Everything from here out is identical with rebuild_file
1257        my ($rel_url) = ( $url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)| );
1258        $rel_url =~ s|//+|/|g;
1259        ## Untaint. We have to assume that we can trust the user's setting of
1260        ## the site_path and the template outfile.
1261        ($file) = $file =~ /(.+)/s;
1262        my $finfo;
1263        require MT::FileInfo;
1264        my @finfos = MT::FileInfo->load(
1265            {
1266                blog_id     => $tmpl->blog_id,
1267                template_id => $tmpl->id
1268            }
1269        );
1270        if (   ( scalar @finfos == 1 )
1271            && ( $finfos[0]->file_path eq $file )
1272            && ( ( $finfos[0]->url || '' ) eq $rel_url ) )
1273        {
1274            $finfo = $finfos[0];
1275        }
1276        else {
1277            foreach (@finfos) { $_->remove(); }
1278            $finfo = MT::FileInfo->set_info_for_url(
1279                $rel_url, $file, 'index',
1280                {
1281                    Blog     => $tmpl->blog_id,
1282                    Template => $tmpl->id,
1283                }
1284              )
1285              || die "Couldn't create FileInfo because " . MT::FileInfo->errstr;
1286        }
1287        if ( $tmpl->build_dynamic ) {
1288            rename( $file, $file . ".static" );
1289
1290            ## If the FileInfo is set to static, flip it to virtual.
1291            if ( !$finfo->virtual ) {
1292                $finfo->virtual(1);
1293                $finfo->save();
1294            }
1295        }
1296
1297        next if ( $tmpl->build_dynamic );
1298
1299        ## We're not building dynamically, so if the FileInfo is currently
1300        ## set as dynamic (virtual), change it to static.
1301        if ( $finfo && $finfo->virtual ) {
1302            $finfo->virtual(0);
1303            $finfo->save();
1304        }
1305
1306        my $timer = MT->get_timer;
1307        if ($timer) {
1308            $timer->pause_partial;
1309        }
1310        local $timer->{elapsed} = 0 if $timer;
1311
1312        my $ctx = MT::Template::Context->new;
1313        next
1314          unless (
1315            MT->run_callbacks(
1316                'build_file_filter',
1317                Context      => $ctx,
1318                context      => $ctx,
1319                ArchiveType  => 'index',
1320                archive_type => 'index',
1321                Blog         => $blog,
1322                blog         => $blog,
1323                FileInfo     => $finfo,
1324                file_info    => $finfo,
1325                Template     => $tmpl,
1326                template     => $tmpl,
1327                File         => $file,
1328                file         => $file
1329            )
1330          );
1331        $ctx->stash( 'blog', $blog );
1332
1333        require MT::Request;
1334        MT::Request->instance->cache('build_template', $tmpl);
1335
1336        my $html = $tmpl->build($ctx);
1337        unless (defined $html) {
1338            $timer->unpause if $timer;
1339            return $mt->error( $tmpl->errstr );
1340        }
1341
1342        my $orig_html = $html;
1343        MT->run_callbacks(
1344            'build_page',
1345            Context      => $ctx,
1346            context      => $ctx,
1347            Blog         => $blog,
1348            blog         => $blog,
1349            FileInfo     => $finfo,
1350            file_info    => $finfo,
1351            ArchiveType  => 'index',
1352            archive_type => 'index',
1353            RawContent   => \$orig_html,
1354            raw_content  => \$orig_html,
1355            Content      => \$html,
1356            content      => \$html,
1357            BuildResult  => \$orig_html,
1358            build_result => \$orig_html,
1359            Template     => $tmpl,
1360            template     => $tmpl,
1361            File         => $file,
1362            file         => $file
1363        );
1364
1365        ## First check whether the content is actually changed. If not,
1366        ## we won't update the published file, so as not to modify the mtime.
1367        next unless $fmgr->content_is_updated( $file, \$html );
1368
1369        ## Determine if we need to build directory structure,
1370        ## and build it if we do. DirUmask determines
1371        ## directory permissions.
1372        require File::Spec;
1373        my $path = dirname($file);
1374        $path =~ s!/$!!
1375          unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
1376        unless ( $fmgr->exists($path) ) {
1377            if (! $fmgr->mkpath($path) ) {
1378                $timer->unpause if $timer;
1379                return $mt->trans_error( "Error making path '[_1]': [_2]",
1380                    $path, $fmgr->errstr );
1381            }
1382        }
1383
1384        ## Update the published file.
1385        my $use_temp_files = !$mt->{NoTempFiles};
1386        my $temp_file = $use_temp_files ? "$file.new" : $file;
1387        unless (defined( $fmgr->put_data( $html, $temp_file ) )) {
1388            $timer->unpause if $timer;
1389            return $mt->trans_error( "Writing to '[_1]' failed: [_2]",
1390                $temp_file, $fmgr->errstr );
1391        }
1392        if ($use_temp_files) {
1393            if (!$fmgr->rename( $temp_file, $file )) {
1394                $timer->unpause if $timer;
1395                return $mt->trans_error( "Renaming tempfile '[_1]' failed: [_2]",
1396                    $temp_file, $fmgr->errstr );
1397            }
1398        }
1399        MT->run_callbacks(
1400            'build_file',
1401            Context      => $ctx,
1402            context      => $ctx,
1403            ArchiveType  => 'index',
1404            archive_type => 'index',
1405            FileInfo     => $finfo,
1406            file_info    => $finfo,
1407            Blog         => $blog,
1408            blog         => $blog,
1409            RawContent   => \$orig_html,
1410            raw_content  => \$orig_html,
1411            Content      => \$html,
1412            content      => \$html,
1413            BuildResult  => \$orig_html,
1414            build_result => \$orig_html,
1415            Template     => $tmpl,
1416            template     => $tmpl,
1417            File         => $file,
1418            file         => $file
1419        );
1420
1421        $timer->mark("total:rebuild_indexes[template_id:" . $tmpl->id . ";file:$file]")
1422            if $timer;
1423    }
1424    1;
1425}
1426
1427sub rebuild_from_fileinfo {
1428    my $pub = shift;
1429    my ($fi) = @_;
1430
1431    require MT::Blog;
1432    require MT::Entry;
1433    require MT::Category;
1434    require MT::Template;
1435    require MT::TemplateMap;
1436    require MT::Template::Context;
1437
1438    my $at = $fi->archive_type
1439      or return $pub->error(
1440        MT->translate( "Parameter '[_1]' is required", 'ArchiveType' ) );
1441
1442    # callback for custom archive types
1443    return
1444      unless MT->run_callbacks(
1445        'build_archive_filter',
1446        archive_type => $at,
1447        file_info    => $fi
1448      );
1449
1450    if ( $at eq 'index' ) {
1451        $pub->rebuild_indexes(
1452            BlogID   => $fi->blog_id,
1453            Template => MT::Template->load( $fi->template_id ),
1454            Force    => 1,
1455        ) or return;
1456        return 1;
1457    }
1458
1459    return 1 if $at eq 'None';
1460
1461    my ( $start, $end );
1462    my $blog = MT::Blog->load( $fi->blog_id )
1463      if $fi->blog_id;
1464    my $entry = MT::Entry->load( $fi->entry_id )
1465      or return $pub->error(
1466        MT->translate( "Parameter '[_1]' is required", 'Entry' ) )
1467      if $fi->entry_id;
1468    if ( $fi->startdate ) {
1469        my $archiver = $pub->archiver($at);
1470
1471        if ( ( $start, $end ) = $archiver->date_range( $fi->startdate ) ) {
1472            $entry = MT::Entry->load( { authored_on => [ $start, $end ] },
1473                { range_incl => { authored_on => 1 }, limit => 1 } )
1474              or return $pub->error(
1475                MT->translate( "Parameter '[_1]' is required", 'Entry' ) );
1476        }
1477    }
1478    my $cat = MT::Category->load( $fi->category_id )
1479      if $fi->category_id;
1480    my $author = MT::Author->load( $fi->author_id )
1481      if $fi->author_id;
1482
1483    ## Load the template-archive-type map entries for this blog and
1484    ## archive type. We do this before we load the list of entries, because
1485    ## we will run through the files and check if we even need to rebuild
1486    ## anything. If there is nothing to rebuild at all for this entry,
1487    ## we save some time by not loading the list of entries.
1488    my $map = MT::TemplateMap->load( $fi->templatemap_id );
1489    my $file = $pub->archive_file_for( $entry, $blog, $at, $cat, $map,
1490        undef, $author );
1491    if ( !defined($file) ) {
1492        return $pub->error( $blog->errstr() );
1493    }
1494    $map->{__saved_output_file} = $file;
1495
1496    my $ctx = MT::Template::Context->new;
1497    $ctx->{current_archive_type} = $at;
1498    if ( $start && $end ) {
1499        $ctx->{current_timestamp} = $start;
1500        $ctx->{current_timestamp_end} = $end;
1501    }
1502
1503    my $arch_root =
1504      ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1505    return $pub->error(
1506        MT->translate("You did not set your blog publishing path") )
1507      unless $arch_root;
1508
1509    my %cond;
1510    $pub->rebuild_file( $blog, $arch_root, $map, $at, $ctx, \%cond, 1,
1511        FileInfo => $fi, )
1512      or return;
1513
1514    1;
1515}
1516
1517sub trans_error {
1518    my $this = shift;
1519    return $this->error( MT->translate(@_) );
1520}
1521
1522sub publish_future_posts {
1523    my $this = shift;
1524
1525    require MT::Blog;
1526    require MT::Entry;
1527    require MT::Util;
1528    my $mt            = MT->instance;
1529    my $total_changed = 0;
1530    my @blogs = MT::Blog->load(undef, {
1531        join => MT::Entry->join_on('blog_id', {
1532            status => MT::Entry::FUTURE(),
1533        }, { unique => 1 })
1534    });
1535    foreach my $blog (@blogs) {
1536        my @ts = MT::Util::offset_time_list( time, $blog );
1537        my $now = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1,
1538          @ts[ 3, 2, 1, 0 ];
1539        my $iter = MT::Entry->load_iter(
1540            {
1541                blog_id => $blog->id,
1542                status  => MT::Entry::FUTURE(),
1543                class   => '*'
1544            },
1545            {
1546                'sort'    => 'authored_on',
1547                direction => 'descend'
1548            }
1549        );
1550        my @queue;
1551        while ( my $entry = $iter->() ) {
1552            push @queue, $entry->id if $entry->authored_on le $now;
1553        }
1554
1555        my $changed = 0;
1556        my @results;
1557        my %rebuild_queue;
1558        my %ping_queue;
1559        foreach my $entry_id (@queue) {
1560            my $entry = MT::Entry->load($entry_id)
1561                or next;
1562            $entry->status( MT::Entry::RELEASE() );
1563            $entry->save
1564              or die $entry->errstr;
1565
1566            $rebuild_queue{ $entry->id } = $entry;
1567            $ping_queue{ $entry->id }    = 1;
1568            my $n = $entry->next(1);
1569            $rebuild_queue{ $n->id } = $n if $n;
1570            my $p = $entry->previous(1);
1571            $rebuild_queue{ $p->id } = $p if $p;
1572            $changed++;
1573            $total_changed++;
1574        }
1575        if ($changed) {
1576            my %rebuilt_okay;
1577            my $rebuilt;
1578            eval {
1579                foreach my $id ( keys %rebuild_queue )
1580                {
1581                    my $entry = $rebuild_queue{$id};
1582                    $mt->rebuild_entry( Entry => $entry, Blog => $blog )
1583                      or die $mt->errstr;
1584                    $rebuilt_okay{$id} = 1;
1585                    if ( $ping_queue{$id} ) {
1586                        $mt->ping_and_save( Entry => $entry, Blog => $blog );
1587                    }
1588                    $rebuilt++;
1589                }
1590                $mt->rebuild_indexes( Blog => $blog )
1591                  or die $mt->errstr;
1592            };
1593            if ( my $err = $@ ) {
1594
1595                # a fatal error occured while processing the rebuild
1596                # step. LOG the error and revert the entry/entries:
1597                require MT::Log;
1598                $mt->log(
1599                    {
1600                        message => $mt->translate(
1601"An error occurred while publishing scheduled entries: [_1]",
1602                            $err
1603                        ),
1604                        class   => "system",
1605                        blog_id => $blog->id,
1606                        level   => MT::Log::ERROR()
1607                    }
1608                );
1609                foreach my $id (@queue) {
1610                    next if exists $rebuilt_okay{$id};
1611                    my $e = $rebuild_queue{$id};
1612                    next unless $e;
1613                    $e->status( MT::Entry::FUTURE() );
1614                    $e->save or die $e->errstr;
1615                }
1616            }
1617        }
1618    }
1619    $total_changed > 0 ? 1 : 0;
1620}
1621
1622sub remove_entry_archive_file {
1623    my $mt    = shift;
1624    my %param = @_;
1625
1626    my $entry = $param{Entry};
1627    my $at    = $param{ArchiveType} || 'Individual';
1628    my $cat   = $param{Category};
1629    my $auth  = $param{Author};
1630
1631    require MT::TemplateMap;
1632    my $blog = $param{Blog};
1633    unless ($blog) {
1634        if ($entry) {
1635            $blog = $entry->blog;
1636        }
1637        elsif ($cat) {
1638            require MT::Blog;
1639            $blog = MT::Blog->load( $cat->blog_id )
1640                or return;
1641        }
1642    }
1643    my @map = MT::TemplateMap->load(
1644        {
1645            archive_type => $at,
1646            blog_id      => $blog->id,
1647            $param{TemplateID} ? ( template_id => $param{TemplateID} ) : (),
1648        }
1649    );
1650    return 1 unless @map;
1651
1652    my $fmgr = $blog->file_mgr;
1653    my $arch_root = ( $at eq 'Page' ) ? $blog->site_path : $blog->archive_path;
1654
1655    require File::Spec;
1656    for my $map (@map) {
1657        my $file =
1658          $mt->archive_file_for( $entry, $blog, $at, $cat, $map, undef, $auth );
1659        $file = File::Spec->catfile( $arch_root, $file );
1660        if ( !defined($file) ) {
1661            die MT->translate( $blog->errstr() );
1662            return $mt->error( MT->translate( $blog->errstr() ) );
1663        }
1664
1665        # Run callbacks
1666        MT->run_callbacks( 'pre_delete_archive_file', $file, $at, $entry);
1667
1668        $fmgr->delete($file);
1669
1670        # Run callbacks
1671        MT->run_callbacks( 'post_delete_archive_file', $file, $at, $entry);
1672    }
1673    1;
1674}
1675
1676##
1677## archive_file_for takes an entry to determine the timestamps,
1678## but if the entry is not available it uses the time_start
1679## and time_end values
1680##
1681sub archive_file_for {
1682    my $mt = shift;
1683    init_archive_types() unless %ArchiveTypes;
1684
1685    my ( $entry, $blog, $at, $cat, $map, $timestamp, $author ) = @_;
1686    return if $at eq 'None';
1687    my $archiver = $mt->archiver($at);
1688    return '' unless $archiver;
1689
1690    my $file;
1691    if ( $blog->is_dynamic ) {
1692        require MT::TemplateMap;
1693        $map = MT::TemplateMap->new;
1694        $map->file_template( $archiver->dynamic_template );
1695    }
1696    unless ($map) {
1697        my $cache = MT::Request->instance->cache('maps');
1698        unless ($cache) {
1699            MT::Request->instance->cache( 'maps', $cache = {} );
1700        }
1701        unless ( $map = $cache->{ $blog->id . $at } ) {
1702            require MT::TemplateMap;
1703            $map = MT::TemplateMap->load(
1704                {
1705                    blog_id      => $blog->id,
1706                    archive_type => $at,
1707                    is_preferred => 1
1708                }
1709            );
1710            $cache->{ $blog->id . $at } = $map if $map;
1711        }
1712    }
1713    my $file_tmpl = $map->file_template if $map;
1714    unless ($file_tmpl) {
1715        if ( my $tmpls = $archiver->default_archive_templates ) {
1716            my ($default) = grep { $_->{default} } @$tmpls;
1717            $file_tmpl = $default->{template} if $default;
1718        }
1719    }
1720    $file_tmpl ||= '';
1721    my ($ctx);
1722    if ( $file_tmpl =~ m/\%[_-]?[A-Za-z]/ ) {
1723        if ( $file_tmpl =~ m/<\$?MT/ ) {
1724            $file_tmpl =~
1725s!(<\$?MT[^>]+?>)|(%[_-]?[A-Za-z])!$1 ? $1 : '<MTFileTemplate format="'. $2 . '">'!gie;
1726        }
1727        else {
1728            $file_tmpl = qq{<MTFileTemplate format="$file_tmpl">};
1729        }
1730    }
1731    if ($file_tmpl) {
1732        require MT::Template::Context;
1733        $ctx = MT::Template::Context->new;
1734        $ctx->stash( 'blog', $blog );
1735    }
1736    local $ctx->{__stash}{category}         = $cat if $cat;
1737    local $ctx->{__stash}{archive_category} = $cat if $cat;
1738    $timestamp = $entry->authored_on() if $entry;
1739    local $ctx->{__stash}{entry} = $entry if $entry;
1740    local $ctx->{__stash}{author} =
1741      $author ? $author : $entry ? $entry->author : undef;
1742
1743    my %blog_at = map { $_ => 1 } split /,/, $blog->archive_type;
1744    return '' unless $blog_at{$at};
1745
1746    $file = $archiver->archive_file(
1747        $ctx,
1748        Timestamp => $timestamp,
1749        Template  => $file_tmpl
1750    );
1751    if ( $file_tmpl && !$file ) {
1752        local $ctx->{archive_type} = $at;
1753        require MT::Builder;
1754        my $build = MT::Builder->new;
1755        my $tokens = $build->compile( $ctx, $file_tmpl )
1756          or return $blog->error( $build->errstr() );
1757        defined( $file = $build->build( $ctx, $tokens ) )
1758          or return $blog->error( $build->errstr() );
1759    }
1760    else {
1761        my $ext = $blog->file_extension;
1762        $file .= '.' . $ext if $ext;
1763    }
1764    $file;
1765}
1766
1767# Adds an element to the rebuild queue when the plugin is enabled.
1768sub queue_build_file_filter {
1769    my $mt = shift;
1770    my ( $cb, %args ) = @_;
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::ASYNC();
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.