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

Revision 1704, 72.6 kB (checked in by bchoate, 20 months ago)

Fixed assignment of entry class for publishing pages.

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