root/branches/release-36/lib/MT/WeblogPublisher.pm @ 2023

Revision 2023, 75.1 kB (checked in by bchoate, 19 months ago)

Fixes for manual status checks and overrides. BugId:79398

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