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

Revision 2458, 85.6 kB (checked in by fumiakiy, 18 months ago)

0 is a valid value for cache_expire_type. BugId:79935

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