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

Revision 2727, 74.7 kB (checked in by bchoate, 17 months ago)

Updated MultiBlog to issue triggered publishing for scheduled posts. BugId:80355

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