root/branches/release-41/lib/MT/CMS/Template.pm @ 2715

Revision 2715, 87.8 kB (checked in by fumiakiy, 17 months ago)

Populate template maps whose build type is dynamic and the file template is changed upon saving the template, to update fileinfo records. BugId:80446

  • 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::CMS::Template;
8
9use strict;
10
11sub edit {
12    my $cb = shift;
13    my ($app, $id, $obj, $param) = @_;
14
15    my $q = $app->param;
16    my $blog_id = $q->param('blog_id');
17
18    # FIXME: enumeration of types
19    unless ( $blog_id ) {
20        my $type = $q->param('type') || ( $obj ? $obj->type : undef );
21        return $app->return_to_dashboard( redirect => 1 )
22            if $type eq 'archive'
23            || $type eq 'individual'
24            || $type eq 'category'
25            || $type eq 'page'
26            || $type eq 'index';
27    }
28
29    # to trigger autosave logic in main edit routine
30    $param->{autosave_support} = 1;
31
32    my $type = $q->param('_type');
33    my $blog = $app->blog;
34    my $cfg = $app->config;
35    my $perms = $app->permissions;
36    my $can_preview = 0;
37
38    if ($blog) {
39        # include_system/include_cache are only applicable
40        # to blog-level templates
41        $param->{include_system} = $blog->include_system;
42        $param->{include_cache} = $blog->include_cache;
43    }
44
45    if ($id) {
46        # FIXME: Template types should not be enumerated here
47        $param->{nav_templates} = 1;
48        my $tab;
49        if ( $obj->type eq 'index' ) {
50            $tab = 'index';
51            $param->{template_group_trans} = $app->translate('index');
52        }
53        elsif ($obj->type eq 'archive'
54            || $obj->type eq 'individual'
55            || $obj->type eq 'category'
56            || $obj->type eq 'page' )
57        {
58
59            # FIXME: enumeration of types
60            $tab = 'archive';
61            $param->{template_group_trans} = $app->translate('archive');
62        }
63        elsif ( $obj->type eq 'custom' ) {
64            $tab = 'module';
65            $param->{template_group_trans} = $app->translate('module');
66        }
67        elsif ( $obj->type eq 'widget' ) {
68            $tab = 'widget';
69            $param->{template_group_trans} = $app->translate('widget');
70        }
71        elsif ( $obj->type eq 'email' ) {
72            $tab = 'email';
73            $param->{template_group_trans} = $app->translate('email');
74        }
75        else {
76            $tab = 'system';
77            $param->{template_group_trans} = $app->translate('system');
78        }
79        $param->{template_group} = $tab;
80        $blog_id = $obj->blog_id;
81
82        # FIXME: enumeration of types
83             $param->{has_name} = $obj->type eq 'index'
84          || $obj->type eq 'custom'
85          || $obj->type eq 'widget'
86          || $obj->type eq 'archive'
87          || $obj->type eq 'category'
88          || $obj->type eq 'page'
89          || $obj->type eq 'individual';
90        if ( !$param->{has_name} ) {
91            $param->{ 'type_' . $obj->type } = 1;
92            $param->{name} = $obj->name;
93        }
94        $app->add_breadcrumb( $param->{name} );
95        $param->{has_outfile} = $obj->type eq 'index';
96        $param->{has_rebuild} =
97          (      ( $obj->type eq 'index' )
98              && ( ( $blog->custom_dynamic_templates || "" ) ne 'all' ) );
99
100        # FIXME: enumeration of types
101             $param->{is_special} = $param->{type} ne 'index'
102          && $param->{type} ne 'archive'
103          && $param->{type} ne 'category'
104          && $param->{type} ne 'page'
105          && $param->{type} ne 'individual';
106             $param->{has_build_options} = $param->{has_build_options}
107          && $param->{type} ne 'custom'
108          && $param->{type} ne 'widget'
109          && !$param->{is_special};
110        $param->{search_label} = $app->translate('Templates');
111        $param->{object_type}  = 'template';
112        my $published_url = $obj->published_url;
113        $param->{published_url} = $published_url if $published_url;
114        $param->{saved_rebuild} = 1 if $q->param('saved_rebuild');
115        require MT::PublishOption;
116        $param->{static_maps} = $obj->build_type == MT::PublishOption::DYNAMIC() ? 0 : 1;
117
118        my $filter = $app->param('filter_key');
119        if ($param->{template_group} eq 'email') {
120            $app->param( 'filter_key', 'email_templates' );
121        }elsif  ($param->{template_group} eq 'system') {
122            $app->param( 'filter_key', 'system_templates' );
123        }
124        $app->load_list_actions( 'template', $param );
125        $app->param( 'filter_key', $filter );
126
127        $obj->compile;
128        if ( $obj->{errors} && @{ $obj->{errors} } ) {
129            $param->{error} = $app->translate(
130                "One or more errors were found in this template.");
131            $param->{error} .= "<ul>\n";
132            foreach my $err ( @{ $obj->{errors} } ) {
133                $param->{error} .= "<li>"
134                  . MT::Util::encode_html( $err->{message} )
135                  . "</li>\n";
136            }
137            $param->{error} .= "</ul>\n";
138        }
139
140        # Populate list of included templates
141        if ( my $includes = $obj->getElementsByTagName('Include') ) {
142            my @includes;
143            my @widgets;
144            my %seen;
145            foreach my $tag (@$includes) {
146                my $include = {};
147                my $mod = $include->{include_module} = $tag->[1]->{module} || $tag->[1]->{widget};
148                next unless $mod;
149                my $type = $tag->[1]->{widget} ? 'widget' : 'custom';
150                next if exists $seen{$type}{$mod};
151                $seen{$type}{$mod} = 1;
152                my $other = MT::Template->load(
153                    {
154                        blog_id => [ $obj->blog_id, 0 ],
155                        name    => $mod,
156                        type    => $type,
157                    }, {
158                        sort      => 'blog_id',
159                        direction => 'descend',
160                    }
161                );
162                if ($other) {
163                    $include->{include_link} = $app->mt_uri(
164                        mode => 'view',
165                        args => {
166                            blog_id => $other->blog_id || 0,
167                            '_type' => 'template',
168                            id      => $other->id
169                        }
170                    );
171                    # Try to compile template module if using MTInclude in this template.
172                    $other->compile;
173                    if ( $other->{errors} && @{ $other->{errors} } ) {
174                        $param->{error} = $app->translate(
175                            "One or more errors were found in included template module (".$other->name.").");
176                        $param->{error} .= "<ul>\n";
177                        foreach my $err ( @{ $other->{errors} } ) {
178                            $param->{error} .= "<li>"
179                              . MT::Util::encode_html( $err->{message} )
180                              . "</li>\n";
181                        }
182                        $param->{error} .= "</ul>\n";
183                    }
184                }
185                else {
186                    $include->{create_link} = $app->mt_uri(
187                        mode => 'view',
188                        args => {
189                            blog_id => $obj->blog_id,
190                            '_type' => 'template',
191                            type    => $type,
192                            name    => $mod,
193                        }
194                    );
195                }
196                if ($type eq 'widget') {
197                    push @widgets, $include;
198                } else {
199                    push @includes, $include;
200                }
201            }
202            $param->{include_loop} = \@includes if @includes;
203            $param->{widget_loop} = \@widgets if @widgets;
204        }
205        my @sets = ( @{ $obj->getElementsByTagName('WidgetSet') || [] }, @{ $obj->getElementsByTagName('WidgetManager') || [] } );
206        if ( @sets ) {
207            my @widget_sets;
208            my %seen;
209            foreach my $set (@sets) {
210                my $name = $set->[1]->{name};
211                next unless $name;
212                next if $seen{$name};
213                $seen{$name} = 1;
214                my $wset = MT::Template->load(
215                    {
216                        blog_id => [ $obj->blog_id, 0 ],
217                        name    => $name,
218                        type    => 'widgetset',
219                    }, {
220                        sort      => 'blog_id',
221                        direction => 'descend',
222                    }
223                );
224                if ( $wset ) {
225                    push @widget_sets, {
226                        include_link => $app->mt_uri(
227                            mode => 'edit_widget',
228                            args => {
229                                blog_id => $wset->blog_id,
230                                id => $wset->id,
231                            },
232                        ),
233                        include_module => $name,
234                    };
235                }
236                else {
237                    push @widget_sets, {
238                        create_link => $app->mt_uri(
239                            mode => 'edit_widget',
240                            args => {
241                                blog_id => $blog_id,
242                                name    => $name
243                            },
244                        ),
245                        include_module => $name,
246                    };
247                }
248            }
249            $param->{widget_set_loop} = \@widget_sets if @widget_sets;
250        }
251        $param->{have_includes} = 1 if $param->{widget_set_loop} || $param->{include_loop} || $param->{widget_loop};
252        # Populate archive types for creating new map
253        my $obj_type = $obj->type;
254        if (   $obj_type eq 'individual'
255            || $obj_type eq 'page'
256            || $obj_type eq 'author'
257            || $obj_type eq 'category'
258            || $obj_type eq 'archive' )
259        {
260            my @at = $app->publisher->archive_types;
261            my @archive_types;
262            for my $at (@at) {
263                my $archiver      = $app->publisher->archiver($at);
264                my $archive_label = $archiver->archive_label;
265                $archive_label = $at unless $archive_label;
266                $archive_label = $archive_label->()
267                  if ( ref $archive_label ) eq 'CODE';
268                if (   ( $obj_type eq 'archive' )
269                    || ( $obj_type eq 'author' )
270                    || ( $obj_type eq 'category' ) )
271                {
272
273                    # only include if it is NOT an entry-based archive type
274                    next if $archiver->entry_based;
275                }
276                elsif ( $obj_type eq 'page' ) {
277                    # only include if it is a entry-based archive type and page
278                    next unless $archiver->entry_based;
279                    next if $archiver->entry_class ne 'page';
280                }
281                elsif ( $obj_type eq 'individual' ) {
282                    # only include if it is a entry-based archive type and entry
283                    next unless $archiver->entry_based;
284                    next if $archiver->entry_class eq 'page';
285                }
286                push @archive_types,
287                  {
288                    archive_type_translated => $archive_label,
289                    archive_type            => $at,
290                  };
291                @archive_types =
292                  sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @archive_types;
293            }
294            $param->{archive_types} = \@archive_types;
295
296            # Populate template maps for this template
297            my $maps = _populate_archive_loop( $app, $blog, $obj );
298            if (@$maps) {
299                $param->{object_loop} = $param->{template_map_loop} = $maps
300                  if @$maps;
301                my %at;
302                my $build_type = MT::PublishOption::DYNAMIC();
303                my $build_type_0 = 0;
304                foreach my $map ( @$maps ) {
305                    $at{ $map->{archive_label} } = 1;
306                    $build_type_0 = $map->{map_build_type};
307                    $build_type = $map->{map_build_type}
308                        if MT::PublishOption::DYNAMIC() ne $map->{map_build_type};
309                }
310                $param->{enabled_archive_types} = join(", ", sort keys %at);
311                $param->{static_maps} = $build_type == MT::PublishOption::DYNAMIC() ? 0 : 1;
312                $param->{build_type_0} = 1 unless $build_type_0;
313            }
314        }
315        # publish options
316        $param->{build_type} = $obj->build_type;
317        $param->{ 'build_type_' . ( $obj->build_type || 0 ) } = 1;
318        #my ( $period, $interval ) = _get_schedule( $obj->build_interval );
319        #$param->{ 'schedule_period_' . $period } = 1;
320        #$param->{schedule_interval} = $interval;
321        $param->{type} = 'custom' if $param->{type} eq 'module';
322    } else {
323        my $new_tmpl = $q->param('create_new_template');
324        my $template_type;
325        if ($new_tmpl) {
326            if ( $new_tmpl =~ m/^blank:(.+)/ ) {
327                $template_type = $1;
328                $param->{type} = $1;
329            }
330            elsif ( $new_tmpl =~ m/^default:([^:]+):(.+)/ ) {
331                $template_type = $1;
332                $template_type = 'custom' if $template_type eq 'module';
333                my $template_id = $2;
334                my $set = $blog ? $blog->template_set : undef;
335                require MT::DefaultTemplates;
336                my $def_tmpl = MT::DefaultTemplates->templates($set) || [];
337                my ($tmpl) =
338                  grep { $_->{identifier} eq $template_id } @$def_tmpl;
339                $param->{text} = $app->translate_templatized( $tmpl->{text} )
340                  if $tmpl;
341                $param->{type} = $template_type;
342            }
343        }
344        else {
345            $template_type = $q->param('type');
346            $template_type = 'custom' if 'module' eq $template_type;
347            $param->{type}   = $template_type;
348        }
349        return $app->errtrans("Create template requires type")
350          unless $template_type;
351        $param->{nav_templates} = 1;
352        my $tab;
353
354        # FIXME: enumeration of types
355        if ( $template_type eq 'index' ) {
356            $tab = 'index';
357            $param->{template_group_trans} = $app->translate('index');
358        }
359        elsif ($template_type eq 'archive'
360            || $template_type eq 'individual'
361            || $template_type eq 'category'
362            || $template_type eq 'page' )
363        {
364            $tab                         = 'archive';
365            $param->{template_group_trans} = $app->translate('archive');
366            $param->{type_archive}         = 1;
367            my @types = (
368                {
369                    key   => 'archive',
370                    label => $app->translate('Archive')
371                },
372                {
373                    key   => 'individual',
374                    label => $app->translate('Entry or Page')
375                },
376            );
377            $param->{new_archive_types} = \@types;
378        }
379        elsif ( $template_type eq 'custom' ) {
380            $tab = 'module';
381            $param->{template_group_trans} = $app->translate('module');
382        }
383        elsif ( $template_type eq 'widget' ) {
384            $tab = 'widget';
385            $param->{template_group_trans} = $app->translate('widget');
386        }
387        else {
388            $tab = 'system';
389            $param->{template_group_trans} = $app->translate('system');
390        }
391        $param->{template_group} = $tab;
392        $app->translate($tab);
393        $app->add_breadcrumb( $app->translate('New Template') );
394
395        # FIXME: enumeration of types
396             $param->{has_name} = $template_type eq 'index'
397          || $template_type eq 'custom'
398          || $template_type eq 'widget'
399          || $template_type eq 'archive'
400          || $template_type eq 'category'
401          || $template_type eq 'page'
402          || $template_type eq 'individual';
403        $param->{has_outfile} = $template_type eq 'index';
404        $param->{has_rebuild} =
405          (      ( $template_type eq 'index' )
406              && ( ( $blog->custom_dynamic_templates || "" ) ne 'all' ) );
407        $param->{custom_dynamic} =
408          $blog && $blog->custom_dynamic_templates eq 'custom';
409        $param->{has_build_options} =
410             $blog && ($blog->custom_dynamic_templates eq 'custom'
411          || $param->{has_rebuild});
412
413        # FIXME: enumeration of types
414             $param->{is_special} = $param->{type} ne 'index'
415          && $param->{type} ne 'archive'
416          && $param->{type} ne 'category'
417          && $param->{type} ne 'page'
418          && $param->{type} ne 'individual';
419             $param->{has_build_options} = $param->{has_build_options}
420          && $param->{type} ne 'custom'
421          && $param->{type} ne 'widget'
422          && !$param->{is_special};
423
424        $param->{name}       = MT::Util::decode_url( $app->param('name') )
425          if $app->param('name');
426    }
427    $param->{publish_queue_available} = eval 'require List::Util; require Scalar::Util; 1;';
428
429    my $set = $blog ? $blog->template_set : undef;
430    require MT::DefaultTemplates;
431    my $tmpls = MT::DefaultTemplates->templates($set);
432    my @tmpl_ids;
433    foreach my $dtmpl (@$tmpls) {
434        if ( !$param->{has_name} ) {
435            if ($obj->type eq 'email') {
436                if ($dtmpl->{identifier} eq $obj->identifier) {
437                    $param->{template_name_label} = $dtmpl->{label};
438                    $param->{template_name}       = $dtmpl->{name};
439                }
440            }
441            else {
442                if ( $dtmpl->{type} eq $obj->type ) {
443                    $param->{template_name_label} = $dtmpl->{label};
444                    $param->{template_name}       = $dtmpl->{name};
445                }
446            }
447        }
448        if ( $dtmpl->{type} eq 'index' ) {
449            push @tmpl_ids,
450              {
451                label    => $dtmpl->{label},
452                key      => $dtmpl->{key},
453                selected => $dtmpl->{key} eq
454                  ( ( $obj ? $obj->identifier : undef ) || '' ),
455              };
456        }
457    }
458    $param->{index_identifiers} = \@tmpl_ids;
459
460    $param->{"type_$param->{type}"} = 1;
461    if ($perms) {
462        my $pref_param =
463          $app->load_template_prefs( $perms->template_prefs );
464        %$param = ( %$param, %$pref_param );
465    }
466
467    # Populate structure for template snippets
468    if ( my $snippets = $app->registry('template_snippets') || {} ) {
469        my @snippets;
470        for my $snip_id ( keys %$snippets ) {
471            my $label = $snippets->{$snip_id}{label};
472            $label = $label->() if ref($label) eq 'CODE';
473            push @snippets,
474              {
475                id      => $snip_id,
476                trigger => $snippets->{$snip_id}{trigger},
477                label   => $label,
478                content => $snippets->{$snip_id}{content},
479              };
480        }
481        @snippets = sort { $a->{label} cmp $b->{label} } @snippets;
482        $param->{template_snippets} = \@snippets;
483    }
484
485    # Populate structure for tag documentation
486    my $all_tags = MT::Component->registry("tags");
487    my $tag_docs = {};
488    foreach my $tag_set (@$all_tags) {
489        my $url = $tag_set->{help_url};
490        $url = $url->() if ref($url) eq 'CODE';
491        # hey, at least give them a google search
492        $url ||= 'http://www.google.com/search?q=mt%t';
493        my $tag_list = '';
494        foreach my $type (qw( block function )) {
495            my $tags = $tag_set->{$type} or next;
496            $tag_list .= ($tag_list eq '' ? '' : ',') . join(",", keys(%$tags));
497        }
498        $tag_list =~ s/(^|,)plugin(,|$)/,/;
499        if (exists $tag_docs->{$url}) {
500            $tag_docs->{$url} .= ',' . $tag_list;
501        }
502        else {
503            $tag_docs->{$url} = $tag_list;
504        }
505    }
506    $param->{tag_docs} = $tag_docs;
507    $param->{link_doc} = $app->help_url('appendices/tags/');
508
509    $param->{screen_id} = "edit-template-" . $param->{type};
510
511    # template language
512    $param->{template_lang} = 'html';
513    if ( $obj && $obj->outfile ) {
514        if ( $obj->outfile =~ m/\.(css|js|html|php|pl|asp)$/ ) {
515            $param->{template_lang} = {
516                css => 'css',
517                js => 'javascript',
518                html => 'html',
519                php => 'php',
520                pl => 'perl',
521                asp => 'asp',
522            }->{$1};
523        }
524    }
525
526    if (($param->{type} eq 'custom') || ($param->{type} eq 'widget')) {
527        if ($blog) {
528            $param->{include_with_ssi}      = 0;
529            $param->{cache_path}            = '';
530            $param->{cache_expire_type}     = 0;
531            $param->{cache_expire_period}   = '';
532            $param->{cache_expire_interval} = 0;
533            $param->{ssi_type} = uc $blog->include_system;
534        }
535        if ($obj) {
536            $param->{include_with_ssi} = $obj->include_with_ssi
537              if defined $obj->include_with_ssi;
538            $param->{cache_path}       = $obj->cache_path
539              if defined $obj->cache_path;
540            $param->{cache_expire_type} = $obj->cache_expire_type
541              if defined $obj->cache_expire_type;
542            my ( $period, $interval ) =
543              _get_schedule( $obj->cache_expire_interval );
544            $param->{cache_expire_period}   = $period   if defined $period;
545            $param->{cache_expire_interval} = $interval if defined $interval;
546            my @events = split ',', ($obj->cache_expire_event || '');
547            foreach my $name (@events) {
548                $param->{ 'cache_expire_event_' . $name } = 1;
549            }
550        }
551    }
552
553    # if unset, default to 30 so if they choose to enable caching,
554    # it will be preset to something sane.
555    $param->{cache_expire_interval} ||= 30;
556
557    $param->{dirty} = 1
558        if $app->param('dirty');
559
560    $param->{can_preview} = 1
561        if (!$param->{is_special}) && (!$obj || ($obj && ($obj->outfile || '') !~ m/\.(css|xml|rss|js)$/));
562
563    1;
564}
565
566sub list {
567    my $app = shift;
568
569    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
570    return $app->return_to_dashboard( redirect => 1 )
571      unless $perms || $app->user->is_superuser;
572    if ( $perms && !$perms->can_edit_templates ) {
573        return $app->return_to_dashboard( permission => 1 );
574    }
575    my $blog = $app->blog;
576
577    require MT::Template;
578    my $blog_id = $app->param('blog_id') || 0;
579    my $terms = { blog_id => $blog_id };
580    my $args  = { sort    => 'name' };
581
582    my $hasher = sub {
583        my ( $obj, $row ) = @_;
584        my $template_type;
585        my $type = $row->{type} || '';
586        if ( $type =~ m/^(individual|page|category|archive)$/ ) {
587            $template_type = 'archive';
588            # populate context with templatemap loop
589            my $tblog = $obj->blog_id == $blog->id ? $blog : MT::Blog->load( $obj->blog_id );
590            if ($tblog) {
591                $row->{archive_types} = _populate_archive_loop( $app, $tblog, $obj );
592            }
593        }
594        elsif ( $type eq 'widget' ) {
595            $template_type = 'widget';
596        }
597        elsif ( $type eq 'index' ) {
598            $template_type = 'index';
599        }
600        elsif ( $type eq 'custom' ) {
601            $template_type = 'module';
602        }
603        elsif ( $type eq 'email' ) {
604            $template_type = 'email';
605        }
606        elsif ( $type eq 'backup' ) {
607            $template_type = 'backup';
608        }
609        else {
610            $template_type = 'system';
611        }
612        $row->{use_cache} = ( ($obj->cache_expire_type || 0) != 0 ) ? 1 : 0;
613        $row->{template_type} = $template_type;
614        $row->{type} = 'entry' if $type eq 'individual';
615        my $published_url = $obj->published_url;
616        $row->{published_url} = $published_url if $published_url;
617    };
618
619    my $params        = {};
620    my $filter = $app->param('filter_key');
621    my $template_type = $filter || '';
622    $template_type =~ s/_templates//;
623
624    $params->{screen_class} = "list-template";
625    $params->{listing_screen} = 1;
626
627    $app->load_list_actions( 'template', $params );
628    $params->{page_actions} = $app->page_actions('list_templates');
629    $params->{search_label} = $app->translate("Templates");
630    $params->{object_type} = 'template';
631    $params->{blog_view} = 1;
632    $params->{refreshed} = $app->param('refreshed');
633    $params->{published} = $app->param('published');
634    $params->{saved_copied} = $app->param('saved_copied');
635    $params->{saved_deleted} = $app->param('saved_deleted');
636    $params->{saved} = $app->param('saved');
637
638    # determine list of system template types:
639    my $scope;
640    my $set;
641    if ( $blog ) {
642        $set   = $blog->template_set;
643        $scope = 'system';
644    }
645    else {
646        $scope = 'global:system';
647    }
648    my @tmpl_path = ( $set && ($set ne 'mt_blog')) ? ("template_sets", $set, 'templates', $scope) : ("default_templates", $scope);
649    my $sys_tmpl = MT->registry(@tmpl_path) || {};
650
651    my @tmpl_loop;
652    my %types;
653    if ($template_type ne 'backup') {
654        if ($blog) {
655            # blog template listings
656            %types = ( 
657                'index' => {
658                    label => $app->translate("Index Templates"),
659                    type => 'index',
660                    order => 100,
661                },
662                'archive' => {
663                    label => $app->translate("Archive Templates"),
664                    type => ['archive', 'individual', 'page', 'category'],
665                    order => 200,
666                },
667                'module' => {
668                    label => $app->translate("Template Modules"),
669                    type => 'custom',
670                    order => 300,
671                },
672                'system' => {
673                    label => $app->translate("System Templates"),
674                    type => [ keys %$sys_tmpl ],
675                    order => 400,
676                },
677            );
678        } else {
679            # global template listings
680            %types = ( 
681                'module' => {
682                    label => $app->translate("Template Modules"),
683                    type => 'custom',
684                    order => 100,
685                },
686                'email' => {
687                    label => $app->translate("Email Templates"),
688                    type => 'email',
689                    order => 200,
690                },
691                'system' => {
692                    label => $app->translate("System Templates"),
693                    type => [ keys %$sys_tmpl ],
694                    order => 300,
695                },
696            );
697        }
698    } else {
699        # global template listings
700        %types = ( 
701            'backup' => {
702                label => $app->translate("Template Backups"),
703                type => 'backup',
704                order => 100,
705            },
706        );
707    }
708    my @types = sort { $types{$a}->{order} <=> $types{$b}->{order} } keys %types;
709    if ($template_type) {
710        @types = ( $template_type );
711    }
712    $app->delete_param('filter_key') if $filter;
713    foreach my $tmpl_type (@types) {
714        if ( $tmpl_type eq 'index' ) {
715            $app->param( 'filter_key', 'index_templates' );
716        }
717        elsif ( $tmpl_type eq 'archive' ) {
718            $app->param( 'filter_key', 'archive_templates' );
719        }
720        elsif ( $tmpl_type eq 'system' ) {
721            $app->param( 'filter_key', 'system_templates' );
722        }
723        elsif ( $tmpl_type eq 'email' ) {
724            $app->param( 'filter_key', 'email_templates' );
725        }
726        elsif ( $tmpl_type eq 'module' ) {
727            $app->param( 'filter_key', 'module_templates' );
728        }
729        my $tmpl_param = {};
730        unless ( exists($types{$tmpl_type}->{type})
731          && 'ARRAY' eq ref($types{$tmpl_type}->{type})
732          && 0 == scalar(@{$types{$tmpl_type}->{type}}) )
733        {
734            $terms->{type} = $types{$tmpl_type}->{type};
735            $tmpl_param = $app->listing(
736                {
737                    type     => 'template',
738                    terms    => $terms,
739                    args     => $args,
740                    no_limit => 1,
741                    no_html  => 1,
742                    code     => $hasher,
743                }
744            );
745        }
746        my $template_type_label = $types{$tmpl_type}->{label};
747        $tmpl_param->{template_type} = $tmpl_type;
748        $tmpl_param->{template_type_label} = $template_type_label;
749        push @tmpl_loop, $tmpl_param;
750    }
751    if ($filter) {
752        $params->{filter_key} = $filter;
753        $params->{filter_label} = $types{$template_type}{label}
754            if exists $types{$template_type};
755        $app->param('filter_key', $filter);
756    } else {
757        # restore filter_key param (we modified it for the
758        # sake of the individual table listings)
759        $app->delete_param('filter_key');
760    }
761
762    $params->{template_type_loop} = \@tmpl_loop;
763    $params->{screen_id} = "list-template";
764
765    return $app->load_tmpl('list_template.tmpl', $params);
766}
767
768sub preview {
769    my $app         = shift;
770    my $q           = $app->param;
771    my $blog_id     = $q->param('blog_id');
772    my $blog        = $app->blog;
773    my $id          = $q->param('id');
774    my $tmpl;
775    my $user_id = $app->user->id;
776
777    # We can only do previews on blog templates. Have to publish
778    # the preview file somewhere!
779    return $app->errtrans("Invalid request.") unless $blog;
780
781    require MT::Template;
782    if ($id) {
783        $tmpl = MT::Template->load( { id => $id, blog_id => $blog_id } )
784            or return $app->errtrans( "Invalid request." );
785    }
786    else {
787        $tmpl = MT::Template->new;
788        $tmpl->id(-1);
789        $tmpl->blog_id($blog_id);
790    }
791
792    my $names = $tmpl->column_names;
793    my %values = map { $_ => scalar $app->param($_) } @$names;
794    delete $values{'id'} unless $q->param('id');
795
796    ## Strip linefeed characters.
797    for my $col (qw( text )) {
798        $values{$col} =~ tr/\r//d if $values{$col};
799    }
800    $tmpl->set_values( \%values );
801
802    my $preview_basename = $app->preview_object_basename;
803
804    my $type = $tmpl->type;
805    my $preview_tmpl = $tmpl;
806    my $archive_file;
807    my $archive_url;
808    my %param;
809    my $blog_path = $blog->site_path;
810    my $blog_url = $blog->site_url;
811
812    if (($type eq 'custom') || ($type eq 'widget')) {
813        # determine 'host' template
814        $preview_tmpl = MT::Template->load({ blog_id => $blog_id, identifier => 'main_index' });
815        if (!$preview_tmpl) {
816            return $app->errtrans("Can't locate host template to preview module/widget.");
817        }
818        my $req = $app->request;
819        # stash this module so that it is selected through a
820        # MTInclude tag instead of the one in the database:
821        my $tmpl_name = $tmpl->name;
822        $tmpl_name =~ s/^Widget: // if $type eq 'widget';
823        my $stash_id = 'template_' . $type . '::' . $blog_id . '::' . $tmpl_name;
824        $req->stash($stash_id, [ $tmpl, $tmpl->tokens ]);
825    } elsif (($type eq 'individual') || ($type eq 'page')) {
826        my $ctx = $preview_tmpl->context;
827        my $entry_type = $type eq 'individual' ? 'entry' : 'page';
828        my ($obj) = create_preview_content($app, $blog, $entry_type, 1);
829        $obj->basename( $preview_basename );
830        $ctx->stash('entry', $obj);
831        $ctx->{current_archive_type} = $type eq 'individual' ? 'Individual' : 'Page';
832        if (($type eq 'individual') && $blog->archive_path) {
833            $blog_path = $blog->archive_path;
834            $blog_url = $blog->archive_url;
835        }
836        $archive_file = File::Spec->catfile( $blog_path, $obj->archive_file );
837        $archive_url = $obj->archive_url;
838
839        my $archiver = MT->publisher->archiver( $ctx->{current_archive_type} );
840        my $tparams = $archiver->template_params;
841        if ($tparams) {
842            $ctx->var( $_, $tparams->{$_} ) for keys %$tparams;
843        }
844    } elsif ($type eq 'archive') {
845        # some variety of archive template
846        my $ctx = $preview_tmpl->context;
847        require MT::TemplateMap;
848        my $map = MT::TemplateMap->load( { template_id => $id, is_preferred => 1 });
849        if (! $map) {
850            return $app->error("Cannot preview without a template map!");
851        }
852        $ctx->{current_archive_type} = $map->archive_type;
853        my $archiver = MT->publisher->archiver( $map->archive_type );
854        my $tparams = $archiver->template_params;
855        if ($tparams) {
856            $ctx->var( $_, $tparams->{$_} ) for keys %$tparams;
857        }
858        my @entries = create_preview_content($app, $blog, $archiver->entry_class, 10);
859        if ($archiver->date_based) {
860            $ctx->{current_timestamp} = $entries[0]->authored_on;
861            $ctx->{current_timestamp_end} = $entries[$#entries]->authored_on;
862        }
863        if ($archiver->author_based) {
864            $ctx->stash('author', $app->user);
865        }
866        my $cat;
867        if ($archiver->category_based) {
868            $cat = new MT::Category;
869            $cat->label($app->translate("Preview"));
870            $cat->basename("preview");
871            $cat->parent(0);
872            $ctx->stash('archive_category', $cat);
873        }
874        $ctx->stash('entries', \@entries);
875
876        my $file = MT->publisher->archive_file_for( $entries[0], $blog, $map->archive_type, $cat, $map, $ctx->{current_timestamp}, $app->user);
877        $archive_file = File::Spec->catfile( $blog_path, $file );
878        $archive_url = MT::Util::caturl( $blog_url, $file );
879    } elsif ($type eq 'index') {
880    } else {
881        # for now, only index templates can be previewed
882        return $app->errtrans("Invalid request.");
883    }
884
885    my $orig_file;
886    my $path;
887
888    # Default case; works for index templates (other template types should
889    # have defined $archive_file by now).
890    $archive_file = File::Spec->catfile( $blog_path, $preview_tmpl->outfile )
891        unless defined $archive_file;
892
893    ( $orig_file, $path ) = File::Basename::fileparse( $archive_file );
894
895    $archive_url = MT::Util::caturl( $blog_url, $orig_file )
896        unless defined $archive_url;
897
898    my $file_ext;
899    require File::Basename;
900    $file_ext = $archive_file;
901    if ($file_ext =~ m/\.[a-z]+$/) {
902        $file_ext =~ s!.+\.!.!;
903    } else {
904        $file_ext = '';
905    }
906    $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext );
907
908    my @data;
909    $app->run_callbacks( 'cms_pre_preview.template', $app, $preview_tmpl, \@data );
910
911    my $has_hires = eval 'require Time::HiRes; 1' ? 1 : 0;
912    my $start_time = $has_hires ? Time::HiRes::time() : time;
913
914    my $ctx = $preview_tmpl->context;
915    $ctx->var('preview_template', 1);
916    my $html = $preview_tmpl->output;
917
918    $param{build_time} = $has_hires ? sprintf("%.3f", Time::HiRes::time() - $start_time ) : "~" . ( time - $start_time );
919
920    unless ( defined($html) ) {
921        return $app->error( $app->translate( "Publish error: [_1]",
922            MT::Util::encode_html( $preview_tmpl->errstr ) ) );
923    }
924
925    # If MT is configured to do 'local' previews, convert all
926    # the normal blog URLs into the domain used by MT itself (ie,
927    # blog is published to www.example.com, which is a different
928    # server from where MT runs, mt.example.com; previews therefore
929    # should occur locally, so replace all http://www.example.com/
930    # with http://mt.example.com/).
931    my ($old_url, $new_url);
932    if ($app->config('LocalPreviews')) {
933        $old_url = $blog_url;
934        $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!;
935        $new_url = $app->base . '/';
936        $html =~ s!\Q$old_url\E!$new_url!g;
937    }
938
939    my $fmgr = $blog->file_mgr;
940
941    ## Determine if we need to build directory structure,
942    ## and build it if we do. DirUmask determines
943    ## directory permissions.
944    require File::Basename;
945    $path =~ s!/$!!
946      unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
947    unless ( $fmgr->exists($path) ) {
948        $fmgr->mkpath($path);
949    }
950
951    if ( $fmgr->exists($path) && $fmgr->can_write($path) ) {
952        $param{preview_file} = $preview_basename;
953        my $preview_url = $archive_url;
954        $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x;
955
956        # We also have to translate the URL used for the
957        # published file to be on the MT app domain.
958        if (defined $new_url) {
959            $preview_url =~ s!^\Q$old_url\E!$new_url!;
960        }
961
962        $param{preview_url}  = $preview_url;
963
964        $fmgr->put_data( $html, $archive_file );
965
966        # we have to make a record of this preview just in case it
967        # isn't cleaned up by re-editing, saving or cancelling on
968        # by the user.
969        require MT::Session;
970        my $sess_obj = MT::Session->get_by_key(
971            {
972                id   => $preview_basename,
973                kind => 'TF',                # TF = Temporary File
974                name => $archive_file,
975            }
976        );
977        $sess_obj->start(time);
978        $sess_obj->save;
979    }
980    else {
981        return $app->error( $app->translate(
982            "Unable to create preview file in this location: [_1]", $path ) );
983    }
984
985    $param{id} = $id if $id;
986    $param{new_object} = $param{id} ? 0 : 1;
987    $param{name} = $tmpl->name;
988    $q->param( 'build_dynamic', $tmpl->build_dynamic );
989    my $cols = $tmpl->column_names;
990    for my $col (@$cols) {
991        push @data,
992          {
993            data_name  => $col,
994            data_value => scalar $q->param($col)
995          };
996    }
997    $param{template_loop} = \@data;
998    $param{object_type}  = $type;
999    return $app->load_tmpl( 'preview_template_strip.tmpl', \%param );
1000}
1001
1002sub create_preview_content {
1003    my ($app, $blog, $type, $number) = @_;
1004
1005    my $blog_id = $blog->id;
1006    my $entry_class = $app->model($type);
1007    my @obj = $entry_class->load({
1008        blog_id => $blog_id,
1009        status => MT::Entry::RELEASE()
1010    }, {
1011        limit => $number || 1,
1012        direction => 'descend',
1013        'sort' => 'authored_on'
1014    });
1015    unless ( @obj ) {
1016        # create a dummy object
1017        my $obj = $entry_class->new;
1018        $obj->blog_id($blog_id);
1019        $obj->id(-1);
1020        $obj->author_id( $app->user->id );
1021        $obj->authored_on( $blog->current_timestamp );
1022        $obj->status( MT::Entry::RELEASE() );
1023        $obj->title($app->translate("Lorem ipsum"));
1024        my $preview_text = $app->translate('LOREM_IPSUM_TEXT');
1025        if ($preview_text eq 'LOREM_IPSUM_TEXT') {
1026            $preview_text = q{Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut diam quam, accumsan eu, aliquam vel, ultrices a, augue. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce hendrerit, lacus eget bibendum sollicitudin, mi tellus interdum neque, sit amet pretium tortor tellus id erat. Duis placerat justo ac erat. Duis posuere, risus eu elementum viverra, nisl lacus sagittis lorem, ac fermentum neque pede vitae arcu. Phasellus arcu elit, placerat eu, luctus posuere, tristique non, augue. In hac habitasse platea dictumst. Nunc non dolor et ipsum mattis malesuada. Praesent porta orci eu ligula. Ut dui augue, dapibus vitae, sodales in, lobortis non, felis. Aliquam feugiat mollis ipsum.};
1027        }
1028        my $preview_more = $app->translate('LORE_IPSUM_TEXT_MORE');
1029        if ($preview_text eq 'LOREM_IPSUM_TEXT_MORE') {
1030            $preview_more = q{Integer nunc nulla, vulputate sit amet, varius ac, faucibus ac, lectus. Nulla semper bibendum justo. In hac habitasse platea dictumst. Aliquam auctor pretium ante. Etiam porta consectetuer erat. Phasellus consequat, nisi eu suscipit elementum, metus leo malesuada pede, vel scelerisque lorem ligula in augue. Sed aliquet. Donec malesuada metus sit amet sapien. Integer non libero. Morbi egestas, mauris posuere consequat sodales, augue lectus suscipit velit, eu commodo lacus dolor congue justo. Suspendisse justo. Curabitur sagittis, lorem tincidunt elementum rhoncus, odio dolor mattis odio, quis ultrices ligula ipsum ac lacus. Nam et sapien ac lacus ultrices sollicitudin. Vestibulum ut dolor nec dui malesuada imperdiet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
1031
1032            Quisque pharetra libero quis nibh. Cras lacus orci, commodo et, fringilla non, lobortis non, mauris. Curabitur dui sapien, tristique imperdiet, ultrices vitae, gravida varius, ante. Maecenas ac arcu nec nibh euismod feugiat. Pellentesque sed orci eget enim egestas faucibus. Aenean laoreet leo ornare velit. Nunc fermentum dolor eget massa. Fusce fringilla, tellus in pellentesque sodales, urna mi hendrerit leo, vel adipiscing ligula odio sit amet risus. Cras rhoncus, mi et posuere gravida, purus sem porttitor nisl, auctor laoreet nisl turpis quis ligula. Aliquam in nisi tristique augue egestas lacinia. Aenean ante magna, facilisis a, faucibus at, aliquam laoreet, dui. Ut tellus leo, tristique a, pellentesque ac, bibendum non, ipsum. Curabitur eu neque pretium arcu accumsan tincidunt. Ut ipsum. Quisque congue accumsan elit. Nulla ligula felis, aliquam ultricies, vestibulum vestibulum, semper vel, sapien. Aenean sodales ligula venenatis tellus. Vestibulum leo. Morbi viverra convallis eros.
1033
1034            Phasellus rhoncus pulvinar enim. Ut gravida ante nec lectus. Nam luctus gravida odio. Morbi vitae lorem vitae justo fermentum porttitor. Suspendisse vestibulum magna at purus. Cras nec sem. Duis id felis. Mauris hendrerit dapibus est. Donec semper. Praesent vehicula interdum velit. Ut sed tellus et diam venenatis pulvinar.};
1035        }
1036        $obj->text($preview_text);
1037        $obj->text_more($preview_more);
1038        $obj->keywords(MT->translate("sample, entry, preview"));
1039        $obj->tags(qw( lorem ipsum sample preview ));
1040        @obj = ($obj);
1041    }
1042    return @obj;
1043}
1044
1045sub reset_blog_templates {
1046    my $app   = shift;
1047    my $q     = $app->param;
1048    my $perms = $app->permissions
1049      or return $app->error( $app->translate("No permissions") );
1050    return $app->error( $app->translate("Permission denied.") )
1051      unless $perms->can_edit_templates;
1052    $app->validate_magic() or return;
1053    my $blog = MT::Blog->load( $perms->blog_id )
1054        or return $app->error($app->translate('Can\'t load blog #[_1].', $perms->blog_id));
1055    require MT::Template;
1056    my @tmpl = MT::Template->load( { blog_id => $blog->id } );
1057
1058    for my $tmpl (@tmpl) {
1059        $tmpl->remove or return $app->error( $tmpl->errstr );
1060    }
1061    my $set = $blog ? $blog->template_set : undef;
1062    require MT::DefaultTemplates;
1063    my $tmpl_list = MT::DefaultTemplates->templates($set) || [];
1064    my @arch_tmpl;
1065    for my $val (@$tmpl_list) {
1066        $val->{text} = $app->translate_templatized( $val->{text} );
1067        my $tmpl = MT::Template->new;
1068        if ( ( 'widgetset' eq $val->{type} )
1069          && ( exists $val->{modulesets} ) ) {
1070            my $modulesets = delete $val->{modulesets};
1071            $tmpl->modulesets( join ',', @$modulesets );
1072        }
1073        $tmpl->set_values($val);
1074        $tmpl->build_dynamic(0);
1075        $tmpl->blog_id( $blog->id );
1076        $tmpl->save
1077          or return $app->error(
1078            $app->translate(
1079                "Populating blog with default templates failed: [_1]",
1080                $tmpl->errstr
1081            )
1082          );
1083
1084        # FIXME: enumeration of types
1085        if (   $val->{type} eq 'archive'
1086            || $val->{type} eq 'category'
1087            || $val->{type} eq 'page'
1088            || $val->{type} eq 'individual' )
1089        {
1090            push @arch_tmpl, $tmpl;
1091        }
1092    }
1093
1094    ## Set up mappings from new templates to archive types.
1095    for my $tmpl (@arch_tmpl) {
1096        my (@at);
1097
1098        # FIXME: enumeration of types
1099        if ( $tmpl->type eq 'archive' ) {
1100            @at = qw( Daily Weekly Monthly Category );
1101        }
1102        elsif ( $tmpl->type eq 'page' ) {
1103            @at = qw( Page );
1104        }
1105        elsif ( $tmpl->type eq 'individual' ) {
1106            @at = qw( Individual );
1107        }
1108        require MT::TemplateMap;
1109        for my $at (@at) {
1110            my $map = MT::TemplateMap->new;
1111            $map->archive_type($at);
1112            $map->is_preferred(1);
1113            $map->template_id( $tmpl->id );
1114            $map->blog_id( $tmpl->blog_id );
1115            $map->save
1116              or return $app->error(
1117                $app->translate(
1118                    "Setting up mappings failed: [_1]",
1119                    $map->errstr
1120                )
1121              );
1122        }
1123    }
1124    $app->redirect(
1125        $app->uri(
1126            'mode' => 'list',
1127            args =>
1128              { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 }
1129        )
1130    );
1131}
1132
1133sub _generate_map_table {
1134    my $app = shift;
1135    my ( $blog_id, $template_id ) = @_;
1136
1137    require MT::Template;
1138    require MT::Blog;
1139    my $blog     = MT::Blog->load($blog_id);
1140    my $template = MT::Template->load($template_id);
1141    my $tmpl     = $app->load_tmpl('include/archive_maps.tmpl');
1142    my $maps     = _populate_archive_loop( $app, $blog, $template );
1143    $tmpl->param( object_type => 'templatemap' );
1144    $tmpl->param( publish_queue_available => eval 'require List::Util; require Scalar::Util; 1;' );
1145    $tmpl->param( object_loop => $maps ) if @$maps;
1146    my $html = $tmpl->output();
1147
1148    if ( $html =~ m/<__trans / ) {
1149        $html = $app->translate_templatized($html);
1150    }
1151    $html;
1152}
1153
1154sub _populate_archive_loop {
1155    my $app = shift;
1156    my ( $blog, $obj ) = @_;
1157
1158    my $index = $app->config('IndexBasename');
1159    my $ext = $blog->file_extension || '';
1160    $ext = '.' . $ext if $ext ne '';
1161
1162    require MT::TemplateMap;
1163    my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } );
1164    my @maps;
1165    my %types;
1166    foreach my $map_obj (@tmpl_maps) {
1167        my $map = {};
1168        $map->{map_id}           = $map_obj->id;
1169        $map->{map_is_preferred} = $map_obj->is_preferred;
1170        # publish options
1171        $map->{map_build_type} = $map_obj->build_type;
1172        $map->{ 'map_build_type_' . ( $map_obj->build_type || 0 ) } = 1;
1173        my ( $period, $interval ) = _get_schedule( $map_obj->build_interval );
1174        $map->{ 'map_schedule_period_' . $period } = 1
1175            if defined $period;
1176        $map->{map_schedule_interval} = $interval
1177            if defined $interval;
1178
1179        my $at = $map->{archive_type} = $map_obj->archive_type;
1180        $types{$at}++;
1181        $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1
1182          if $blog->archive_type_preferred;
1183        $map->{file_template} = $map_obj->file_template
1184          if $map_obj->file_template;
1185
1186        my $archiver = $app->publisher->archiver($at);
1187        next unless $archiver;
1188        $map->{archive_label} = $archiver->archive_label;
1189        my $tmpls     = $archiver->default_archive_templates;
1190        my $tmpl_loop = [];
1191        foreach (@$tmpls) {
1192            my $name = $_->{label};
1193            $name =~ s/\.html$/$ext/;
1194            $name =~ s/index$ext$/$index$ext/;
1195            push @$tmpl_loop,
1196              {
1197                name    => $name,
1198                value   => $_->{template},
1199                default => ( $_->{default} || 0 ),
1200              };
1201        }
1202
1203        my $custom = 1;
1204
1205        foreach (@$tmpl_loop) {
1206            if (   ( !$map->{file_template} && $_->{default} )
1207                || ( $map->{file_template} eq $_->{value} ) )
1208            {
1209                $_->{selected}        = 1;
1210                $custom               = 0;
1211                $map->{file_template} = $_->{value}
1212                  if !$map->{file_template};
1213            }
1214        }
1215        if ($custom) {
1216            unshift @$tmpl_loop,
1217              {
1218                name     => $map->{file_template},
1219                value    => $map->{file_template},
1220                selected => 1,
1221              };
1222        }
1223
1224        $map->{archive_tmpl_loop} = $tmpl_loop;
1225        if (
1226            1 < MT::TemplateMap->count(
1227                { archive_type => $at, blog_id => $obj->blog_id }
1228            )
1229          )
1230        {
1231            $map->{has_multiple_archives} = 1;
1232        }
1233
1234        push @maps, $map;
1235    }
1236    @maps = sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @maps;
1237    return \@maps;
1238}
1239
1240sub delete_map {
1241    my $app = shift;
1242    $app->validate_magic() or return;
1243    my $perms = $app->{perms}
1244      or return $app->error( $app->translate("No permissions") );
1245    my $q  = $app->param;
1246    my $id = $q->param('id');
1247
1248    require MT::TemplateMap;
1249    MT::TemplateMap->remove( { id => $id } );
1250    my $html =
1251      _generate_map_table( $app, $q->param('blog_id'),
1252        $q->param('template_id') );
1253    $app->{no_print_body} = 1;
1254    $app->send_http_header("text/plain");
1255    $app->print($html);
1256}
1257
1258sub add_map {
1259    my $app = shift;
1260    $app->validate_magic() or return;
1261    my $perms = $app->{perms}
1262      or return $app->error( $app->translate("No permissions") );
1263
1264    my $q = $app->param;
1265
1266    require MT::TemplateMap;
1267    my $blog_id = $q->param('blog_id');
1268    my $at      = $q->param('new_archive_type');
1269    my $exist   = MT::TemplateMap->exist(
1270        {
1271            blog_id      => $blog_id,
1272            archive_type => $at
1273        }
1274    );
1275    my $map = MT::TemplateMap->new;
1276    $map->is_preferred( $exist ? 0 : 1 );
1277    $map->template_id( scalar $q->param('template_id') );
1278    $map->blog_id($blog_id);
1279    $map->archive_type($at);
1280    $map->save
1281      or return $app->error(
1282        $app->translate( "Saving map failed: [_1]", $map->errstr ) );
1283    my $html =
1284      _generate_map_table( $app, $blog_id, scalar $q->param('template_id') );
1285    $app->{no_print_body} = 1;
1286    $app->send_http_header("text/plain");
1287    $app->print($html);
1288}
1289
1290sub can_view {
1291    my ( $eh, $app, $id ) = @_;
1292    my $perms = $app->permissions;
1293    return !$id || ($perms && $perms->can_edit_templates) || (!$app->blog && $app->user->can_edit_templates);
1294}
1295
1296sub can_save {
1297    my ( $eh, $app, $id ) = @_;
1298    my $perms = $app->permissions;
1299    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1300}
1301
1302sub can_delete {
1303    my ( $eh, $app, $obj ) = @_;
1304    return 1 if $app->user->is_superuser();
1305    my $perms = $app->permissions;
1306    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1307}
1308
1309sub pre_save {
1310    my $eh = shift;
1311    my ( $app, $obj ) = @_;
1312
1313    ## Strip linefeed characters.
1314    ( my $text = $obj->text ) =~ tr/\r//d;
1315
1316    if ($text =~ m/<(MT|_)_trans/i) {
1317        $text = $app->translate_templatized($text);
1318    }
1319
1320    $obj->text($text);
1321
1322    # update text heights if necessary
1323    if ( my $perms = $app->permissions ) {
1324        my $prefs = $perms->template_prefs || '';
1325        my $text_height = $app->param('text_height');
1326        if ( defined $text_height ) {
1327            my ($pref_text_height) = $prefs =~ m/\btext:(\d+)\b/;
1328            $pref_text_height ||= 0;
1329            if ( $text_height != $pref_text_height ) {
1330                if ( $prefs =~ m/\btext\b/ ) {
1331                    $prefs =~ s/\btext(:\d+)\b/text:$text_height/;
1332                }
1333                else {
1334                    $prefs = 'text:' . $text_height . ',' . $prefs;
1335                }
1336            }
1337        }
1338
1339        if ( $prefs ne ( $perms->template_prefs || '' ) ) {
1340            $perms->template_prefs($prefs);
1341            $perms->save;
1342        }
1343    }
1344
1345    # module caching
1346    $obj->include_with_ssi( $app->param('include_with_ssi') ? 1 : 0 );
1347    $obj->cache_path( $app->param('cache_path'));
1348    my $cache_expire_type = defined $app->param('cache_expire_type')
1349      ? $app->param('cache_expire_type')
1350      : '0';
1351    $obj->cache_expire_type($cache_expire_type);
1352    my $period   = $app->param('cache_expire_period');
1353    my $interval = $app->param('cache_expire_interval');
1354    my $sec      = _get_interval( $period, $interval );
1355    $obj->cache_expire_interval($sec) if defined $sec;
1356    my $q = $app->param;
1357    my @events;
1358
1359    foreach my $name ( $q->param('cache_expire_event') ) {
1360        push @events, $name;
1361    }
1362    $obj->cache_expire_event( join ',', @events ) if $#events >= 0;
1363    if ( $cache_expire_type == 1 ) {
1364        return $eh->error(
1365            $app->translate("You should not be able to enter 0 as the time.") )
1366          if $interval == 0;
1367    }
1368    elsif ( $cache_expire_type == 2 ) {
1369        return $eh->error(
1370            $app->translate("You must select at least one event checkbox.") )
1371          if !@events;
1372    }
1373
1374    require MT::PublishOption;
1375    my $build_type = $app->param('build_type');
1376
1377    if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1378        my $period   = $app->param('schedule_period');
1379        my $interval = $app->param('schedule_interval');
1380        my $sec      = _get_interval( $period, $interval );
1381        $obj->build_interval($sec);
1382    }
1383    my $rebuild_me = 1;
1384    if (   $build_type == MT::PublishOption::DISABLED()
1385        || $build_type == MT::PublishOption::MANUALLY() )
1386    {
1387        $rebuild_me = 0;
1388    }
1389    $obj->rebuild_me($rebuild_me);
1390    1;
1391}
1392
1393sub post_save {
1394    my $eh = shift;
1395    my ( $app, $obj, $original ) = @_;
1396
1397    my $sess_obj = $app->autosave_session_obj;
1398    $sess_obj->remove if $sess_obj;
1399
1400    my $dynamic = 0;
1401    my $q = $app->param;
1402    my $type = $q->param('type');
1403    # FIXME: enumeration of types
1404    if ( $type eq 'custom'
1405      || $type eq 'index'
1406      || $type eq 'widget'
1407      || $type eq 'widgetset' )
1408    {
1409        $dynamic = $obj->build_dynamic;
1410    }
1411    else
1412    {
1413        # archive template specific post_save tasks
1414        require MT::TemplateMap;
1415        my @p = $q->param;
1416        my %static_maps;
1417        for my $p (@p) {
1418            my $map;
1419            if ( $p =~ /^archive_tmpl_preferred_(\w+)_(\d+)$/ ) {
1420                my $at     = $1;
1421                my $map_id = $2;
1422                $map    = MT::TemplateMap->load($map_id)
1423                    or next;
1424                $map->prefer( $q->param($p) );    # prefer method saves in itself
1425            }
1426            elsif ( $p =~ /^archive_file_tmpl_(\d+)$/ ) {
1427                my $map_id = $1;
1428                $map    = MT::TemplateMap->load($map_id)
1429                    or next;
1430                my $file_template = $q->param($p);
1431                my $build_type_1  = $q->param("map_build_type_$map_id");
1432                # Populate maps whose build type is dynamic
1433                # and file template are changed
1434                $static_maps{ $map->id } = 1
1435                    if ( ( $file_template ne $map->file_template )
1436                      && ( MT::PublishOption::DYNAMIC() eq $build_type_1 ) );
1437                $map->file_template( $file_template );
1438                $map->save;
1439            }
1440            elsif ( $p =~ /^map_build_type_(\d+)$/ ) {
1441                my $map_id     = $1;
1442                $map        = MT::TemplateMap->load($map_id)
1443                    or next;
1444                my $build_type = $q->param($p);
1445                require MT::PublishOption;
1446                # Populate maps that are changed from static to dynamic
1447                # This should capture new map as well
1448                $static_maps{ $map->id } = 1
1449                    if ( ( $build_type ne $map->build_type )
1450                      && ( MT::PublishOption::DYNAMIC() eq $build_type ) );
1451                $map->build_type($build_type);
1452                if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1453                    my $period   = $q->param( 'map_schedule_period_' . $map_id );
1454                    my $interval = $q->param( 'map_schedule_interval_' . $map_id );
1455                    my $sec      = _get_interval( $period, $interval );
1456                    $map->build_interval($sec);
1457                }
1458                $map->save;
1459            }
1460            if ( !$dynamic
1461              && $map && $map->build_type == MT::PublishOption::DYNAMIC() )
1462            {
1463                $dynamic = 1;
1464            }
1465        }
1466        $app->{static_dynamic_maps} = %static_maps ? [ keys %static_maps ] : 0;
1467    }
1468
1469    if ( !$original->id ) {
1470        $app->log(
1471            {
1472                message => $app->translate(
1473                    "Template '[_1]' (ID:[_2]) created by '[_3]'",
1474                    $obj->name, $obj->id, $app->user->name
1475                ),
1476                level    => MT::Log::INFO(),
1477                class    => 'template',
1478                category => 'new',
1479            }
1480        );
1481    }
1482
1483    if ( $dynamic ) {
1484        if ( $obj->type eq 'index' ) {
1485            $app->rebuild_indexes(
1486                BlogID   => $obj->blog_id,
1487                Template => $obj,
1488                NoStatic => 1,
1489            ) or return $app->publish_error();    # XXXX
1490        }
1491        if ( my $blog = $app->blog ) {
1492            require MT::CMS::Blog;
1493            my ( $path, $url );
1494            if ( $obj->type eq 'index' ) {
1495                $path = $blog->site_path;
1496                $url = $blog->site_url;
1497            }
1498            else {
1499                # must be archive since other types can't be dynamic
1500                if ( $path = $blog->archive_path ) {
1501                    $url = $blog->archive_url;
1502                }
1503                else {
1504                    $path = $blog->site_path;
1505                    $url = $blog->site_url;
1506                }
1507            }
1508            # specific arguments so not to overwrite mtview and htaccess
1509            MT::CMS::Blog::prepare_dynamic_publishing(
1510                $eh, 
1511                $blog,
1512                undef,
1513                undef,
1514                $path,
1515                $url
1516            );
1517        }
1518    }
1519    1;
1520}
1521
1522sub post_delete {
1523    my ( $eh, $app, $obj ) = @_;
1524
1525    $app->log(
1526        {
1527            message => $app->translate(
1528                "Template '[_1]' (ID:[_2]) deleted by '[_3]'",
1529                $obj->name, $obj->id, $app->user->name
1530            ),
1531            level    => MT::Log::INFO(),
1532            class    => 'system',
1533            category => 'delete'
1534        }
1535    );
1536}
1537
1538sub build_template_table {
1539    my $app = shift;
1540    my (%args) = @_;
1541
1542    my $perms     = $app->permissions;
1543    my $list_pref = $app->list_pref('template');
1544    my $limit     = $args{limit};
1545    my $param     = $args{param} || {};
1546    my $iter;
1547    if ( $args{load_args} ) {
1548        my $class = $app->model('template');
1549        $iter = $class->load_iter( @{ $args{load_args} } );
1550    }
1551    elsif ( $args{iter} ) {
1552        $iter = $args{iter};
1553    }
1554    elsif ( $args{items} ) {
1555        $iter = sub { pop @{ $args{items} } };
1556        $limit = scalar @{ $args{items} };
1557    }
1558    return [] unless $iter;
1559
1560    my @data;
1561    my $i;
1562    my %blogs;
1563    while ( my $tmpl = $iter->() ) {
1564        my $blog = $blogs{ $tmpl->blog_id } ||=
1565          MT::Blog->load( $tmpl->blog_id ) if $tmpl->blog_id;
1566
1567        my $row = $tmpl->column_values;
1568        $row->{name} = '' if !defined $row->{name};
1569        $row->{name} =~ s/^\s+|\s+$//g;
1570        $row->{name} = "(" . $app->translate("No Name") . ")"
1571          if $row->{name} eq '';
1572        my $published_url = $tmpl->published_url;
1573        $row->{published_url} = $published_url if $published_url;
1574        $row->{use_cache} = ( ($tmpl->cache_expire_type || 0) != 0 )  ? 1 : 0;
1575
1576        # FIXME: enumeration of types
1577        $row->{can_delete} = 1
1578          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1579        if ($blog) {
1580            $row->{weblog_name} = $blog->name;
1581        }
1582        elsif ($tmpl->blog_id) {
1583            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1584        }
1585        else {
1586            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1587        }
1588        $row->{object} = $tmpl;
1589        push @data, $row;
1590        last if defined($limit) && (@data > $limit);
1591    }
1592    return [] unless @data;
1593
1594    $param->{template_table}[0]              = {%$list_pref};
1595    $param->{template_table}[0]{object_loop} = \@data;
1596    $param->{template_table}[0]{object_type} = 'template';
1597    $app->load_list_actions( 'template', $param );
1598    $param->{object_loop} = \@data;
1599    \@data;
1600}
1601
1602sub dialog_publishing_profile {
1603    my $app = shift;
1604    $app->validate_magic or return;
1605
1606    my $blog = $app->blog;
1607    $app->assert( $blog ) or return;
1608
1609    # permission check
1610    my $perms = $app->permissions;
1611    return $app->errtrans("Permission denied.")
1612        unless $app->user->is_superuser ||
1613            $perms->can_administer_blog ||
1614            $perms->can_edit_templates;
1615
1616    my $param = {};
1617    $param->{dynamicity} = $blog->custom_dynamic_templates || 'none';
1618    $param->{screen_id} = "publishing-profile-dialog";
1619    $param->{return_args} = $app->param('return_args');
1620
1621    $app->build_page('dialog/publishing_profile.tmpl',
1622        $param);
1623}
1624
1625sub dialog_refresh_templates {
1626    my $app = shift;
1627    $app->validate_magic or return;
1628
1629    # permission check
1630    my $perms = $app->permissions;
1631    return $app->errtrans("Permission denied.")
1632        unless $app->user->is_superuser ||
1633            $perms->can_administer_blog ||
1634            $perms->can_edit_templates;
1635
1636    my $param = {};
1637    my $blog = $app->blog;
1638    $param->{return_args} = $app->param('return_args');
1639
1640    if ($blog) {
1641        $param->{blog_id} = $blog->id;
1642
1643        my $sets = $app->registry("template_sets");
1644        $sets->{$_}{key} = $_ for keys %$sets;
1645        $sets = $app->filter_conditional_list([ values %$sets ]);
1646
1647        no warnings; # some sets may not define an order
1648        @$sets = sort { $a->{order} <=> $b->{order} } @$sets;
1649        $param->{'template_set_loop'} = $sets;
1650
1651        my $existing_set = $blog->template_set || 'mt_blog';
1652        foreach (@$sets) {
1653            if ($_->{key} eq $existing_set) {
1654                $_->{selected} = 1;
1655            }
1656        }
1657        $param->{'template_set_index'} = $#$sets;
1658        $param->{'template_set_count'} = scalar @$sets;
1659
1660        $param->{template_sets} = $sets;
1661        $param->{screen_id} = "refresh-templates-dialog";
1662    }
1663
1664    # load template sets
1665    $app->build_page('dialog/refresh_templates.tmpl',
1666        $param);
1667}
1668
1669sub refresh_all_templates {
1670    my ($app) = @_;
1671
1672    my $backup = 0;
1673    if ($app->param('backup')) {
1674        # refresh templates dialog uses a 'backup' field
1675        $backup = 1;
1676    }
1677
1678    my $template_set = $app->param('template_set');
1679    my $refresh_type = $app->param('refresh_type') || 'refresh';
1680
1681    my $t = time;
1682
1683    my @id;
1684    if ($app->param('blog_id')) {
1685        @id = ( scalar $app->param('blog_id') );
1686    }
1687    else {
1688        @id = $app->param('id');
1689        if (! @id) {
1690            # refresh global templates
1691            @id = ( 0 );
1692        }
1693    }
1694
1695    require MT::Template;
1696    require MT::DefaultTemplates;
1697    require MT::Blog;
1698    require MT::Permission;
1699    require MT::Util;
1700
1701    my @blog_ids;
1702    my $refreshed;
1703    foreach my $blog_id (@id) {
1704        my $blog;
1705        if ($blog_id) {
1706            $blog = MT::Blog->load($blog_id);
1707            next unless $blog;
1708        }
1709        if ( !$app->user->is_superuser() ) {
1710            my $perms = MT::Permission->load(
1711                { blog_id => $blog_id, author_id => $app->user->id } );
1712            if (
1713                !$perms
1714                || (   !$perms->can_edit_templates()
1715                    && !$perms->can_administer_blog() )
1716              )
1717            {
1718                push @blog_ids, $blog->id;
1719                next;
1720            }
1721        }
1722
1723        my $tmpl_list;
1724        if ($blog_id) {
1725
1726            if ($refresh_type eq 'clean') {
1727                # the user wants to back up all templates and
1728                # install the new ones
1729
1730                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1731                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1732                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1733
1734                my $tmpl_iter = MT::Template->load_iter({
1735                    blog_id => $blog_id,
1736                    type => { not => 'backup' },
1737                });
1738
1739                while (my $tmpl = $tmpl_iter->()) {
1740                    if ($backup) {
1741                        # zap all template maps
1742                        require MT::TemplateMap;
1743                        MT::TemplateMap->remove({
1744                            template_id => $tmpl->id,
1745                        });
1746                        $tmpl->type('backup');
1747                        $tmpl->name(
1748                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1749                        $tmpl->identifier(undef);
1750                        $tmpl->rebuild_me(0);
1751                        $tmpl->linked_file(undef);
1752                        $tmpl->outfile('');
1753                        $tmpl->save;
1754                    } else {
1755                        $tmpl->remove;
1756                    }
1757                }
1758
1759                # This also creates our template mappings
1760                $blog->create_default_templates( $template_set ||
1761                    $blog->template_set || 'mt_blog' );
1762
1763                if ($template_set) {
1764                    $blog->template_set( $template_set );
1765                    $blog->save;
1766                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1767                }
1768
1769                next;
1770            }
1771
1772            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1773        }
1774        else {
1775            $tmpl_list = MT::DefaultTemplates->templates();
1776        }
1777
1778        foreach my $val (@$tmpl_list) {
1779            if ($blog_id) {
1780                # when refreshing blog templates,
1781                # skip over global templates which
1782                # specify a blog_id of 0...
1783                next if $val->{global};
1784            }
1785            else {
1786                next unless exists $val->{global};
1787            }
1788
1789            if ( !$val->{orig_name} ) {
1790                $val->{orig_name} = $val->{name};
1791                $val->{text}      = $app->translate_templatized( $val->{text} );
1792            }
1793
1794            my $orig_name = $val->{orig_name};
1795
1796            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1797            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1798              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1799
1800            my $terms = {};
1801            $terms->{blog_id} = $blog_id;
1802            $terms->{type} = $val->{type};
1803            if ( $val->{type} =~
1804                m/^(archive|individual|page|category|index|custom|widget|widgetset)$/ )
1805            {
1806                $terms->{name} = $val->{name};
1807            }
1808            else {
1809                $terms->{identifier} = $val->{identifier};
1810            }
1811
1812            # this should only return 1 template; we're searching
1813            # within a given blog for a specific type of template (for
1814            # "system" templates; or for a type + name, which should be
1815            # unique for that blog.
1816            my $tmpl = MT::Template->load($terms);
1817            if ($tmpl && $backup) {
1818
1819                # check for default template text...
1820                # if it is a default template, then outright replace it
1821                my $text = $tmpl->text;
1822                $text =~ s/\s+//g;
1823
1824                my $def_text = $val->{text};
1825                $def_text =~ s/\s+//g;
1826
1827                # if it has been customized, back it up to a new tmpl record
1828                if ($def_text ne $text) {
1829                    my $backup = $tmpl->clone;
1830                    delete $backup->{column_values}
1831                      ->{id};    # make sure we don't overwrite original
1832                    delete $backup->{changed_cols}->{id};
1833                    $backup->name(
1834                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1835                    $backup->type('backup');
1836                    # if ( $backup->type !~
1837                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1838                    # {
1839                    #     $backup->type('custom')
1840                    #       ;      # system templates can't be created
1841                    # }
1842                    $backup->outfile('');
1843                    $backup->linked_file( $tmpl->linked_file );
1844                    $backup->identifier(undef);
1845                    $backup->rebuild_me(0);
1846                    $backup->build_dynamic(0);
1847                    $backup->save;
1848                }
1849            }
1850            if ($tmpl) {
1851                # we found that the previous template had not been
1852                # altered, so replace it with new default template...
1853                if ( ( 'widgetset' eq $val->{type} )
1854                  && ( exists $val->{widgets} ) ) {
1855                    my $modulesets = delete $val->{widgets};
1856                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1857                }
1858                $tmpl->text( $val->{text} );
1859                $tmpl->identifier( $val->{identifier} );
1860                $tmpl->type( $val->{type} )
1861                  ; # fixes mismatch of types for cases like "archive" => "individual"
1862                $tmpl->linked_file('');
1863                $tmpl->save;
1864            }
1865            else {
1866                # create this one...
1867                my $tmpl = new MT::Template;
1868                if ( ( 'widgetset' eq $val->{type} )
1869                  && ( exists $val->{widgets} ) ) {
1870                    my $modulesets = delete $val->{widgets};
1871                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1872                }
1873                $tmpl->build_dynamic(0);
1874                $tmpl->set_values(
1875                    {
1876                        text       => $val->{text},
1877                        name       => $val->{name},
1878                        type       => $val->{type},
1879                        identifier => $val->{identifier},
1880                        outfile    => $val->{outfile},
1881                        rebuild_me => $val->{rebuild_me},
1882                    }
1883                );
1884                $tmpl->blog_id($blog_id);
1885                $tmpl->save
1886                  or return $app->error(
1887                        $app->translate("Error creating new template: ")
1888                      . $tmpl->errstr );
1889            }
1890        }
1891        $refreshed = 1;
1892    }
1893    if (@blog_ids) {
1894        $app->add_return_arg( 'error_id' => join( ',', @blog_ids ) );
1895    }
1896    $app->add_return_arg( 'refreshed' => 1 ) if $refreshed;
1897    $app->call_return;
1898}
1899
1900sub refresh_individual_templates {
1901    my ($app) = @_;
1902
1903    require MT::Util;
1904
1905    my $user = $app->user;
1906    my $perms = $app->permissions;
1907    return $app->error(
1908        $app->translate(
1909            "Permission denied.")
1910      )
1911      #TODO: system level-designer permission
1912      unless $user->is_superuser() || $user->can_edit_templates()
1913      || ( $perms
1914        && ( $perms->can_edit_templates()
1915          || $perms->can_administer_blog ) );
1916
1917    my $set;
1918    if ( my $blog_id = $app->param('blog_id') ) {
1919        my $blog = $app->model('blog')->load($blog_id)
1920            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1921        $set = $blog->template_set()
1922            if $blog;
1923    }
1924
1925    require MT::DefaultTemplates;
1926    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1927
1928    my $tmpl_types = {};
1929    my $tmpl_ids   = {};
1930    my $tmpls      = {};
1931    foreach my $tmpl (@$tmpl_list) {
1932        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1933        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1934            if $tmpl->{identifier};
1935        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1936        {
1937            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1938        }
1939        else {
1940            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1941        }
1942    }
1943
1944    my $t = time;
1945
1946    my @msg;
1947    my @id = $app->param('id');
1948    require MT::Template;
1949    foreach my $tmpl_id (@id) {
1950        my $tmpl = MT::Template->load($tmpl_id);
1951        next unless $tmpl;
1952        my $blog_id = $tmpl->blog_id;
1953
1954        # FIXME: permission check -- for this blog_id
1955
1956        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1957        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1958          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1959
1960        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1961          || $tmpl_types->{ $tmpl->type() }
1962          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1963        if ( !$val ) {
1964            push @msg,
1965              $app->translate(
1966"Skipping template '[_1]' since it appears to be a custom template.",
1967                $tmpl->name
1968              );
1969            next;
1970        }
1971
1972        my $text = $tmpl->text;
1973        $text =~ s/\s+//g;
1974
1975        my $def_text = $val->{text};
1976        $def_text =~ s/\s+//g;
1977
1978        if ($text ne $def_text) {
1979            # if it has been customized, back it up to a new tmpl record
1980            my $backup = $tmpl->clone;
1981            delete $backup->{column_values}
1982              ->{id};    # make sure we don't overwrite original
1983            delete $backup->{changed_cols}->{id};
1984            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1985            $backup->type('backup');
1986            $backup->outfile('');
1987            $backup->linked_file( $tmpl->linked_file );
1988            $backup->rebuild_me(0);
1989            $backup->build_dynamic(0);
1990            $backup->identifier(undef);
1991            $backup->save;
1992            push @msg,
1993              $app->translate(
1994    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1995                  $blog_id, $backup->id, $tmpl->name );
1996
1997            # we found that the previous template had not been
1998            # altered, so replace it with new default template...
1999            $tmpl->text( $val->{text} );
2000            $tmpl->identifier( $val->{identifier} );
2001            $tmpl->linked_file('');
2002            $tmpl->save;
2003        } else {
2004            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
2005        }
2006    }
2007    my @msg_loop;
2008    push @msg_loop, { message => $_ } foreach @msg;
2009
2010    $app->build_page( 'refresh_results.tmpl',
2011        { message_loop => \@msg_loop, return_url => $app->return_uri } );
2012}
2013
2014sub clone_templates {
2015    my ($app) = @_;
2016
2017    my $user = $app->user;
2018    my $perms = $app->permissions;
2019    return $app->error(
2020        $app->translate(
2021            "Permission denied.")
2022      )
2023      #TODO: system level-designer permission
2024      unless $user->is_superuser() || $user->can_edit_templates()
2025      || ( $perms
2026        && ( $perms->can_edit_templates()
2027          || $perms->can_administer_blog ) );
2028
2029    my @id = $app->param('id');
2030    require MT::Template;
2031    foreach my $tmpl_id (@id) {
2032        my $tmpl = MT::Template->load($tmpl_id);
2033        next unless $tmpl;
2034
2035        my $new_tmpl = $tmpl->clone({
2036            Except => {
2037                id => 1,
2038                name => 1,
2039                identifier => 1,
2040            },
2041        });
2042
2043        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
2044        my $new_name = $new_basename;
2045        my $i = 0;
2046        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
2047            $new_name = $new_basename . ' (' . ++$i . ')';
2048        }
2049
2050        $new_tmpl->name($new_name);
2051        $new_tmpl->save;
2052    }
2053
2054    $app->add_return_arg( 'saved_copied' => 1 );
2055    $app->call_return;
2056}
2057
2058sub publish_index_templates {
2059    my $app = shift;
2060    $app->validate_magic or return;
2061
2062    # permission check
2063    my $perms = $app->permissions;
2064    return $app->errtrans("Permission denied.")
2065        unless $app->user->is_superuser ||
2066            $perms->can_administer_blog ||
2067            $perms->can_rebuild;
2068
2069    my $blog = $app->blog;
2070    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
2071    TEMPLATE: for my $tmpl (@$templates) {
2072        next TEMPLATE if !defined $tmpl;
2073        next TEMPLATE if $tmpl->blog_id != $blog->id;
2074        next TEMPLATE unless $tmpl->build_type;
2075
2076        $app->rebuild_indexes(
2077            Blog     => $blog,
2078            Template => $tmpl,
2079            Force    => 1,
2080        );
2081    }
2082
2083    $app->call_return( published => 1 );
2084}
2085
2086sub publish_archive_templates {
2087    my $app = shift;
2088    $app->validate_magic or return;
2089
2090    # permission check
2091    my $perms = $app->permissions;
2092    return $app->errtrans("Permission denied.")
2093      unless $app->user->is_superuser
2094      || $perms->can_administer_blog
2095      || $perms->can_rebuild;
2096
2097    my @ids = $app->param('id');
2098    if (scalar @ids == 1) {
2099        # we also support a list of comma-delimited ids like this
2100        @ids = split /,/, $ids[0];
2101    }
2102    return $app->error($app->translate("Invalid request."))
2103        unless @ids;
2104
2105    my $tmpl_id;
2106    my %ats;
2107    require MT::TemplateMap;
2108    while (!$tmpl_id && @ids) {
2109        $tmpl_id = shift @ids;
2110        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl_id } );
2111        foreach my $map (@tmpl_maps) {
2112            next unless $map->build_type;
2113            $ats{ $map->archive_type } = 1;
2114        }
2115        undef $tmpl_id unless keys %ats;
2116    }
2117
2118    # we have a template and archive types to publish!
2119
2120    require MT::CMS::Blog;
2121    my $return_args;
2122    my $reedit = $app->param('reedit');
2123    if (@ids) {
2124        # we have more to do after this, so save the list
2125        # of remaining archive templates...
2126        $return_args = $app->uri_params(
2127            mode => 'publish_archive_templates',
2128            args => {
2129                magic_token => $app->current_magic,
2130                blog_id => scalar $app->param('blog_id'),
2131                id => join(",", @ids),
2132                reedit => $reedit,
2133            }
2134        );
2135    } else {
2136        my $mode = $reedit ? 'view' : 'list';
2137        $return_args = $app->uri_params(
2138            mode => $mode,
2139            args => {
2140                _type     => 'template',
2141                blog_id   => scalar $app->param('blog_id'),
2142                published => 1,
2143                ( $reedit ? ( saved => 1 )       : () ),
2144                ( $reedit ? ( id    => $reedit ) : () ),
2145            }
2146        );
2147    }
2148    $return_args =~ s/^\?//;
2149
2150    $app->return_args( $return_args );
2151    $app->param( 'template_id', $tmpl_id );
2152    $app->param( 'single_template', 1 ); # forces fullscreen mode
2153    $app->param( 'type', join(",", keys %ats) );
2154    return MT::CMS::Blog::start_rebuild_pages($app);
2155}
2156
2157sub save_widget {
2158    my $app = shift;
2159    my $q   = $app->param;
2160
2161    $app->validate_magic() or return;
2162    my $author = $app->user;
2163
2164    my $id = $q->param('id');
2165
2166    if ( !$author->is_superuser ) {
2167        $app->run_callbacks( 'cms_save_permission_filter.template', $app, $id )
2168          || return $app->error(
2169            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2170    }
2171
2172    my $filter_result = $app->run_callbacks( 'cms_save_filter.widgetset', $app );
2173
2174    if ( !$filter_result ) {
2175        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2176    }
2177
2178    my $class = $app->model('template');
2179    my $obj;
2180    if ( $id ) {
2181        $obj = $class->load($id)
2182            or return $app->error($app->translate("Invalid ID [_1]", $id));
2183    }
2184    else {
2185        $obj = $class->new;
2186    }
2187
2188    my $original = $obj->clone();
2189    $obj->name($q->param('name'));
2190    $obj->type('widgetset');
2191    $obj->blog_id( $q->param('blog_id') || 0 );
2192    $obj->modulesets($q->param('modules'));
2193
2194    unless (
2195        $app->run_callbacks( 'cms_pre_save.template', $app, $obj, $original ) )
2196    {
2197        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2198    }
2199
2200    $obj->save
2201      or return $app->error(
2202        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
2203
2204    $app->run_callbacks( 'cms_post_save.template', $app, $obj, $original )
2205      or return $app->error( $app->errstr() );
2206
2207    $app->redirect(
2208        $app->uri(
2209            'mode' => 'edit_widget',
2210            args =>
2211              { blog_id => $obj->blog_id, 'saved' => 1, rebuild => 1, id => $obj->id }
2212        )
2213    );
2214}
2215
2216sub edit_widget {
2217    my $app = shift;
2218    my (%opt) = @_;
2219
2220    my $q       = $app->param();
2221    my $id      = scalar($q->param('id')) || $opt{id};
2222    my $name    = scalar($q->param('name'));
2223    my $blog_id = scalar $q->param('blog_id') || 0;
2224
2225    my $tmpl_class = $app->model('template');
2226    require MT::Promise;
2227    my $obj_promise = MT::Promise::delay(
2228        sub {
2229            return $tmpl_class->load($id) || undef;
2230        }
2231    );
2232
2233    if ( !$app->user->is_superuser ) {
2234        $app->run_callbacks( 'cms_view_permission_filter.template',
2235            $app, $id, $obj_promise )
2236          || return $app->error(
2237            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2238    }
2239
2240    my $param = {
2241        blog_id      => $blog_id,
2242        search_type  => "template",
2243        search_label => MT::Template->class_label_plural,
2244        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2245        exists($opt{error}) ? ( error => $opt{error} ) : (),
2246        exists($opt{saved}) ? ( saved => $opt{saved} ) : (),
2247        $id
2248          ? ( id => $id )
2249          : $name
2250            ? ( name => $name )
2251            : (),
2252    };
2253    if ($blog_id) {
2254        my $blog = $app->blog;
2255        # include_system/include_cache are only applicable
2256        # to blog-level templates
2257        $param->{include_system} = $blog->include_system;
2258        $param->{include_cache} = $blog->include_cache;
2259        $param->{include_with_ssi}      = 0;
2260        $param->{cache_path}            = '';
2261        $param->{cache_enabled}         = 0;
2262        $param->{cache_expire_type}     = 0;
2263        $param->{cache_expire_period}   = '';
2264        $param->{cache_expire_interval} = 0;
2265        $param->{ssi_type} = uc $blog->include_system;
2266    }
2267   
2268    my $iter = $tmpl_class->load_iter(
2269        { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2270        { sort => 'name', direction => 'ascend' }
2271    );
2272
2273    my %all_widgets;
2274    while (my $m = $iter->()) {
2275        next unless $m;
2276        $all_widgets{ $m->id }{name} = $m->name;
2277        $all_widgets{ $m->id }{blog_id} = $m->blog_id;
2278    }
2279
2280    my @inst_modules;
2281    my $wtmpl;
2282    if ( $id ) {
2283        $wtmpl = $obj_promise->force()
2284          or return $app->error(
2285            $app->translate(
2286                "Load failed: [_1]",
2287                $tmpl_class->errstr || $app->translate("(no reason given)")
2288            )
2289          );
2290        return $app->return_to_dashboard( redirect => 1 )
2291            if $wtmpl->blog_id ne $blog_id;
2292        $param->{name} = $wtmpl->name;
2293        $param->{include_with_ssi} = $wtmpl->include_with_ssi
2294          if defined $wtmpl->include_with_ssi;
2295        $param->{cache_path}       = $wtmpl->cache_path
2296          if defined $wtmpl->cache_path;
2297        $param->{cache_expire_type} = $wtmpl->cache_expire_type
2298          if defined $wtmpl->cache_expire_type;
2299        my ( $period, $interval ) =
2300          _get_schedule( $wtmpl->cache_expire_interval );
2301        $param->{cache_expire_period}   = $period   if defined $period;
2302        $param->{cache_expire_interval} = $interval if defined $interval;
2303        my @events = split ',', $wtmpl->cache_expire_event;
2304        foreach my $name (@events) {
2305            $param->{ 'cache_expire_event_' . $name } = 1;
2306        }
2307        my $modulesets = $wtmpl->modulesets;
2308        if ( $modulesets ) {
2309            my @modules = split ',', $modulesets;
2310            foreach my $mid ( @modules ) {
2311                push @inst_modules, {
2312                    id => $mid,
2313                    name => $all_widgets{$mid}{name},
2314                    blog_id => $all_widgets{$mid}{blog_id},
2315                };
2316                delete $all_widgets{$mid};
2317            }
2318        }
2319    }
2320    $param->{installed} = \@inst_modules if @inst_modules;
2321    my @avail_modules = map { {
2322        id => $_, name => $all_widgets{$_}{name}, blog_id => $all_widgets{$_}{blog_id}
2323    } } keys %all_widgets;
2324    $param->{available} = \@avail_modules;
2325
2326    my $res = $app->run_callbacks('cms_edit.widgetset', $app, $id, $wtmpl, $param);
2327    if (!$res) {
2328        return $app->error($app->callback_errstr());
2329    }
2330
2331    $app->load_tmpl('edit_widget.tmpl', $param);
2332}
2333
2334sub list_widget {
2335    my $app = shift;
2336    my (%opt) = @_;
2337    my $q = $app->param;
2338
2339    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
2340    return $app->return_to_dashboard( redirect => 1 )
2341      unless $perms || $app->user->is_superuser;
2342    if ( $perms && !$perms->can_edit_templates ) {
2343        return $app->return_to_dashboard( permission => 1 );
2344    }
2345    my $blog_id = $q->param('blog_id') || 0;
2346
2347    my $widget_loop = &build_template_table( $app,
2348        load_args => [ 
2349            { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2350            { sort => 'name', direction => 'ascend' }
2351        ],
2352    );
2353
2354    my $iter = $app->model('template')->load_iter(
2355        { type => 'widgetset', blog_id => $blog_id ? $blog_id : 0 },
2356        { sort => 'name', direction => 'ascend' }
2357    );
2358    my @widgetmanagers;
2359    while ( my $widgetset = $iter->() ) {
2360        next unless $widgetset;
2361        my $ws = { 
2362            id => $widgetset->id,
2363            widgetmanager => $widgetset->name,
2364        };
2365        if ( my $modulesets = $widgetset->modulesets ) {
2366            $ws->{widgets} = $modulesets;
2367            my @names;
2368            foreach my $module ( split ',', $modulesets ) { 
2369                my ( $widget ) = grep { $_->{id} eq $module } @$widget_loop;
2370                push @names, $widget->{name} if $widget;
2371            }
2372            $ws->{names} = join(', ', @names) if @names;
2373        }
2374        push @widgetmanagers, $ws;
2375    }
2376
2377    my @widget_loop;
2378    if ( $blog_id ) {
2379        # Remove system level widgets from the listing
2380        @widget_loop = grep { $_->{</