root/branches/release-39/lib/MT/CMS/Template.pm @ 2549

Revision 2549, 87.0 kB (checked in by fumiakiy, 18 months ago)

Do not call listing method when there is no appropriate condition for the type when building list of templates. BugId:79976

  • 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                $map->file_template( $q->param($p) );
1431                $map->save;
1432            }
1433            elsif ( $p =~ /^map_build_type_(\d+)$/ ) {
1434                my $map_id     = $1;
1435                $map        = MT::TemplateMap->load($map_id)
1436                    or next;
1437                my $build_type = $q->param($p);
1438                require MT::PublishOption;
1439                # Populate maps that are changed from static to dynamic
1440                # This should capture new map as well
1441                push @static_maps, $map->id
1442                    if ( ( $build_type ne $map->build_type )
1443                      && ( MT::PublishOption::DYNAMIC() eq $build_type ) );
1444                $map->build_type($build_type);
1445                if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1446                    my $period   = $q->param( 'map_schedule_period_' . $map_id );
1447                    my $interval = $q->param( 'map_schedule_interval_' . $map_id );
1448                    my $sec      = _get_interval( $period, $interval );
1449                    $map->build_interval($sec);
1450                }
1451                $map->save;
1452            }
1453            if ( !$dynamic
1454              && $map && $map->build_type == MT::PublishOption::DYNAMIC() )
1455            {
1456                $dynamic = 1;
1457            }
1458        }
1459        $app->{static_dynamic_maps} = @static_maps ? \@static_maps : 0;
1460    }
1461
1462    if ( !$original->id ) {
1463        $app->log(
1464            {
1465                message => $app->translate(
1466                    "Template '[_1]' (ID:[_2]) created by '[_3]'",
1467                    $obj->name, $obj->id, $app->user->name
1468                ),
1469                level    => MT::Log::INFO(),
1470                class    => 'template',
1471                category => 'new',
1472            }
1473        );
1474    }
1475
1476    if ( $dynamic ) {
1477        if ( $obj->type eq 'index' ) {
1478            $app->rebuild_indexes(
1479                BlogID   => $obj->blog_id,
1480                Template => $obj,
1481                NoStatic => 1,
1482            ) or return $app->publish_error();    # XXXX
1483        }
1484        if ( my $blog = $app->blog ) {
1485            require MT::CMS::Blog;
1486            my ( $path, $url );
1487            if ( $obj->type eq 'index' ) {
1488                $path = $blog->site_path;
1489                $url = $blog->site_url;
1490            }
1491            else {
1492                # must be archive since other types can't be dynamic
1493                if ( $path = $blog->archive_path ) {
1494                    $url = $blog->archive_url;
1495                }
1496                else {
1497                    $path = $blog->site_path;
1498                    $url = $blog->site_url;
1499                }
1500            }
1501            # specific arguments so not to overwrite mtview and htaccess
1502            MT::CMS::Blog::prepare_dynamic_publishing(
1503                $eh, 
1504                $blog,
1505                undef,
1506                undef,
1507                $path,
1508                $url
1509            );
1510        }
1511    }
1512    1;
1513}
1514
1515sub post_delete {
1516    my ( $eh, $app, $obj ) = @_;
1517
1518    $app->log(
1519        {
1520            message => $app->translate(
1521                "Template '[_1]' (ID:[_2]) deleted by '[_3]'",
1522                $obj->name, $obj->id, $app->user->name
1523            ),
1524            level    => MT::Log::INFO(),
1525            class    => 'system',
1526            category => 'delete'
1527        }
1528    );
1529}
1530
1531sub build_template_table {
1532    my $app = shift;
1533    my (%args) = @_;
1534
1535    my $perms     = $app->permissions;
1536    my $list_pref = $app->list_pref('template');
1537    my $limit     = $args{limit};
1538    my $param     = $args{param} || {};
1539    my $iter;
1540    if ( $args{load_args} ) {
1541        my $class = $app->model('template');
1542        $iter = $class->load_iter( @{ $args{load_args} } );
1543    }
1544    elsif ( $args{iter} ) {
1545        $iter = $args{iter};
1546    }
1547    elsif ( $args{items} ) {
1548        $iter = sub { pop @{ $args{items} } };
1549        $limit = scalar @{ $args{items} };
1550    }
1551    return [] unless $iter;
1552
1553    my @data;
1554    my $i;
1555    my %blogs;
1556    while ( my $tmpl = $iter->() ) {
1557        my $blog = $blogs{ $tmpl->blog_id } ||=
1558          MT::Blog->load( $tmpl->blog_id ) if $tmpl->blog_id;
1559
1560        my $row = $tmpl->column_values;
1561        $row->{name} = '' if !defined $row->{name};
1562        $row->{name} =~ s/^\s+|\s+$//g;
1563        $row->{name} = "(" . $app->translate("No Name") . ")"
1564          if $row->{name} eq '';
1565        my $published_url = $tmpl->published_url;
1566        $row->{published_url} = $published_url if $published_url;
1567        $row->{use_cache} = ( ($tmpl->cache_expire_type || 0) != 0 )  ? 1 : 0;
1568
1569        # FIXME: enumeration of types
1570        $row->{can_delete} = 1
1571          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1572        if ($blog) {
1573            $row->{weblog_name} = $blog->name;
1574        }
1575        elsif ($tmpl->blog_id) {
1576            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1577        }
1578        else {
1579            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1580        }
1581        $row->{object} = $tmpl;
1582        push @data, $row;
1583        last if defined($limit) && (@data > $limit);
1584    }
1585    return [] unless @data;
1586
1587    $param->{template_table}[0]              = {%$list_pref};
1588    $param->{template_table}[0]{object_loop} = \@data;
1589    $param->{template_table}[0]{object_type} = 'template';
1590    $app->load_list_actions( 'template', $param );
1591    $param->{object_loop} = \@data;
1592    \@data;
1593}
1594
1595sub dialog_publishing_profile {
1596    my $app = shift;
1597    $app->validate_magic or return;
1598
1599    my $blog = $app->blog;
1600    $app->assert( $blog ) or return;
1601
1602    # permission check
1603    my $perms = $app->permissions;
1604    return $app->errtrans("Permission denied.")
1605        unless $app->user->is_superuser ||
1606            $perms->can_administer_blog ||
1607            $perms->can_edit_templates;
1608
1609    my $param = {};
1610    $param->{dynamicity} = $blog->custom_dynamic_templates || 'none';
1611    $param->{screen_id} = "publishing-profile-dialog";
1612    $param->{return_args} = $app->param('return_args');
1613
1614    $app->build_page('dialog/publishing_profile.tmpl',
1615        $param);
1616}
1617
1618sub dialog_refresh_templates {
1619    my $app = shift;
1620    $app->validate_magic or return;
1621
1622    # permission check
1623    my $perms = $app->permissions;
1624    return $app->errtrans("Permission denied.")
1625        unless $app->user->is_superuser ||
1626            $perms->can_administer_blog ||
1627            $perms->can_edit_templates;
1628
1629    my $param = {};
1630    my $blog = $app->blog;
1631    $param->{return_args} = $app->param('return_args');
1632
1633    if ($blog) {
1634        $param->{blog_id} = $blog->id;
1635
1636        my $sets = $app->registry("template_sets");
1637        $sets->{$_}{key} = $_ for keys %$sets;
1638        $sets = $app->filter_conditional_list([ values %$sets ]);
1639
1640        no warnings; # some sets may not define an order
1641        @$sets = sort { $a->{order} <=> $b->{order} } @$sets;
1642        $param->{'template_set_loop'} = $sets;
1643
1644        my $existing_set = $blog->template_set || 'mt_blog';
1645        foreach (@$sets) {
1646            if ($_->{key} eq $existing_set) {
1647                $_->{selected} = 1;
1648            }
1649        }
1650        $param->{'template_set_index'} = $#$sets;
1651        $param->{'template_set_count'} = scalar @$sets;
1652
1653        $param->{template_sets} = $sets;
1654        $param->{screen_id} = "refresh-templates-dialog";
1655    }
1656
1657    # load template sets
1658    $app->build_page('dialog/refresh_templates.tmpl',
1659        $param);
1660}
1661
1662sub refresh_all_templates {
1663    my ($app) = @_;
1664
1665    my $backup = 0;
1666    if ($app->param('backup')) {
1667        # refresh templates dialog uses a 'backup' field
1668        $backup = 1;
1669    }
1670
1671    my $template_set = $app->param('template_set');
1672    my $refresh_type = $app->param('refresh_type') || 'refresh';
1673
1674    my $t = time;
1675
1676    my @id;
1677    if ($app->param('blog_id')) {
1678        @id = ( scalar $app->param('blog_id') );
1679    }
1680    else {
1681        @id = $app->param('id');
1682        if (! @id) {
1683            # refresh global templates
1684            @id = ( 0 );
1685        }
1686    }
1687
1688    require MT::Template;
1689    require MT::DefaultTemplates;
1690    require MT::Blog;
1691    require MT::Permission;
1692    require MT::Util;
1693
1694    foreach my $blog_id (@id) {
1695        my $blog;
1696        if ($blog_id) {
1697            $blog = MT::Blog->load($blog_id);
1698            next unless $blog;
1699        }
1700        if ( !$app->user->is_superuser() ) {
1701            my $perms = MT::Permission->load(
1702                { blog_id => $blog_id, author_id => $app->user->id } );
1703            if (
1704                !$perms
1705                || (   !$perms->can_edit_templates()
1706                    && !$perms->can_administer_blog() )
1707              )
1708            {
1709                next;
1710            }
1711        }
1712
1713        my $tmpl_list;
1714        if ($blog_id) {
1715
1716            if ($refresh_type eq 'clean') {
1717                # the user wants to back up all templates and
1718                # install the new ones
1719
1720                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1721                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1722                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1723
1724                my $tmpl_iter = MT::Template->load_iter({
1725                    blog_id => $blog_id,
1726                    type => { not => 'backup' },
1727                });
1728
1729                while (my $tmpl = $tmpl_iter->()) {
1730                    if ($backup) {
1731                        # zap all template maps
1732                        require MT::TemplateMap;
1733                        MT::TemplateMap->remove({
1734                            template_id => $tmpl->id,
1735                        });
1736                        $tmpl->type('backup');
1737                        $tmpl->name(
1738                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1739                        $tmpl->identifier(undef);
1740                        $tmpl->rebuild_me(0);
1741                        $tmpl->linked_file(undef);
1742                        $tmpl->outfile('');
1743                        $tmpl->save;
1744                    } else {
1745                        $tmpl->remove;
1746                    }
1747                }
1748
1749                # This also creates our template mappings
1750                $blog->create_default_templates( $template_set ||
1751                    $blog->template_set || 'mt_blog' );
1752
1753                if ($template_set) {
1754                    $blog->template_set( $template_set );
1755                    $blog->save;
1756                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1757                }
1758
1759                next;
1760            }
1761
1762            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1763        }
1764        else {
1765            $tmpl_list = MT::DefaultTemplates->templates();
1766        }
1767
1768        foreach my $val (@$tmpl_list) {
1769            if ($blog_id) {
1770                # when refreshing blog templates,
1771                # skip over global templates which
1772                # specify a blog_id of 0...
1773                next if $val->{global};
1774            }
1775            else {
1776                next unless exists $val->{global};
1777            }
1778
1779            if ( !$val->{orig_name} ) {
1780                $val->{orig_name} = $val->{name};
1781                $val->{text}      = $app->translate_templatized( $val->{text} );
1782            }
1783
1784            my $orig_name = $val->{orig_name};
1785
1786            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1787            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1788              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1789
1790            my $terms = {};
1791            $terms->{blog_id} = $blog_id;
1792            $terms->{type} = $val->{type};
1793            if ( $val->{type} =~
1794                m/^(archive|individual|page|category|index|custom|widget|widgetset)$/ )
1795            {
1796                $terms->{name} = $val->{name};
1797            }
1798            else {
1799                $terms->{identifier} = $val->{identifier};
1800            }
1801
1802            # this should only return 1 template; we're searching
1803            # within a given blog for a specific type of template (for
1804            # "system" templates; or for a type + name, which should be
1805            # unique for that blog.
1806            my $tmpl = MT::Template->load($terms);
1807            if ($tmpl && $backup) {
1808
1809                # check for default template text...
1810                # if it is a default template, then outright replace it
1811                my $text = $tmpl->text;
1812                $text =~ s/\s+//g;
1813
1814                my $def_text = $val->{text};
1815                $def_text =~ s/\s+//g;
1816
1817                # if it has been customized, back it up to a new tmpl record
1818                if ($def_text ne $text) {
1819                    my $backup = $tmpl->clone;
1820                    delete $backup->{column_values}
1821                      ->{id};    # make sure we don't overwrite original
1822                    delete $backup->{changed_cols}->{id};
1823                    $backup->name(
1824                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1825                    $backup->type('backup');
1826                    # if ( $backup->type !~
1827                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1828                    # {
1829                    #     $backup->type('custom')
1830                    #       ;      # system templates can't be created
1831                    # }
1832                    $backup->outfile('');
1833                    $backup->linked_file( $tmpl->linked_file );
1834                    $backup->identifier(undef);
1835                    $backup->rebuild_me(0);
1836                    $backup->build_dynamic(0);
1837                    $backup->save;
1838                }
1839            }
1840            if ($tmpl) {
1841                # we found that the previous template had not been
1842                # altered, so replace it with new default template...
1843                if ( ( 'widgetset' eq $val->{type} )
1844                  && ( exists $val->{widgets} ) ) {
1845                    my $modulesets = delete $val->{widgets};
1846                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1847                }
1848                $tmpl->text( $val->{text} );
1849                $tmpl->identifier( $val->{identifier} );
1850                $tmpl->type( $val->{type} )
1851                  ; # fixes mismatch of types for cases like "archive" => "individual"
1852                $tmpl->linked_file('');
1853                $tmpl->save;
1854            }
1855            else {
1856                # create this one...
1857                my $tmpl = new MT::Template;
1858                if ( ( 'widgetset' eq $val->{type} )
1859                  && ( exists $val->{widgets} ) ) {
1860                    my $modulesets = delete $val->{widgets};
1861                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1862                }
1863                $tmpl->build_dynamic(0);
1864                $tmpl->set_values(
1865                    {
1866                        text       => $val->{text},
1867                        name       => $val->{name},
1868                        type       => $val->{type},
1869                        identifier => $val->{identifier},
1870                        outfile    => $val->{outfile},
1871                        rebuild_me => $val->{rebuild_me},
1872                    }
1873                );
1874                $tmpl->blog_id($blog_id);
1875                $tmpl->save
1876                  or return $app->error(
1877                        $app->translate("Error creating new template: ")
1878                      . $tmpl->errstr );
1879            }
1880        }
1881    }
1882
1883    $app->add_return_arg( 'refreshed' => 1 );
1884    $app->call_return;
1885}
1886
1887sub refresh_individual_templates {
1888    my ($app) = @_;
1889
1890    require MT::Util;
1891
1892    my $user = $app->user;
1893    my $perms = $app->permissions;
1894    return $app->error(
1895        $app->translate(
1896            "Permission denied.")
1897      )
1898      #TODO: system level-designer permission
1899      unless $user->is_superuser() || $user->can_edit_templates()
1900      || ( $perms
1901        && ( $perms->can_edit_templates()
1902          || $perms->can_administer_blog ) );
1903
1904    my $set;
1905    if ( my $blog_id = $app->param('blog_id') ) {
1906        my $blog = $app->model('blog')->load($blog_id)
1907            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1908        $set = $blog->template_set()
1909            if $blog;
1910    }
1911
1912    require MT::DefaultTemplates;
1913    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1914
1915    my $tmpl_types = {};
1916    my $tmpl_ids   = {};
1917    my $tmpls      = {};
1918    foreach my $tmpl (@$tmpl_list) {
1919        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1920        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1921            if $tmpl->{identifier};
1922        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1923        {
1924            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1925        }
1926        else {
1927            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1928        }
1929    }
1930
1931    my $t = time;
1932
1933    my @msg;
1934    my @id = $app->param('id');
1935    require MT::Template;
1936    foreach my $tmpl_id (@id) {
1937        my $tmpl = MT::Template->load($tmpl_id);
1938        next unless $tmpl;
1939        my $blog_id = $tmpl->blog_id;
1940
1941        # FIXME: permission check -- for this blog_id
1942
1943        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1944        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1945          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1946
1947        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1948          || $tmpl_types->{ $tmpl->type() }
1949          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1950        if ( !$val ) {
1951            push @msg,
1952              $app->translate(
1953"Skipping template '[_1]' since it appears to be a custom template.",
1954                $tmpl->name
1955              );
1956            next;
1957        }
1958
1959        my $text = $tmpl->text;
1960        $text =~ s/\s+//g;
1961
1962        my $def_text = $val->{text};
1963        $def_text =~ s/\s+//g;
1964
1965        if ($text ne $def_text) {
1966            # if it has been customized, back it up to a new tmpl record
1967            my $backup = $tmpl->clone;
1968            delete $backup->{column_values}
1969              ->{id};    # make sure we don't overwrite original
1970            delete $backup->{changed_cols}->{id};
1971            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1972            $backup->type('backup');
1973            $backup->outfile('');
1974            $backup->linked_file( $tmpl->linked_file );
1975            $backup->rebuild_me(0);
1976            $backup->build_dynamic(0);
1977            $backup->identifier(undef);
1978            $backup->save;
1979            push @msg,
1980              $app->translate(
1981    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1982                  $blog_id, $backup->id, $tmpl->name );
1983
1984            # we found that the previous template had not been
1985            # altered, so replace it with new default template...
1986            $tmpl->text( $val->{text} );
1987            $tmpl->identifier( $val->{identifier} );
1988            $tmpl->linked_file('');
1989            $tmpl->save;
1990        } else {
1991            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
1992        }
1993    }
1994    my @msg_loop;
1995    push @msg_loop, { message => $_ } foreach @msg;
1996
1997    $app->build_page( 'refresh_results.tmpl',
1998        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1999}
2000
2001sub clone_templates {
2002    my ($app) = @_;
2003
2004    my $user = $app->user;
2005    my $perms = $app->permissions;
2006    return $app->error(
2007        $app->translate(
2008            "Permission denied.")
2009      )
2010      #TODO: system level-designer permission
2011      unless $user->is_superuser() || $user->can_edit_templates()
2012      || ( $perms
2013        && ( $perms->can_edit_templates()
2014          || $perms->can_administer_blog ) );
2015
2016    my @id = $app->param('id');
2017    require MT::Template;
2018    foreach my $tmpl_id (@id) {
2019        my $tmpl = MT::Template->load($tmpl_id);
2020        next unless $tmpl;
2021
2022        my $new_tmpl = $tmpl->clone({
2023            Except => {
2024                id => 1,
2025                name => 1,
2026                identifier => 1,
2027            },
2028        });
2029
2030        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
2031        my $new_name = $new_basename;
2032        my $i = 0;
2033        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
2034            $new_name = $new_basename . ' (' . ++$i . ')';
2035        }
2036
2037        $new_tmpl->name($new_name);
2038        $new_tmpl->save;
2039    }
2040
2041    $app->add_return_arg( 'saved_copied' => 1 );
2042    $app->call_return;
2043}
2044
2045sub publish_index_templates {
2046    my $app = shift;
2047    $app->validate_magic or return;
2048
2049    # permission check
2050    my $perms = $app->permissions;
2051    return $app->errtrans("Permission denied.")
2052        unless $app->user->is_superuser ||
2053            $perms->can_administer_blog ||
2054            $perms->can_rebuild;
2055
2056    my $blog = $app->blog;
2057    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
2058    TEMPLATE: for my $tmpl (@$templates) {
2059        next TEMPLATE if !defined $tmpl;
2060        next TEMPLATE if $tmpl->blog_id != $blog->id;
2061        next TEMPLATE unless $tmpl->build_type;
2062
2063        $app->rebuild_indexes(
2064            Blog     => $blog,
2065            Template => $tmpl,
2066            Force    => 1,
2067        );
2068    }
2069
2070    $app->call_return( published => 1 );
2071}
2072
2073sub publish_archive_templates {
2074    my $app = shift;
2075    $app->validate_magic or return;
2076
2077    # permission check
2078    my $perms = $app->permissions;
2079    return $app->errtrans("Permission denied.")
2080      unless $app->user->is_superuser
2081      || $perms->can_administer_blog
2082      || $perms->can_rebuild;
2083
2084    my @ids = $app->param('id');
2085    if (scalar @ids == 1) {
2086        # we also support a list of comma-delimited ids like this
2087        @ids = split /,/, $ids[0];
2088    }
2089    return $app->error($app->translate("Invalid request."))
2090        unless @ids;
2091
2092    my $tmpl_id;
2093    my %ats;
2094    require MT::TemplateMap;
2095    while (!$tmpl_id && @ids) {
2096        $tmpl_id = shift @ids;
2097        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl_id } );
2098        foreach my $map (@tmpl_maps) {
2099            next unless $map->build_type;
2100            $ats{ $map->archive_type } = 1;
2101        }
2102        undef $tmpl_id unless keys %ats;
2103    }
2104
2105    # we have a template and archive types to publish!
2106
2107    require MT::CMS::Blog;
2108    my $return_args;
2109    my $reedit = $app->param('reedit');
2110    if (@ids) {
2111        # we have more to do after this, so save the list
2112        # of remaining archive templates...
2113        $return_args = $app->uri_params(
2114            mode => 'publish_archive_templates',
2115            args => {
2116                magic_token => $app->current_magic,
2117                blog_id => scalar $app->param('blog_id'),
2118                id => join(",", @ids),
2119                reedit => $reedit,
2120            }
2121        );
2122    } else {
2123        my $mode = $reedit ? 'view' : 'list';
2124        $return_args = $app->uri_params(
2125            mode => $mode,
2126            args => {
2127                _type     => 'template',
2128                blog_id   => scalar $app->param('blog_id'),
2129                published => 1,
2130                ( $reedit ? ( saved => 1 )       : () ),
2131                ( $reedit ? ( id    => $reedit ) : () ),
2132            }
2133        );
2134    }
2135    $return_args =~ s/^\?//;
2136
2137    $app->return_args( $return_args );
2138    $app->param( 'template_id', $tmpl_id );
2139    $app->param( 'single_template', 1 ); # forces fullscreen mode
2140    $app->param( 'type', join(",", keys %ats) );
2141    return MT::CMS::Blog::start_rebuild_pages($app);
2142}
2143
2144sub save_widget {
2145    my $app = shift;
2146    my $q   = $app->param;
2147
2148    $app->validate_magic() or return;
2149    my $author = $app->user;
2150
2151    my $id = $q->param('id');
2152
2153    if ( !$author->is_superuser ) {
2154        $app->run_callbacks( 'cms_save_permission_filter.template', $app, $id )
2155          || return $app->error(
2156            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2157    }
2158
2159    my $filter_result = $app->run_callbacks( 'cms_save_filter.widgetset', $app );
2160
2161    if ( !$filter_result ) {
2162        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2163    }
2164
2165    my $class = $app->model('template');
2166    my $obj;
2167    if ( $id ) {
2168        $obj = $class->load($id)
2169            or return $app->error($app->translate("Invalid ID [_1]", $id));
2170    }
2171    else {
2172        $obj = $class->new;
2173    }
2174
2175    my $original = $obj->clone();
2176    $obj->name($q->param('name'));
2177    $obj->type('widgetset');
2178    $obj->blog_id( $q->param('blog_id') || 0 );
2179    $obj->modulesets($q->param('modules'));
2180
2181    unless (
2182        $app->run_callbacks( 'cms_pre_save.template', $app, $obj, $original ) )
2183    {
2184        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2185    }
2186
2187    $obj->save
2188      or return $app->error(
2189        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
2190
2191    $app->run_callbacks( 'cms_post_save.template', $app, $obj, $original )
2192      or return $app->error( $app->errstr() );
2193
2194    $app->redirect(
2195        $app->uri(
2196            'mode' => 'edit_widget',
2197            args =>
2198              { blog_id => $obj->blog_id, 'saved' => 1, rebuild => 1, id => $obj->id }
2199        )
2200    );
2201}
2202
2203sub edit_widget {
2204    my $app = shift;
2205    my (%opt) = @_;
2206
2207    my $q       = $app->param();
2208    my $id      = scalar($q->param('id')) || $opt{id};
2209    my $name    = scalar($q->param('name'));
2210    my $blog_id = scalar $q->param('blog_id') || 0;
2211
2212    my $tmpl_class = $app->model('template');
2213    require MT::Promise;
2214    my $obj_promise = MT::Promise::delay(
2215        sub {
2216            return $tmpl_class->load($id) || undef;
2217        }
2218    );
2219
2220    if ( !$app->user->is_superuser ) {
2221        $app->run_callbacks( 'cms_view_permission_filter.template',
2222            $app, $id, $obj_promise )
2223          || return $app->error(
2224            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2225    }
2226
2227    my $param = {
2228        blog_id      => $blog_id,
2229        search_type  => "template",
2230        search_label => MT::Template->class_label_plural,
2231        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2232        exists($opt{error}) ? ( error => $opt{error} ) : (),
2233        exists($opt{saved}) ? ( saved => $opt{saved} ) : (),
2234        $id
2235          ? ( id => $id )
2236          : $name
2237            ? ( name => $name )
2238            : (),
2239    };
2240    if ($blog_id) {
2241        my $blog = $app->blog;
2242        # include_system/include_cache are only applicable
2243        # to blog-level templates
2244        $param->{include_system} = $blog->include_system;
2245        $param->{include_cache} = $blog->include_cache;
2246        $param->{include_with_ssi}      = 0;
2247        $param->{cache_path}            = '';
2248        $param->{cache_enabled}         = 0;
2249        $param->{cache_expire_type}     = 0;
2250        $param->{cache_expire_period}   = '';
2251        $param->{cache_expire_interval} = 0;
2252        $param->{ssi_type} = uc $blog->include_system;
2253    }
2254   
2255    my $iter = $tmpl_class->load_iter(
2256        { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2257        { sort => 'name', direction => 'ascend' }
2258    );
2259
2260    my %all_widgets;
2261    while (my $m = $iter->()) {
2262        next unless $m;
2263        $all_widgets{ $m->id }{name} = $m->name;
2264        $all_widgets{ $m->id }{blog_id} = $m->blog_id;
2265    }
2266
2267    my @inst_modules;
2268    my $wtmpl;
2269    if ( $id ) {
2270        $wtmpl = $obj_promise->force()
2271          or return $app->error(
2272            $app->translate(
2273                "Load failed: [_1]",
2274                $tmpl_class->errstr || $app->translate("(no reason given)")
2275            )
2276          );
2277        return $app->return_to_dashboard( redirect => 1 )
2278            if $wtmpl->blog_id ne $blog_id;
2279        $param->{name} = $wtmpl->name;
2280        $param->{include_with_ssi} = $wtmpl->include_with_ssi
2281          if defined $wtmpl->include_with_ssi;
2282        $param->{cache_path}       = $wtmpl->cache_path
2283          if defined $wtmpl->cache_path;
2284        $param->{cache_expire_type} = $wtmpl->cache_expire_type
2285          if defined $wtmpl->cache_expire_type;
2286        my ( $period, $interval ) =
2287          _get_schedule( $wtmpl->cache_expire_interval );
2288        $param->{cache_expire_period}   = $period   if defined $period;
2289        $param->{cache_expire_interval} = $interval if defined $interval;
2290        my @events = split ',', $wtmpl->cache_expire_event;
2291        foreach my $name (@events) {
2292            $param->{ 'cache_expire_event_' . $name } = 1;
2293        }
2294        my $modulesets = $wtmpl->modulesets;
2295        if ( $modulesets ) {
2296            my @modules = split ',', $modulesets;
2297            foreach my $mid ( @modules ) {
2298                push @inst_modules, {
2299                    id => $mid,
2300                    name => $all_widgets{$mid}{name},
2301                    blog_id => $all_widgets{$mid}{blog_id},
2302                };
2303                delete $all_widgets{$mid};
2304            }
2305        }
2306    }
2307    $param->{installed} = \@inst_modules if @inst_modules;
2308    my @avail_modules = map { {
2309        id => $_, name => $all_widgets{$_}{name}, blog_id => $all_widgets{$_}{blog_id}
2310    } } keys %all_widgets;
2311    $param->{available} = \@avail_modules;
2312
2313    my $res = $app->run_callbacks('cms_edit.widgetset', $app, $id, $wtmpl, $param);
2314    if (!$res) {
2315        return $app->error($app->callback_errstr());
2316    }
2317
2318    $app->load_tmpl('edit_widget.tmpl', $param);
2319}
2320
2321sub list_widget {
2322    my $app = shift;
2323    my (%opt) = @_;
2324    my $q = $app->param;
2325
2326    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
2327    return $app->return_to_dashboard( redirect => 1 )
2328      unless $perms || $app->user->is_superuser;
2329    if ( $perms && !$perms->can_edit_templates ) {
2330        return $app->return_to_dashboard( permission => 1 );
2331    }
2332    my $blog_id = $q->param('blog_id') || 0;
2333
2334    my $widget_loop = &build_template_table( $app,
2335        load_args => [ 
2336            { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2337            { sort => 'name', direction => 'ascend' }
2338        ],
2339    );
2340
2341    my $iter = $app->model('template')->load_iter(
2342        { type => 'widgetset', blog_id => $blog_id ? $blog_id : 0 },
2343        { sort => 'name', direction => 'ascend' }
2344    );
2345    my @widgetmanagers;
2346    while ( my $widgetset = $iter->() ) {
2347        next unless $widgetset;
2348        my $ws = { 
2349            id => $widgetset->id,
2350            widgetmanager => $widgetset->name,
2351        };
2352        if ( my $modulesets = $widgetset->modulesets ) {
2353            $ws->{widgets} = $modulesets;
2354            my @names;
2355            foreach my $module ( split ',', $modulesets ) { 
2356                my ( $widget ) = grep { $_->{id} eq $module } @$widget_loop;
2357                push @names, $widget->{name} if $widget;
2358            }
2359            $ws->{names} = join(', ', @names) if @names;
2360        }
2361        push @widgetmanagers, $ws;
2362    }
2363
2364    my @widget_loop;
2365    if ( $blog_id ) {
2366        # Remove system level widgets from the listing
2367        @widget_loop = grep { $_->{blog_id} == $blog_id } @$widget_loop;
2368    }
2369    else {
2370        @widget_loop = @$widget_loop;
2371    }
2372
2373    my $param = {
2374        @widgetmanagers ? ( object_loop  => \@widgetmanagers ) : (),
2375        @widget_loop    ? ( widget_table => \@widget_loop ) : (),
2376        object_type    => "template",
2377        search_type    => "template",
2378        search_label   => MT::Template->class_label_plural,
2379        listing_screen => 1,
2380        screen_id      => "list-widget-set",
2381        $blog_id ? ( blog_view => 1, blog_id => $blog_id ) : (),
2382        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),