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

Revision 2717, 87.9 kB (checked in by fumiakiy, 17 months ago)

Copy data structure for customized view so the data won't be re-used in a persistent environment. BugId:80513

  • 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
1650        my $existing_set = $blog->template_set || 'mt_blog';
1651        my @sets;
1652        foreach (@$sets) {
1653            my %set = %{$_};
1654            if ($set{key} eq $existing_set) {
1655                $set{selected} = 1;
1656            }
1657            push @sets, \%set;
1658        }
1659        $param->{'template_set_index'} = $#sets;
1660        $param->{'template_set_count'} = scalar @sets;
1661        $param->{'template_set_loop'} = \@sets;
1662
1663        $param->{screen_id} = "refresh-templates-dialog";
1664    }
1665
1666    # load template sets
1667    $app->build_page('dialog/refresh_templates.tmpl',
1668        $param);
1669}
1670
1671sub refresh_all_templates {
1672    my ($app) = @_;
1673
1674    my $backup = 0;
1675    if ($app->param('backup')) {
1676        # refresh templates dialog uses a 'backup' field
1677        $backup = 1;
1678    }
1679
1680    my $template_set = $app->param('template_set');
1681    my $refresh_type = $app->param('refresh_type') || 'refresh';
1682
1683    my $t = time;
1684
1685    my @id;
1686    if ($app->param('blog_id')) {
1687        @id = ( scalar $app->param('blog_id') );
1688    }
1689    else {
1690        @id = $app->param('id');
1691        if (! @id) {
1692            # refresh global templates
1693            @id = ( 0 );
1694        }
1695    }
1696
1697    require MT::Template;
1698    require MT::DefaultTemplates;
1699    require MT::Blog;
1700    require MT::Permission;
1701    require MT::Util;
1702
1703    my @blog_ids;
1704    my $refreshed;
1705    foreach my $blog_id (@id) {
1706        my $blog;
1707        if ($blog_id) {
1708            $blog = MT::Blog->load($blog_id);
1709            next unless $blog;
1710        }
1711        if ( !$app->user->is_superuser() ) {
1712            my $perms = MT::Permission->load(
1713                { blog_id => $blog_id, author_id => $app->user->id } );
1714            if (
1715                !$perms
1716                || (   !$perms->can_edit_templates()
1717                    && !$perms->can_administer_blog() )
1718              )
1719            {
1720                push @blog_ids, $blog->id;
1721                next;
1722            }
1723        }
1724
1725        my $tmpl_list;
1726        if ($blog_id) {
1727
1728            if ($refresh_type eq 'clean') {
1729                # the user wants to back up all templates and
1730                # install the new ones
1731
1732                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1733                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1734                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1735
1736                my $tmpl_iter = MT::Template->load_iter({
1737                    blog_id => $blog_id,
1738                    type => { not => 'backup' },
1739                });
1740
1741                while (my $tmpl = $tmpl_iter->()) {
1742                    if ($backup) {
1743                        # zap all template maps
1744                        require MT::TemplateMap;
1745                        MT::TemplateMap->remove({
1746                            template_id => $tmpl->id,
1747                        });
1748                        $tmpl->type('backup');
1749                        $tmpl->name(
1750                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1751                        $tmpl->identifier(undef);
1752                        $tmpl->rebuild_me(0);
1753                        $tmpl->linked_file(undef);
1754                        $tmpl->outfile('');
1755                        $tmpl->save;
1756                    } else {
1757                        $tmpl->remove;
1758                    }
1759                }
1760
1761                # This also creates our template mappings
1762                $blog->create_default_templates( $template_set ||
1763                    $blog->template_set || 'mt_blog' );
1764
1765                if ($template_set) {
1766                    $blog->template_set( $template_set );
1767                    $blog->save;
1768                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1769                }
1770
1771                next;
1772            }
1773
1774            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1775        }
1776        else {
1777            $tmpl_list = MT::DefaultTemplates->templates();
1778        }
1779
1780        foreach my $val (@$tmpl_list) {
1781            if ($blog_id) {
1782                # when refreshing blog templates,
1783                # skip over global templates which
1784                # specify a blog_id of 0...
1785                next if $val->{global};
1786            }
1787            else {
1788                next unless exists $val->{global};
1789            }
1790
1791            if ( !$val->{orig_name} ) {
1792                $val->{orig_name} = $val->{name};
1793                $val->{text}      = $app->translate_templatized( $val->{text} );
1794            }
1795
1796            my $orig_name = $val->{orig_name};
1797
1798            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1799            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1800              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1801
1802            my $terms = {};
1803            $terms->{blog_id} = $blog_id;
1804            $terms->{type} = $val->{type};
1805            if ( $val->{type} =~
1806                m/^(archive|individual|page|category|index|custom|widget|widgetset)$/ )
1807            {
1808                $terms->{name} = $val->{name};
1809            }
1810            else {
1811                $terms->{identifier} = $val->{identifier};
1812            }
1813
1814            # this should only return 1 template; we're searching
1815            # within a given blog for a specific type of template (for
1816            # "system" templates; or for a type + name, which should be
1817            # unique for that blog.
1818            my $tmpl = MT::Template->load($terms);
1819            if ($tmpl && $backup) {
1820
1821                # check for default template text...
1822                # if it is a default template, then outright replace it
1823                my $text = $tmpl->text;
1824                $text =~ s/\s+//g;
1825
1826                my $def_text = $val->{text};
1827                $def_text =~ s/\s+//g;
1828
1829                # if it has been customized, back it up to a new tmpl record
1830                if ($def_text ne $text) {
1831                    my $backup = $tmpl->clone;
1832                    delete $backup->{column_values}
1833                      ->{id};    # make sure we don't overwrite original
1834                    delete $backup->{changed_cols}->{id};
1835                    $backup->name(
1836                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1837                    $backup->type('backup');
1838                    # if ( $backup->type !~
1839                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1840                    # {
1841                    #     $backup->type('custom')
1842                    #       ;      # system templates can't be created
1843                    # }
1844                    $backup->outfile('');
1845                    $backup->linked_file( $tmpl->linked_file );
1846                    $backup->identifier(undef);
1847                    $backup->rebuild_me(0);
1848                    $backup->build_dynamic(0);
1849                    $backup->save;
1850                }
1851            }
1852            if ($tmpl) {
1853                # we found that the previous template had not been
1854                # altered, so replace it with new default template...
1855                if ( ( 'widgetset' eq $val->{type} )
1856                  && ( exists $val->{widgets} ) ) {
1857                    my $modulesets = delete $val->{widgets};
1858                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1859                }
1860                $tmpl->text( $val->{text} );
1861                $tmpl->identifier( $val->{identifier} );
1862                $tmpl->type( $val->{type} )
1863                  ; # fixes mismatch of types for cases like "archive" => "individual"
1864                $tmpl->linked_file('');
1865                $tmpl->save;
1866            }
1867            else {
1868                # create this one...
1869                my $tmpl = new MT::Template;
1870                if ( ( 'widgetset' eq $val->{type} )
1871                  && ( exists $val->{widgets} ) ) {
1872                    my $modulesets = delete $val->{widgets};
1873                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1874                }
1875                $tmpl->build_dynamic(0);
1876                $tmpl->set_values(
1877                    {
1878                        text       => $val->{text},
1879                        name       => $val->{name},
1880                        type       => $val->{type},
1881                        identifier => $val->{identifier},
1882                        outfile    => $val->{outfile},
1883                        rebuild_me => $val->{rebuild_me},
1884                    }
1885                );
1886                $tmpl->blog_id($blog_id);
1887                $tmpl->save
1888                  or return $app->error(
1889                        $app->translate("Error creating new template: ")
1890                      . $tmpl->errstr );
1891            }
1892        }
1893        $refreshed = 1;
1894    }
1895    if (@blog_ids) {
1896        $app->add_return_arg( 'error_id' => join( ',', @blog_ids ) );
1897    }
1898    $app->add_return_arg( 'refreshed' => 1 ) if $refreshed;
1899    $app->call_return;
1900}
1901
1902sub refresh_individual_templates {
1903    my ($app) = @_;
1904
1905    require MT::Util;
1906
1907    my $user = $app->user;
1908    my $perms = $app->permissions;
1909    return $app->error(
1910        $app->translate(
1911            "Permission denied.")
1912      )
1913      #TODO: system level-designer permission
1914      unless $user->is_superuser() || $user->can_edit_templates()
1915      || ( $perms
1916        && ( $perms->can_edit_templates()
1917          || $perms->can_administer_blog ) );
1918
1919    my $set;
1920    if ( my $blog_id = $app->param('blog_id') ) {
1921        my $blog = $app->model('blog')->load($blog_id)
1922            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1923        $set = $blog->template_set()
1924            if $blog;
1925    }
1926
1927    require MT::DefaultTemplates;
1928    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1929
1930    my $tmpl_types = {};
1931    my $tmpl_ids   = {};
1932    my $tmpls      = {};
1933    foreach my $tmpl (@$tmpl_list) {
1934        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1935        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1936            if $tmpl->{identifier};
1937        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1938        {
1939            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1940        }
1941        else {
1942            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1943        }
1944    }
1945
1946    my $t = time;
1947
1948    my @msg;
1949    my @id = $app->param('id');
1950    require MT::Template;
1951    foreach my $tmpl_id (@id) {
1952        my $tmpl = MT::Template->load($tmpl_id);
1953        next unless $tmpl;
1954        my $blog_id = $tmpl->blog_id;
1955
1956        # FIXME: permission check -- for this blog_id
1957
1958        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1959        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1960          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1961
1962        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1963          || $tmpl_types->{ $tmpl->type() }
1964          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1965        if ( !$val ) {
1966            push @msg,
1967              $app->translate(
1968"Skipping template '[_1]' since it appears to be a custom template.",
1969                $tmpl->name
1970              );
1971            next;
1972        }
1973
1974        my $text = $tmpl->text;
1975        $text =~ s/\s+//g;
1976
1977        my $def_text = $val->{text};
1978        $def_text =~ s/\s+//g;
1979
1980        if ($text ne $def_text) {
1981            # if it has been customized, back it up to a new tmpl record
1982            my $backup = $tmpl->clone;
1983            delete $backup->{column_values}
1984              ->{id};    # make sure we don't overwrite original
1985            delete $backup->{changed_cols}->{id};
1986            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1987            $backup->type('backup');
1988            $backup->outfile('');
1989            $backup->linked_file( $tmpl->linked_file );
1990            $backup->rebuild_me(0);
1991            $backup->build_dynamic(0);
1992            $backup->identifier(undef);
1993            $backup->save;
1994            push @msg,
1995              $app->translate(
1996    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1997                  $blog_id, $backup->id, $tmpl->name );
1998
1999            # we found that the previous template had not been
2000            # altered, so replace it with new default template...
2001            $tmpl->text( $val->{text} );
2002            $tmpl->identifier( $val->{identifier} );
2003            $tmpl->linked_file('');
2004            $tmpl->save;
2005        } else {
2006            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
2007        }
2008    }
2009    my @msg_loop;
2010    push @msg_loop, { message => $_ } foreach @msg;
2011
2012    $app->build_page( 'refresh_results.tmpl',
2013        { message_loop => \@msg_loop, return_url => $app->return_uri } );
2014}
2015
2016sub clone_templates {
2017    my ($app) = @_;
2018
2019    my $user = $app->user;
2020    my $perms = $app->permissions;
2021    return $app->error(
2022        $app->translate(
2023            "Permission denied.")
2024      )
2025      #TODO: system level-designer permission
2026      unless $user->is_superuser() || $user->can_edit_templates()
2027      || ( $perms
2028        && ( $perms->can_edit_templates()
2029          || $perms->can_administer_blog ) );
2030
2031    my @id = $app->param('id');
2032    require MT::Template;
2033    foreach my $tmpl_id (@id) {
2034        my $tmpl = MT::Template->load($tmpl_id);
2035        next unless $tmpl;
2036
2037        my $new_tmpl = $tmpl->clone({
2038            Except => {
2039                id => 1,
2040                name => 1,
2041                identifier => 1,
2042            },
2043        });
2044
2045        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
2046        my $new_name = $new_basename;
2047        my $i = 0;
2048        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
2049            $new_name = $new_basename . ' (' . ++$i . ')';
2050        }
2051
2052        $new_tmpl->name($new_name);
2053        $new_tmpl->save;
2054    }
2055
2056    $app->add_return_arg( 'saved_copied' => 1 );
2057    $app->call_return;
2058}
2059
2060sub publish_index_templates {
2061    my $app = shift;
2062    $app->validate_magic or return;
2063
2064    # permission check
2065    my $perms = $app->permissions;
2066    return $app->errtrans("Permission denied.")
2067        unless $app->user->is_superuser ||
2068            $perms->can_administer_blog ||
2069            $perms->can_rebuild;
2070
2071    my $blog = $app->blog;
2072    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
2073    TEMPLATE: for my $tmpl (@$templates) {
2074        next TEMPLATE if !defined $tmpl;
2075        next TEMPLATE if $tmpl->blog_id != $blog->id;
2076        next TEMPLATE unless $tmpl->build_type;
2077
2078        $app->rebuild_indexes(
2079            Blog     => $blog,
2080            Template => $tmpl,
2081            Force    => 1,
2082        );
2083    }
2084
2085    $app->call_return( published => 1 );
2086}
2087
2088sub publish_archive_templates {
2089    my $app = shift;
2090    $app->validate_magic or return;
2091
2092    # permission check
2093    my $perms = $app->permissions;
2094    return $app->errtrans("Permission denied.")
2095      unless $app->user->is_superuser
2096      || $perms->can_administer_blog
2097      || $perms->can_rebuild;
2098
2099    my @ids = $app->param('id');
2100    if (scalar @ids == 1) {
2101        # we also support a list of comma-delimited ids like this
2102        @ids = split /,/, $ids[0];
2103    }
2104    return $app->error($app->translate("Invalid request."))
2105        unless @ids;
2106
2107    my $tmpl_id;
2108    my %ats;
2109    require MT::TemplateMap;
2110    while (!$tmpl_id && @ids) {
2111        $tmpl_id = shift @ids;
2112        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl_id } );
2113        foreach my $map (@tmpl_maps) {
2114            next unless $map->build_type;
2115            $ats{ $map->archive_type } = 1;
2116        }
2117        undef $tmpl_id unless keys %ats;
2118    }
2119
2120    # we have a template and archive types to publish!
2121
2122    require MT::CMS::Blog;
2123    my $return_args;
2124    my $reedit = $app->param('reedit');
2125    if (@ids) {
2126        # we have more to do after this, so save the list
2127        # of remaining archive templates...
2128        $return_args = $app->uri_params(
2129            mode => 'publish_archive_templates',
2130            args => {
2131                magic_token => $app->current_magic,
2132                blog_id => scalar $app->param('blog_id'),
2133                id => join(",", @ids),
2134                reedit => $reedit,
2135            }
2136        );
2137    } else {
2138        my $mode = $reedit ? 'view' : 'list';
2139        $return_args = $app->uri_params(
2140            mode => $mode,
2141            args => {
2142                _type     => 'template',
2143                blog_id   => scalar $app->param('blog_id'),
2144                published => 1,
2145                ( $reedit ? ( saved => 1 )       : () ),
2146                ( $reedit ? ( id    => $reedit ) : () ),
2147            }
2148        );
2149    }
2150    $return_args =~ s/^\?//;
2151
2152    $app->return_args( $return_args );
2153    $app->param( 'template_id', $tmpl_id );
2154    $app->param( 'single_template', 1 ); # forces fullscreen mode
2155    $app->param( 'type', join(",", keys %ats) );
2156    return MT::CMS::Blog::start_rebuild_pages($app);
2157}
2158
2159sub save_widget {
2160    my $app = shift;
2161    my $q   = $app->param;
2162
2163    $app->validate_magic() or return;
2164    my $author = $app->user;
2165
2166    my $id = $q->param('id');
2167
2168    if ( !$author->is_superuser ) {
2169        $app->run_callbacks( 'cms_save_permission_filter.template', $app, $id )
2170          || return $app->error(
2171            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2172    }
2173
2174    my $filter_result = $app->run_callbacks( 'cms_save_filter.widgetset', $app );
2175
2176    if ( !$filter_result ) {
2177        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2178    }
2179
2180    my $class = $app->model('template');
2181    my $obj;
2182    if ( $id ) {
2183        $obj = $class->load($id)
2184            or return $app->error($app->translate("Invalid ID [_1]", $id));
2185    }
2186    else {
2187        $obj = $class->new;
2188    }
2189
2190    my $original = $obj->clone();
2191    $obj->name($q->param('name'));
2192    $obj->type('widgetset');
2193    $obj->blog_id( $q->param('blog_id') || 0 );
2194    $obj->modulesets($q->param('modules'));
2195
2196    unless (
2197        $app->run_callbacks( 'cms_pre_save.template', $app, $obj, $original ) )
2198    {
2199        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2200    }
2201
2202    $obj->save
2203      or return $app->error(
2204        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
2205
2206    $app->run_callbacks( 'cms_post_save.template', $app, $obj, $original )
2207      or return $app->error( $app->errstr() );
2208
2209    $app->redirect(
2210        $app->uri(
2211            'mode' => 'edit_widget',
2212            args =>
2213              { blog_id => $obj->blog_id, 'saved' => 1, rebuild => 1, id => $obj->id }
2214        )
2215    );
2216}
2217
2218sub edit_widget {
2219    my $app = shift;
2220    my (%opt) = @_;
2221
2222    my $q       = $app->param();
2223    my $id      = scalar($q->param('id')) || $opt{id};
2224    my $name    = scalar($q->param('name'));
2225    my $blog_id = scalar $q->param('blog_id') || 0;
2226
2227    my $tmpl_class = $app->model('template');
2228    require MT::Promise;
2229    my $obj_promise = MT::Promise::delay(
2230        sub {
2231            return $tmpl_class->load($id) || undef;
2232        }
2233    );
2234
2235    if ( !$app->user->is_superuser ) {
2236        $app->run_callbacks( 'cms_view_permission_filter.template',
2237            $app, $id, $obj_promise )
2238          || return $app->error(
2239            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2240    }
2241
2242    my $param = {
2243        blog_id      => $blog_id,
2244        search_type  => "template",
2245        search_label => MT::Template->class_label_plural,
2246        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2247        exists($opt{error}) ? ( error => $opt{error} ) : (),
2248        exists($opt{saved}) ? ( saved => $opt{saved} ) : (),
2249        $id
2250          ? ( id => $id )
2251          : $name
2252            ? ( name => $name )
2253            : (),
2254    };
2255    if ($blog_id) {
2256        my $blog = $app->blog;
2257        # include_system/include_cache are only applicable
2258        # to blog-level templates
2259        $param->{include_system} = $blog->include_system;
2260        $param->{include_cache} = $blog->include_cache;
2261        $param->{include_with_ssi}      = 0;
2262        $param->{cache_path}            = '';
2263        $param->{cache_enabled}         = 0;
2264        $param->{cache_expire_type}     = 0;
2265        $param->{cache_expire_period}   = '';
2266        $param->{cache_expire_interval} = 0;
2267        $param->{ssi_type} = uc $blog->include_system;
2268    }
2269   
2270    my $iter = $tmpl_class->load_iter(
2271        { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2272        { sort => 'name', direction => 'ascend' }
2273    );
2274
2275    my %all_widgets;
2276    while (my $m = $iter->()) {
2277        next unless $m;
2278        $all_widgets{ $m->id }{name} = $m->name;
2279        $all_widgets{ $m->id }{blog_id} = $m->blog_id;
2280    }
2281
2282    my @inst_modules;
2283    my $wtmpl;
2284    if ( $id ) {
2285        $wtmpl = $obj_promise->force()
2286          or return $app->error(
2287            $app->translate(
2288                "Load failed: [_1]",
2289                $tmpl_class->errstr || $app->translate("(no reason given)")
2290            )
2291          );
2292        return $app->return_to_dashboard( redirect => 1 )
2293            if $wtmpl->blog_id ne $blog_id;
2294        $param->{name} = $wtmpl->name;
2295        $param->{include_with_ssi} = $wtmpl->include_with_ssi
2296          if defined $wtmpl->include_with_ssi;
2297        $param->{cache_path}       = $wtmpl->cache_path
2298          if defined $wtmpl->cache_path;
2299        $param->{cache_expire_type} = $wtmpl->cache_expire_type
2300          if defined $wtmpl->cache_expire_type;
2301        my ( $period, $interval ) =
2302          _get_schedule( $wtmpl->cache_expire_interval );
2303        $param->{cache_expire_period}   = $period   if defined $period;
2304        $param->{cache_expire_interval} = $interval if defined $interval;
2305        my @events = split ',', $wtmpl->cache_expire_event;
2306        foreach my $name (@events) {
2307            $param->{ 'cache_expire_event_' . $name } = 1;
2308        }
2309        my $modulesets = $wtmpl->modulesets;
2310        if ( $modulesets ) {
2311            my @modules = split ',', $modulesets;
2312            foreach my $mid ( @modules ) {
2313                push @inst_modules, {
2314                    id => $mid,
2315                    name => $all_widgets{$mid}{name},
2316                    blog_id => $all_widgets{$mid}{blog_id},
2317                };
2318                delete $all_widgets{$mid};
2319            }
2320        }
2321    }
2322    $param->{installed} = \@inst_modules if @inst_modules;
2323    my @avail_modules = map { {
2324        id => $_, name => $all_widgets{$_}{name}, blog_id => $all_widgets{$_}{blog_id}
2325    } } keys %all_widgets;
2326    $param->{available} = \@avail_modules;
2327
2328    my $res = $app->run_callbacks('cms_edit.widgetset', $app, $id, $wtmpl, $param);
2329    if (!$res) {
2330        return $app->error($app->callback_errstr());
2331    }
2332
2333    $app->load_tmpl('edit_widget.tmpl', $param);
2334}
2335
2336sub list_widget {
2337    my $app = shift;
2338    my (%opt) = @_;
2339    my $q = $app->param;
2340
2341    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
2342    return $app->return_to_dashboard( redirect => 1 )
2343      unless $perms || $app->user->is_superuser;
2344    if ( $perms && !$perms->can_edit_templates ) {
2345        return $app->return_to_dashboard( permission => 1 );
2346    }
2347    my $blog_id = $q->param('blog_id') || 0;
2348
2349    my $widget_loop = &build_template_table( $app,
2350        load_args => [ 
2351            { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2352            { sort => 'name', direction => 'ascend' }
2353        ],
2354    );
2355
2356    my $iter = $app->model('template')->load_iter(
2357        { type => 'widgetset', blog_id => $blog_id ? $blog_id : 0 },
2358        { sort => 'name', direction => 'ascend' }
2359    );
2360    my @widgetmanagers;
2361    while ( my $widgetset = $iter->() ) {
2362        next unless $widgetset;
2363        my $ws = { 
2364            id => $widgetset->id,
2365            widgetmanager => $widgetset->name,
2366        };
2367        if ( my $modulesets = $widgetset->modulesets ) {
2368            $ws->{widgets} = $modulesets;
2369            my @names;
2370            foreach my $module ( split ',', $modulesets ) { 
2371                my ( $widget ) = grep { $_->{id} eq $module } @$widget_loop;
2372                push @names, $widget->{name} if $widget;
2373            }
2374            $ws->{names} = join(', ', @names) if @names;
2375        }
2376        push @widgetmanagers, $ws;
2377    }
2378
2379    my @widget_loop;
2380    if ( $blog_id ) {
2381        # Rem