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

Revision 2491, 85.7 kB (checked in by fumiakiy, 18 months ago)

Preserve the value of build_dynamic before and after previewing template. BugId:80001

  • 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    $q->param( 'build_dynamic', $tmpl->build_dynamic );
972    my $cols = $tmpl->column_names;
973    for my $col (@$cols) {
974        push @data,
975          {
976            data_name  => $col,
977            data_value => scalar $q->param($col)
978          };
979    }
980    $param{template_loop} = \@data;
981    $param{object_type}  = $type;
982    return $app->load_tmpl( 'preview_template_strip.tmpl', \%param );
983}
984
985sub create_preview_content {
986    my ($app, $blog, $type, $number) = @_;
987
988    my $blog_id = $blog->id;
989    my $entry_class = $app->model($type);
990    my @obj = $entry_class->load({
991        blog_id => $blog_id,
992        status => MT::Entry::RELEASE()
993    }, {
994        limit => $number || 1,
995        direction => 'descend',
996        'sort' => 'authored_on'
997    });
998    unless ( @obj ) {
999        # create a dummy object
1000        my $obj = $entry_class->new;
1001        $obj->blog_id($blog_id);
1002        $obj->id(-1);
1003        $obj->author_id( $app->user->id );
1004        $obj->authored_on( $blog->current_timestamp );
1005        $obj->status( MT::Entry::RELEASE() );
1006        $obj->title($app->translate("Lorem ipsum"));
1007        my $preview_text = $app->translate('LOREM_IPSUM_TEXT');
1008        if ($preview_text eq 'LOREM_IPSUM_TEXT') {
1009            $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.};
1010        }
1011        my $preview_more = $app->translate('LORE_IPSUM_TEXT_MORE');
1012        if ($preview_text eq 'LOREM_IPSUM_TEXT_MORE') {
1013            $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;
1014
1015            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.
1016
1017            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.};
1018        }
1019        $obj->text($preview_text);
1020        $obj->text_more($preview_more);
1021        $obj->keywords(MT->translate("sample, entry, preview"));
1022        $obj->tags(qw( lorem ipsum sample preview ));
1023        @obj = ($obj);
1024    }
1025    return @obj;
1026}
1027
1028sub reset_blog_templates {
1029    my $app   = shift;
1030    my $q     = $app->param;
1031    my $perms = $app->permissions
1032      or return $app->error( $app->translate("No permissions") );
1033    return $app->error( $app->translate("Permission denied.") )
1034      unless $perms->can_edit_templates;
1035    $app->validate_magic() or return;
1036    my $blog = MT::Blog->load( $perms->blog_id )
1037        or return $app->error($app->translate('Can\'t load blog #[_1].', $perms->blog_id));
1038    require MT::Template;
1039    my @tmpl = MT::Template->load( { blog_id => $blog->id } );
1040
1041    for my $tmpl (@tmpl) {
1042        $tmpl->remove or return $app->error( $tmpl->errstr );
1043    }
1044    my $set = $blog ? $blog->template_set : undef;
1045    require MT::DefaultTemplates;
1046    my $tmpl_list = MT::DefaultTemplates->templates($set) || [];
1047    my @arch_tmpl;
1048    for my $val (@$tmpl_list) {
1049        $val->{text} = $app->translate_templatized( $val->{text} );
1050        my $tmpl = MT::Template->new;
1051        if ( ( 'widgetset' eq $val->{type} )
1052          && ( exists $val->{modulesets} ) ) {
1053            my $modulesets = delete $val->{modulesets};
1054            $tmpl->modulesets( join ',', @$modulesets );
1055        }
1056        $tmpl->set_values($val);
1057        $tmpl->build_dynamic(0);
1058        $tmpl->blog_id( $blog->id );
1059        $tmpl->save
1060          or return $app->error(
1061            $app->translate(
1062                "Populating blog with default templates failed: [_1]",
1063                $tmpl->errstr
1064            )
1065          );
1066
1067        # FIXME: enumeration of types
1068        if (   $val->{type} eq 'archive'
1069            || $val->{type} eq 'category'
1070            || $val->{type} eq 'page'
1071            || $val->{type} eq 'individual' )
1072        {
1073            push @arch_tmpl, $tmpl;
1074        }
1075    }
1076
1077    ## Set up mappings from new templates to archive types.
1078    for my $tmpl (@arch_tmpl) {
1079        my (@at);
1080
1081        # FIXME: enumeration of types
1082        if ( $tmpl->type eq 'archive' ) {
1083            @at = qw( Daily Weekly Monthly Category );
1084        }
1085        elsif ( $tmpl->type eq 'page' ) {
1086            @at = qw( Page );
1087        }
1088        elsif ( $tmpl->type eq 'individual' ) {
1089            @at = qw( Individual );
1090        }
1091        require MT::TemplateMap;
1092        for my $at (@at) {
1093            my $map = MT::TemplateMap->new;
1094            $map->archive_type($at);
1095            $map->is_preferred(1);
1096            $map->template_id( $tmpl->id );
1097            $map->blog_id( $tmpl->blog_id );
1098            $map->save
1099              or return $app->error(
1100                $app->translate(
1101                    "Setting up mappings failed: [_1]",
1102                    $map->errstr
1103                )
1104              );
1105        }
1106    }
1107    $app->redirect(
1108        $app->uri(
1109            'mode' => 'list',
1110            args =>
1111              { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 }
1112        )
1113    );
1114}
1115
1116sub _generate_map_table {
1117    my $app = shift;
1118    my ( $blog_id, $template_id ) = @_;
1119
1120    require MT::Template;
1121    require MT::Blog;
1122    my $blog     = MT::Blog->load($blog_id);
1123    my $template = MT::Template->load($template_id);
1124    my $tmpl     = $app->load_tmpl('include/archive_maps.tmpl');
1125    my $maps     = _populate_archive_loop( $app, $blog, $template );
1126    $tmpl->param( object_type => 'templatemap' );
1127    $tmpl->param( publish_queue_available => eval 'require List::Util; require Scalar::Util; 1;' );
1128    $tmpl->param( object_loop => $maps ) if @$maps;
1129    my $html = $tmpl->output();
1130
1131    if ( $html =~ m/<__trans / ) {
1132        $html = $app->translate_templatized($html);
1133    }
1134    $html;
1135}
1136
1137sub _populate_archive_loop {
1138    my $app = shift;
1139    my ( $blog, $obj ) = @_;
1140
1141    my $index = $app->config('IndexBasename');
1142    my $ext = $blog->file_extension || '';
1143    $ext = '.' . $ext if $ext ne '';
1144
1145    require MT::TemplateMap;
1146    my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } );
1147    my @maps;
1148    my %types;
1149    foreach my $map_obj (@tmpl_maps) {
1150        my $map = {};
1151        $map->{map_id}           = $map_obj->id;
1152        $map->{map_is_preferred} = $map_obj->is_preferred;
1153        # publish options
1154        $map->{map_build_type} = $map_obj->build_type;
1155        $map->{ 'map_build_type_' . ( $map_obj->build_type || 0 ) } = 1;
1156        my ( $period, $interval ) = _get_schedule( $map_obj->build_interval );
1157        $map->{ 'map_schedule_period_' . $period } = 1
1158            if defined $period;
1159        $map->{map_schedule_interval} = $interval
1160            if defined $interval;
1161
1162        my $at = $map->{archive_type} = $map_obj->archive_type;
1163        $types{$at}++;
1164        $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1
1165          if $blog->archive_type_preferred;
1166        $map->{file_template} = $map_obj->file_template
1167          if $map_obj->file_template;
1168
1169        my $archiver = $app->publisher->archiver($at);
1170        next unless $archiver;
1171        $map->{archive_label} = $archiver->archive_label;
1172        my $tmpls     = $archiver->default_archive_templates;
1173        my $tmpl_loop = [];
1174        foreach (@$tmpls) {
1175            my $name = $_->{label};
1176            $name =~ s/\.html$/$ext/;
1177            $name =~ s/index$ext$/$index$ext/;
1178            push @$tmpl_loop,
1179              {
1180                name    => $name,
1181                value   => $_->{template},
1182                default => ( $_->{default} || 0 ),
1183              };
1184        }
1185
1186        my $custom = 1;
1187
1188        foreach (@$tmpl_loop) {
1189            if (   ( !$map->{file_template} && $_->{default} )
1190                || ( $map->{file_template} eq $_->{value} ) )
1191            {
1192                $_->{selected}        = 1;
1193                $custom               = 0;
1194                $map->{file_template} = $_->{value}
1195                  if !$map->{file_template};
1196            }
1197        }
1198        if ($custom) {
1199            unshift @$tmpl_loop,
1200              {
1201                name     => $map->{file_template},
1202                value    => $map->{file_template},
1203                selected => 1,
1204              };
1205        }
1206
1207        $map->{archive_tmpl_loop} = $tmpl_loop;
1208        if (
1209            1 < MT::TemplateMap->count(
1210                { archive_type => $at, blog_id => $obj->blog_id }
1211            )
1212          )
1213        {
1214            $map->{has_multiple_archives} = 1;
1215        }
1216
1217        push @maps, $map;
1218    }
1219    @maps = sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @maps;
1220    return \@maps;
1221}
1222
1223sub delete_map {
1224    my $app = shift;
1225    $app->validate_magic() or return;
1226    my $perms = $app->{perms}
1227      or return $app->error( $app->translate("No permissions") );
1228    my $q  = $app->param;
1229    my $id = $q->param('id');
1230
1231    require MT::TemplateMap;
1232    MT::TemplateMap->remove( { id => $id } );
1233    my $html =
1234      _generate_map_table( $app, $q->param('blog_id'),
1235        $q->param('template_id') );
1236    $app->{no_print_body} = 1;
1237    $app->send_http_header("text/plain");
1238    $app->print($html);
1239}
1240
1241sub add_map {
1242    my $app = shift;
1243    $app->validate_magic() or return;
1244    my $perms = $app->{perms}
1245      or return $app->error( $app->translate("No permissions") );
1246
1247    my $q = $app->param;
1248
1249    require MT::TemplateMap;
1250    my $blog_id = $q->param('blog_id');
1251    my $at      = $q->param('new_archive_type');
1252    my $exist   = MT::TemplateMap->exist(
1253        {
1254            blog_id      => $blog_id,
1255            archive_type => $at
1256        }
1257    );
1258    my $map = MT::TemplateMap->new;
1259    $map->is_preferred( $exist ? 0 : 1 );
1260    $map->template_id( scalar $q->param('template_id') );
1261    $map->blog_id($blog_id);
1262    $map->archive_type($at);
1263    $map->save
1264      or return $app->error(
1265        $app->translate( "Saving map failed: [_1]", $map->errstr ) );
1266    my $html =
1267      _generate_map_table( $app, $blog_id, scalar $q->param('template_id') );
1268    $app->{no_print_body} = 1;
1269    $app->send_http_header("text/plain");
1270    $app->print($html);
1271}
1272
1273sub can_view {
1274    my ( $eh, $app, $id ) = @_;
1275    my $perms = $app->permissions;
1276    return !$id || ($perms && $perms->can_edit_templates) || (!$app->blog && $app->user->can_edit_templates);
1277}
1278
1279sub can_save {
1280    my ( $eh, $app, $id ) = @_;
1281    my $perms = $app->permissions;
1282    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1283}
1284
1285sub can_delete {
1286    my ( $eh, $app, $obj ) = @_;
1287    return 1 if $app->user->is_superuser();
1288    my $perms = $app->permissions;
1289    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1290}
1291
1292sub pre_save {
1293    my $eh = shift;
1294    my ( $app, $obj ) = @_;
1295
1296    ## Strip linefeed characters.
1297    ( my $text = $obj->text ) =~ tr/\r//d;
1298
1299    if ($text =~ m/<(MT|_)_trans/i) {
1300        $text = $app->translate_templatized($text);
1301    }
1302
1303    $obj->text($text);
1304
1305    # update text heights if necessary
1306    if ( my $perms = $app->permissions ) {
1307        my $prefs = $perms->template_prefs || '';
1308        my $text_height = $app->param('text_height');
1309        if ( defined $text_height ) {
1310            my ($pref_text_height) = $prefs =~ m/\btext:(\d+)\b/;
1311            $pref_text_height ||= 0;
1312            if ( $text_height != $pref_text_height ) {
1313                if ( $prefs =~ m/\btext\b/ ) {
1314                    $prefs =~ s/\btext(:\d+)\b/text:$text_height/;
1315                }
1316                else {
1317                    $prefs = 'text:' . $text_height . ',' . $prefs;
1318                }
1319            }
1320        }
1321
1322        if ( $prefs ne ( $perms->template_prefs || '' ) ) {
1323            $perms->template_prefs($prefs);
1324            $perms->save;
1325        }
1326    }
1327
1328    # module caching
1329    $obj->include_with_ssi( $app->param('include_with_ssi') ? 1 : 0 );
1330    $obj->cache_path( $app->param('cache_path'));
1331    my $cache_expire_type = defined $app->param('cache_expire_type')
1332      ? $app->param('cache_expire_type')
1333      : '0';
1334    $obj->cache_expire_type($cache_expire_type);
1335    my $period   = $app->param('cache_expire_period');
1336    my $interval = $app->param('cache_expire_interval');
1337    my $sec      = _get_interval( $period, $interval );
1338    $obj->cache_expire_interval($sec) if defined $sec;
1339    my $q = $app->param;
1340    my @events;
1341
1342    foreach my $name ( $q->param('cache_expire_event') ) {
1343        push @events, $name;
1344    }
1345    $obj->cache_expire_event( join ',', @events ) if $#events >= 0;
1346    if ( $cache_expire_type == 1 ) {
1347        return $eh->error(
1348            $app->translate("You should not be able to enter 0 as the time.") )
1349          if $interval == 0;
1350    }
1351    elsif ( $cache_expire_type == 2 ) {
1352        return $eh->error(
1353            $app->translate("You must select at least one event checkbox.") )
1354          if !@events;
1355    }
1356
1357    require MT::PublishOption;
1358    my $build_type = $app->param('build_type');
1359
1360    if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1361        my $period   = $app->param('schedule_period');
1362        my $interval = $app->param('schedule_interval');
1363        my $sec      = _get_interval( $period, $interval );
1364        $obj->build_interval($sec);
1365    }
1366    my $rebuild_me = 1;
1367    if (   $build_type == MT::PublishOption::DISABLED()
1368        || $build_type == MT::PublishOption::MANUALLY() )
1369    {
1370        $rebuild_me = 0;
1371    }
1372    $obj->rebuild_me($rebuild_me);
1373    1;
1374}
1375
1376sub post_save {
1377    my $eh = shift;
1378    my ( $app, $obj, $original ) = @_;
1379
1380    my $sess_obj = $app->autosave_session_obj;
1381    $sess_obj->remove if $sess_obj;
1382
1383    my $dynamic = 0;
1384    my $q = $app->param;
1385    my $type = $q->param('type');
1386    # FIXME: enumeration of types
1387    if ( $type eq 'custom'
1388      || $type eq 'index'
1389      || $type eq 'widget'
1390      || $type eq 'widgetset' )
1391    {
1392        $dynamic = $obj->build_dynamic;
1393    }
1394    else
1395    {
1396        # archive template specific post_save tasks
1397        require MT::TemplateMap;
1398        my @p = $q->param;
1399        for my $p (@p) {
1400            my $map;
1401            if ( $p =~ /^archive_tmpl_preferred_(\w+)_(\d+)$/ ) {
1402                my $at     = $1;
1403                my $map_id = $2;
1404                $map    = MT::TemplateMap->load($map_id)
1405                    or next;
1406                $map->prefer( $q->param($p) );    # prefer method saves in itself
1407            }
1408            elsif ( $p =~ /^archive_file_tmpl_(\d+)$/ ) {
1409                my $map_id = $1;
1410                $map    = MT::TemplateMap->load($map_id)
1411                    or next;
1412                $map->file_template( $q->param($p) );
1413                $map->save;
1414            }
1415            elsif ( $p =~ /^map_build_type_(\d+)$/ ) {
1416                my $map_id     = $1;
1417                $map        = MT::TemplateMap->load($map_id)
1418                    or next;
1419                my $build_type = $q->param($p);
1420                require MT::PublishOption;
1421                $map->build_type($build_type);
1422                if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1423                    my $period   = $q->param( 'map_schedule_period_' . $map_id );
1424                    my $interval = $q->param( 'map_schedule_interval_' . $map_id );
1425                    my $sec      = _get_interval( $period, $interval );
1426                    $map->build_interval($sec);
1427                }
1428                $map->save;
1429            }
1430            if ( !$dynamic
1431              && $map && $map->build_type == MT::PublishOption::DYNAMIC() )
1432            {
1433                $dynamic = 1;
1434            }
1435        }
1436    }
1437
1438    if ( !$original->id ) {
1439        $app->log(
1440            {
1441                message => $app->translate(
1442                    "Template '[_1]' (ID:[_2]) created by '[_3]'",
1443                    $obj->name, $obj->id, $app->user->name
1444                ),
1445                level    => MT::Log::INFO(),
1446                class    => 'template',
1447                category => 'new',
1448            }
1449        );
1450    }
1451
1452    if ( $dynamic ) {
1453        if ( $obj->type eq 'index' ) {
1454            $app->rebuild_indexes(
1455                BlogID   => $obj->blog_id,
1456                Template => $obj,
1457                NoStatic => 1,
1458            ) or return $app->publish_error();    # XXXX
1459        }
1460        if ( my $blog = $app->blog ) {
1461            require MT::CMS::Blog;
1462            my ( $path, $url );
1463            if ( $obj->type eq 'index' ) {
1464                $path = $blog->site_path;
1465                $url = $blog->site_url;
1466            }
1467            else {
1468                # must be archive since other types can't be dynamic
1469                if ( $path = $blog->archive_path ) {
1470                    $url = $blog->archive_url;
1471                }
1472                else {
1473                    $path = $blog->site_path;
1474                    $url = $blog->site_url;
1475                }
1476            }
1477            # specific arguments so not to overwrite mtview and htaccess
1478            MT::CMS::Blog::prepare_dynamic_publishing(
1479                $eh, 
1480                $blog,
1481                undef,
1482                undef,
1483                $path,
1484                $url
1485            );
1486        }
1487    }
1488    1;
1489}
1490
1491sub post_delete {
1492    my ( $eh, $app, $obj ) = @_;
1493
1494    $app->log(
1495        {
1496            message => $app->translate(
1497                "Template '[_1]' (ID:[_2]) deleted by '[_3]'",
1498                $obj->name, $obj->id, $app->user->name
1499            ),
1500            level    => MT::Log::INFO(),
1501            class    => 'system',
1502            category => 'delete'
1503        }
1504    );
1505}
1506
1507sub build_template_table {
1508    my $app = shift;
1509    my (%args) = @_;
1510
1511    my $perms     = $app->permissions;
1512    my $list_pref = $app->list_pref('template');
1513    my $limit     = $args{limit};
1514    my $param     = $args{param} || {};
1515    my $iter;
1516    if ( $args{load_args} ) {
1517        my $class = $app->model('template');
1518        $iter = $class->load_iter( @{ $args{load_args} } );
1519    }
1520    elsif ( $args{iter} ) {
1521        $iter = $args{iter};
1522    }
1523    elsif ( $args{items} ) {
1524        $iter = sub { pop @{ $args{items} } };
1525        $limit = scalar @{ $args{items} };
1526    }
1527    return [] unless $iter;
1528
1529    my @data;
1530    my $i;
1531    my %blogs;
1532    while ( my $tmpl = $iter->() ) {
1533        my $blog = $blogs{ $tmpl->blog_id } ||=
1534          MT::Blog->load( $tmpl->blog_id ) if $tmpl->blog_id;
1535
1536        my $row = $tmpl->column_values;
1537        $row->{name} = '' if !defined $row->{name};
1538        $row->{name} =~ s/^\s+|\s+$//g;
1539        $row->{name} = "(" . $app->translate("No Name") . ")"
1540          if $row->{name} eq '';
1541        my $published_url = $tmpl->published_url;
1542        $row->{published_url} = $published_url if $published_url;
1543        $row->{use_cache} = ( ($tmpl->cache_expire_type || 0) != 0 )  ? 1 : 0;
1544
1545        # FIXME: enumeration of types
1546        $row->{can_delete} = 1
1547          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1548        if ($blog) {
1549            $row->{weblog_name} = $blog->name;
1550        }
1551        elsif ($tmpl->blog_id) {
1552            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1553        }
1554        else {
1555            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1556        }
1557        $row->{object} = $tmpl;
1558        push @data, $row;
1559        last if defined($limit) && (@data > $limit);
1560    }
1561    return [] unless @data;
1562
1563    $param->{template_table}[0]              = {%$list_pref};
1564    $param->{template_table}[0]{object_loop} = \@data;
1565    $param->{template_table}[0]{object_type} = 'template';
1566    $app->load_list_actions( 'template', $param );
1567    $param->{object_loop} = \@data;
1568    \@data;
1569}
1570
1571sub dialog_publishing_profile {
1572    my $app = shift;
1573    $app->validate_magic or return;
1574
1575    my $blog = $app->blog;
1576    $app->assert( $blog ) or return;
1577
1578    # permission check
1579    my $perms = $app->permissions;
1580    return $app->errtrans("Permission denied.")
1581        unless $app->user->is_superuser ||
1582            $perms->can_administer_blog ||
1583            $perms->can_edit_templates;
1584
1585    my $param = {};
1586    $param->{dynamicity} = $blog->custom_dynamic_templates || 'none';
1587    $param->{screen_id} = "publishing-profile-dialog";
1588    $param->{return_args} = $app->param('return_args');
1589
1590    $app->build_page('dialog/publishing_profile.tmpl',
1591        $param);
1592}
1593
1594sub dialog_refresh_templates {
1595    my $app = shift;
1596    $app->validate_magic or return;
1597
1598    # permission check
1599    my $perms = $app->permissions;
1600    return $app->errtrans("Permission denied.")
1601        unless $app->user->is_superuser ||
1602            $perms->can_administer_blog ||
1603            $perms->can_edit_templates;
1604
1605    my $param = {};
1606    my $blog = $app->blog;
1607    $param->{return_args} = $app->param('return_args');
1608
1609    if ($blog) {
1610        $param->{blog_id} = $blog->id;
1611
1612        my $sets = $app->registry("template_sets");
1613        $sets->{$_}{key} = $_ for keys %$sets;
1614        $sets = $app->filter_conditional_list([ values %$sets ]);
1615
1616        no warnings; # some sets may not define an order
1617        @$sets = sort { $a->{order} <=> $b->{order} } @$sets;
1618        $param->{'template_set_loop'} = $sets;
1619
1620        my $existing_set = $blog->template_set || 'mt_blog';
1621        foreach (@$sets) {
1622            if ($_->{key} eq $existing_set) {
1623                $_->{selected} = 1;
1624            }
1625        }
1626        $param->{'template_set_index'} = $#$sets;
1627        $param->{'template_set_count'} = scalar @$sets;
1628
1629        $param->{template_sets} = $sets;
1630        $param->{screen_id} = "refresh-templates-dialog";
1631    }
1632
1633    # load template sets
1634    $app->build_page('dialog/refresh_templates.tmpl',
1635        $param);
1636}
1637
1638sub refresh_all_templates {
1639    my ($app) = @_;
1640
1641    my $backup = 0;
1642    if ($app->param('backup')) {
1643        # refresh templates dialog uses a 'backup' field
1644        $backup = 1;
1645    }
1646
1647    my $template_set = $app->param('template_set');
1648    my $refresh_type = $app->param('refresh_type') || 'refresh';
1649
1650    my $t = time;
1651
1652    my @id;
1653    if ($app->param('blog_id')) {
1654        @id = ( scalar $app->param('blog_id') );
1655    }
1656    else {
1657        @id = $app->param('id');
1658        if (! @id) {
1659            # refresh global templates
1660            @id = ( 0 );
1661        }
1662    }
1663
1664    require MT::Template;
1665    require MT::DefaultTemplates;
1666    require MT::Blog;
1667    require MT::Permission;
1668    require MT::Util;
1669
1670    foreach my $blog_id (@id) {
1671        my $blog;
1672        if ($blog_id) {
1673            $blog = MT::Blog->load($blog_id);
1674            next unless $blog;
1675        }
1676        if ( !$app->user->is_superuser() ) {
1677            my $perms = MT::Permission->load(
1678                { blog_id => $blog_id, author_id => $app->user->id } );
1679            if (
1680                !$perms
1681                || (   !$perms->can_edit_templates()
1682                    && !$perms->can_administer_blog() )
1683              )
1684            {
1685                next;
1686            }
1687        }
1688
1689        my $tmpl_list;
1690        if ($blog_id) {
1691
1692            if ($refresh_type eq 'clean') {
1693                # the user wants to back up all templates and
1694                # install the new ones
1695
1696                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1697                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1698                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1699
1700                my $tmpl_iter = MT::Template->load_iter({
1701                    blog_id => $blog_id,
1702                    type => { not => 'backup' },
1703                });
1704
1705                while (my $tmpl = $tmpl_iter->()) {
1706                    if ($backup) {
1707                        # zap all template maps
1708                        require MT::TemplateMap;
1709                        MT::TemplateMap->remove({
1710                            template_id => $tmpl->id,
1711                        });
1712                        $tmpl->type('backup');
1713                        $tmpl->name(
1714                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1715                        $tmpl->identifier(undef);
1716                        $tmpl->rebuild_me(0);
1717                        $tmpl->linked_file(undef);
1718                        $tmpl->outfile('');
1719                        $tmpl->save;
1720                    } else {
1721                        $tmpl->remove;
1722                    }
1723                }
1724
1725                # This also creates our template mappings
1726                $blog->create_default_templates( $template_set ||
1727                    $blog->template_set || 'mt_blog' );
1728
1729                if ($template_set) {
1730                    $blog->template_set( $template_set );
1731                    $blog->save;
1732                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1733                }
1734
1735                next;
1736            }
1737
1738            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1739        }
1740        else {
1741            $tmpl_list = MT::DefaultTemplates->templates();
1742        }
1743
1744        foreach my $val (@$tmpl_list) {
1745            if ($blog_id) {
1746                # when refreshing blog templates,
1747                # skip over global templates which
1748                # specify a blog_id of 0...
1749                next if $val->{global};
1750            }
1751            else {
1752                next unless exists $val->{global};
1753            }
1754
1755            if ( !$val->{orig_name} ) {
1756                $val->{orig_name} = $val->{name};
1757                $val->{text}      = $app->translate_templatized( $val->{text} );
1758            }
1759
1760            my $orig_name = $val->{orig_name};
1761
1762            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1763            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1764              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1765
1766            my $terms = {};
1767            $terms->{blog_id} = $blog_id;
1768            $terms->{type} = $val->{type};
1769            if ( $val->{type} =~
1770                m/^(archive|individual|page|category|index|custom|widget|widgetset)$/ )
1771            {
1772                $terms->{name} = $val->{name};
1773            }
1774            else {
1775                $terms->{identifier} = $val->{identifier};
1776            }
1777
1778            # this should only return 1 template; we're searching
1779            # within a given blog for a specific type of template (for
1780            # "system" templates; or for a type + name, which should be
1781            # unique for that blog.
1782            my $tmpl = MT::Template->load($terms);
1783            if ($tmpl && $backup) {
1784
1785                # check for default template text...
1786                # if it is a default template, then outright replace it
1787                my $text = $tmpl->text;
1788                $text =~ s/\s+//g;
1789
1790                my $def_text = $val->{text};
1791                $def_text =~ s/\s+//g;
1792
1793                # if it has been customized, back it up to a new tmpl record
1794                if ($def_text ne $text) {
1795                    my $backup = $tmpl->clone;
1796                    delete $backup->{column_values}
1797                      ->{id};    # make sure we don't overwrite original
1798                    delete $backup->{changed_cols}->{id};
1799                    $backup->name(
1800                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1801                    $backup->type('backup');
1802                    # if ( $backup->type !~
1803                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1804                    # {
1805                    #     $backup->type('custom')
1806                    #       ;      # system templates can't be created
1807                    # }
1808                    $backup->outfile('');
1809                    $backup->linked_file( $tmpl->linked_file );
1810                    $backup->identifier(undef);
1811                    $backup->rebuild_me(0);
1812                    $backup->build_dynamic(0);
1813                    $backup->save;
1814                }
1815            }
1816            if ($tmpl) {
1817                # we found that the previous template had not been
1818                # altered, so replace it with new default template...
1819                if ( ( 'widgetset' eq $val->{type} )
1820                  && ( exists $val->{widgets} ) ) {
1821                    my $modulesets = delete $val->{widgets};
1822                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1823                }
1824                $tmpl->text( $val->{text} );
1825                $tmpl->identifier( $val->{identifier} );
1826                $tmpl->type( $val->{type} )
1827                  ; # fixes mismatch of types for cases like "archive" => "individual"
1828                $tmpl->linked_file('');
1829                $tmpl->save;
1830            }
1831            else {
1832                # create this one...
1833                my $tmpl = new MT::Template;
1834                if ( ( 'widgetset' eq $val->{type} )
1835                  && ( exists $val->{widgets} ) ) {
1836                    my $modulesets = delete $val->{widgets};
1837                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1838                }
1839                $tmpl->build_dynamic(0);
1840                $tmpl->set_values(
1841                    {
1842                        text       => $val->{text},
1843                        name       => $val->{name},
1844                        type       => $val->{type},
1845                        identifier => $val->{identifier},
1846                        outfile    => $val->{outfile},
1847                        rebuild_me => $val->{rebuild_me},
1848                    }
1849                );
1850                $tmpl->blog_id($blog_id);
1851                $tmpl->save
1852                  or return $app->error(
1853                        $app->translate("Error creating new template: ")
1854                      . $tmpl->errstr );
1855            }
1856        }
1857    }
1858
1859    $app->add_return_arg( 'refreshed' => 1 );
1860    $app->call_return;
1861}
1862
1863sub refresh_individual_templates {
1864    my ($app) = @_;
1865
1866    require MT::Util;
1867
1868    my $user = $app->user;
1869    my $perms = $app->permissions;
1870    return $app->error(
1871        $app->translate(
1872            "Permission denied.")
1873      )
1874      #TODO: system level-designer permission
1875      unless $user->is_superuser() || $user->can_edit_templates()
1876      || ( $perms
1877        && ( $perms->can_edit_templates()
1878          || $perms->can_administer_blog ) );
1879
1880    my $set;
1881    if ( my $blog_id = $app->param('blog_id') ) {
1882        my $blog = $app->model('blog')->load($blog_id)
1883            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1884        $set = $blog->template_set()
1885            if $blog;
1886    }
1887
1888    require MT::DefaultTemplates;
1889    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1890
1891    my $tmpl_types = {};
1892    my $tmpl_ids   = {};
1893    my $tmpls      = {};
1894    foreach my $tmpl (@$tmpl_list) {
1895        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1896        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1897            if $tmpl->{identifier};
1898        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1899        {
1900            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1901        }
1902        else {
1903            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1904        }
1905    }
1906
1907    my $t = time;
1908
1909    my @msg;
1910    my @id = $app->param('id');
1911    require MT::Template;
1912    foreach my $tmpl_id (@id) {
1913        my $tmpl = MT::Template->load($tmpl_id);
1914        next unless $tmpl;
1915        my $blog_id = $tmpl->blog_id;
1916
1917        # FIXME: permission check -- for this blog_id
1918
1919        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1920        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1921          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1922
1923        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1924          || $tmpl_types->{ $tmpl->type() }
1925          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1926        if ( !$val ) {
1927            push @msg,
1928              $app->translate(
1929"Skipping template '[_1]' since it appears to be a custom template.",
1930                $tmpl->name
1931              );
1932            next;
1933        }
1934
1935        my $text = $tmpl->text;
1936        $text =~ s/\s+//g;
1937
1938        my $def_text = $val->{text};
1939        $def_text =~ s/\s+//g;
1940
1941        if ($text ne $def_text) {
1942            # if it has been customized, back it up to a new tmpl record
1943            my $backup = $tmpl->clone;
1944            delete $backup->{column_values}
1945              ->{id};    # make sure we don't overwrite original
1946            delete $backup->{changed_cols}->{id};
1947            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1948            $backup->type('backup');
1949            $backup->outfile('');
1950            $backup->linked_file( $tmpl->linked_file );
1951            $backup->rebuild_me(0);
1952            $backup->build_dynamic(0);
1953            $backup->identifier(undef);
1954            $backup->save;
1955            push @msg,
1956              $app->translate(
1957    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1958                  $blog_id, $backup->id, $tmpl->name );
1959
1960            # we found that the previous template had not been
1961            # altered, so replace it with new default template...
1962            $tmpl->text( $val->{text} );
1963            $tmpl->identifier( $val->{identifier} );
1964            $tmpl->linked_file('');
1965            $tmpl->save;
1966        } else {
1967            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
1968        }
1969    }
1970    my @msg_loop;
1971    push @msg_loop, { message => $_ } foreach @msg;
1972
1973    $app->build_page( 'refresh_results.tmpl',
1974        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1975}
1976
1977sub clone_templates {
1978    my ($app) = @_;
1979
1980    my $user = $app->user;
1981    my $perms = $app->permissions;
1982    return $app->error(
1983        $app->translate(
1984            "Permission denied.")
1985      )
1986      #TODO: system level-designer permission
1987      unless $user->is_superuser() || $user->can_edit_templates()
1988      || ( $perms
1989        && ( $perms->can_edit_templates()
1990          || $perms->can_administer_blog ) );
1991
1992    my @id = $app->param('id');
1993    require MT::Template;
1994    foreach my $tmpl_id (@id) {
1995        my $tmpl = MT::Template->load($tmpl_id);
1996        next unless $tmpl;
1997
1998        my $new_tmpl = $tmpl->clone({
1999            Except => {
2000                id => 1,
2001                name => 1,
2002                identifier => 1,
2003            },
2004        });
2005
2006        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
2007        my $new_name = $new_basename;
2008        my $i = 0;
2009        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
2010            $new_name = $new_basename . ' (' . ++$i . ')';
2011        }
2012
2013        $new_tmpl->name($new_name);
2014        $new_tmpl->save;
2015    }
2016
2017    $app->add_return_arg( 'saved_copied' => 1 );
2018    $app->call_return;
2019}
2020
2021sub publish_index_templates {
2022    my $app = shift;
2023    $app->validate_magic or return;
2024
2025    # permission check
2026    my $perms = $app->permissions;
2027    return $app->errtrans("Permission denied.")
2028        unless $app->user->is_superuser ||
2029            $perms->can_administer_blog ||
2030            $perms->can_rebuild;
2031
2032    my $blog = $app->blog;
2033    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
2034    TEMPLATE: for my $tmpl (@$templates) {
2035        next TEMPLATE if !defined $tmpl;
2036        next TEMPLATE if $tmpl->blog_id != $blog->id;
2037        next TEMPLATE unless $tmpl->build_type;
2038
2039        $app->rebuild_indexes(
2040            Blog     => $blog,
2041            Template => $tmpl,
2042            Force    => 1,
2043        );
2044    }
2045
2046    $app->call_return( published => 1 );
2047}
2048
2049sub publish_archive_templates {
2050    my $app = shift;
2051    $app->validate_magic or return;
2052
2053    # permission check
2054    my $perms = $app->permissions;
2055    return $app->errtrans("Permission denied.")
2056      unless $app->user->is_superuser
2057      || $perms->can_administer_blog
2058      || $perms->can_rebuild;
2059
2060    my @ids = $app->param('id');
2061    if (scalar @ids == 1) {
2062        # we also support a list of comma-delimited ids like this
2063        @ids = split /,/, $ids[0];
2064    }
2065    return $app->error($app->translate("Invalid request."))
2066        unless @ids;
2067
2068    my $tmpl_id;
2069    my %ats;
2070    require MT::TemplateMap;
2071    while (!$tmpl_id && @ids) {
2072        $tmpl_id = shift @ids;
2073        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl_id } );
2074        foreach my $map (@tmpl_maps) {
2075            next unless $map->build_type;
2076            $ats{ $map->archive_type } = 1;
2077        }
2078        undef $tmpl_id unless keys %ats;
2079    }
2080
2081    # we have a template and archive types to publish!
2082
2083    require MT::CMS::Blog;
2084    my $return_args;
2085    my $reedit = $app->param('reedit');
2086    if (@ids) {
2087        # we have more to do after this, so save the list
2088        # of remaining archive templates...
2089        $return_args = $app->uri_params(
2090            mode => 'publish_archive_templates',
2091            args => {
2092                magic_token => $app->current_magic,
2093                blog_id => scalar $app->param('blog_id'),
2094                id => join(",", @ids),
2095                reedit => $reedit,
2096            }
2097        );
2098    } else {
2099        my $mode = $reedit ? 'view' : 'list';
2100        $return_args = $app->uri_params(
2101            mode => $mode,
2102            args => {
2103                _type     => 'template',
2104                blog_id   => scalar $app->param('blog_id'),
2105                published => 1,
2106                ( $reedit ? ( saved => 1 )       : () ),
2107                ( $reedit ? ( id    => $reedit ) : () ),
2108            }
2109        );
2110    }
2111    $return_args =~ s/^\?//;
2112
2113    $app->return_args( $return_args );
2114    $app->param( 'template_id', $tmpl_id );
2115    $app->param( 'single_template', 1 ); # forces fullscreen mode
2116    $app->param( 'type', join(",", keys %ats) );
2117    return MT::CMS::Blog::start_rebuild_pages($app);
2118}
2119
2120sub save_widget {
2121    my $app = shift;
2122    my $q   = $app->param;
2123
2124    $app->validate_magic() or return;
2125    my $author = $app->user;
2126
2127    my $id = $q->param('id');
2128
2129    if ( !$author->is_superuser ) {
2130        $app->run_callbacks( 'cms_save_permission_filter.template', $app, $id )
2131          || return $app->error(
2132            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2133    }
2134
2135    my $filter_result = $app->run_callbacks( 'cms_save_filter.widgetset', $app );
2136
2137    if ( !$filter_result ) {
2138        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2139    }
2140
2141    my $class = $app->model('template');
2142    my $obj;
2143    if ( $id ) {
2144        $obj = $class->load($id)
2145            or return $app->error($app->translate("Invalid ID [_1]", $id));
2146    }
2147    else {
2148        $obj = $class->new;
2149    }
2150
2151    my $original = $obj->clone();
2152    $obj->name($q->param('name'));
2153    $obj->type('widgetset');
2154    $obj->blog_id( $q->param('blog_id') || 0 );
2155    $obj->modulesets($q->param('modules'));
2156
2157    unless (
2158        $app->run_callbacks( 'cms_pre_save.template', $app, $obj, $original ) )
2159    {
2160        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2161    }
2162
2163    $obj->save
2164      or return $app->error(
2165        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
2166
2167    $app->run_callbacks( 'cms_post_save.template', $app, $obj, $original )
2168      or return $app->error( $app->errstr() );
2169
2170    $app->redirect(
2171        $app->uri(
2172            'mode' => 'edit_widget',
2173            args =>
2174              { blog_id => $obj->blog_id, 'saved' => 1, rebuild => 1, id => $obj->id }
2175        )
2176    );
2177}
2178
2179sub edit_widget {
2180    my $app = shift;
2181    my (%opt) = @_;
2182
2183    my $q       = $app->param();
2184    my $id      = scalar($q->param('id')) || $opt{id};
2185    my $name    = scalar($q->param('name'));
2186    my $blog_id = scalar $q->param('blog_id') || 0;
2187
2188    my $tmpl_class = $app->model('template');
2189    require MT::Promise;
2190    my $obj_promise = MT::Promise::delay(
2191        sub {
2192            return $tmpl_class->load($id) || undef;
2193        }
2194    );
2195
2196    if ( !$app->user->is_superuser ) {
2197        $app->run_callbacks( 'cms_view_permission_filter.template',
2198            $app, $id, $obj_promise )
2199          || return $app->error(
2200            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2201    }
2202
2203    my $param = {
2204        blog_id      => $blog_id,
2205        search_type  => "template",
2206        search_label => MT::Template->class_label_plural,
2207        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2208        exists($opt{error}) ? ( error => $opt{error} ) : (),
2209        exists($opt{saved}) ? ( saved => $opt{saved} ) : (),
2210        $id
2211          ? ( id => $id )
2212          : $name
2213            ? ( name => $name )
2214            : (),
2215    };
2216    if ($blog_id) {
2217        my $blog = $app->blog;
2218        # include_system/include_cache are only applicable
2219        # to blog-level templates
2220        $param->{include_system} = $blog->include_system;
2221        $param->{include_cache} = $blog->include_cache;
2222        $param->{include_with_ssi}      = 0;
2223        $param->{cache_path}            = '';
2224        $param->{cache_enabled}         = 0;
2225        $param->{cache_expire_type}     = 0;
2226        $param->{cache_expire_period}   = '';
2227        $param->{cache_expire_interval} = 0;
2228        $param->{ssi_type} = uc $blog->include_system;
2229    }
2230   
2231    my $iter = $tmpl_class->load_iter(
2232        { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2233        { sort => 'name', direction => 'ascend' }
2234    );
2235
2236    my %all_widgets;
2237    while (my $m = $iter->()) {
2238        next unless $m;
2239        $all_widgets{ $m->id }{name} = $m->name;
2240        $all_widgets{ $m->id }{blog_id} = $m->blog_id;
2241    }
2242
2243    my @inst_modules;
2244    my $wtmpl;
2245    if ( $id ) {
2246        $wtmpl = $obj_promise->force()
2247          or return $app->error(
2248            $app->translate(
2249                "Load failed: [_1]",
2250                $tmpl_class->errstr || $app->translate("(no reason given)")
2251            )
2252          );
2253        $param->{name} = $wtmpl->name;
2254        $param->{include_with_ssi} = $wtmpl->include_with_ssi
2255          if defined $wtmpl->include_with_ssi;
2256        $param->{cache_path}       = $wtmpl->cache_path
2257          if defined $wtmpl->cache_path;
2258        $param->{cache_expire_type} = $wtmpl->cache_expire_type
2259          if defined $wtmpl->cache_expire_type;
2260        my ( $period, $interval ) =
2261          _get_schedule( $wtmpl->cache_expire_interval );
2262        $param->{cache_expire_period}   = $period   if defined $period;
2263        $param->{cache_expire_interval} = $interval if defined $interval;
2264        my @events = split ',', $wtmpl->cache_expire_event;
2265        foreach my $name (@events) {
2266            $param->{ 'cache_expire_event_' . $name } = 1;
2267        }
2268        my $modulesets = $wtmpl->modulesets;
2269        if ( $modulesets ) {
2270            my @modules = split ',', $modulesets;
2271            foreach my $mid ( @modules ) {
2272                push @inst_modules, {
2273                    id => $mid,
2274                    name => $all_widgets{$mid}{name},
2275                    blog_id => $all_widgets{$mid}{blog_id},
2276                };
2277                delete $all_widgets{$mid};
2278            }
2279        }
2280    }
2281    $param->{installed} = \@inst_modules if @inst_modules;
2282    my @avail_modules = map { {
2283        id => $_, name => $all_widgets{$_}{name}, blog_id => $all_widgets{$_}{blog_id}
2284    } } keys %all_widgets;
2285    $param->{available} = \@avail_modules;
2286
2287    my $res = $app->run_callbacks('cms_edit.widgetset', $app, $id, $wtmpl, $param);
2288    if (!$res) {
2289        return $app->error($app->callback_errstr());
2290    }
2291
2292    $app->load_tmpl('edit_widget.tmpl', $param);
2293}
2294
2295sub list_widget {
2296    my $app = shift;
2297    my (%opt) = @_;
2298    my $q = $app->param;
2299
2300    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
2301    return $app->return_to_dashboard( redirect => 1 )
2302      unless $perms || $app->user->is_superuser;
2303    if ( $perms && !$perms->can_edit_templates ) {
2304        return $app->return_to_dashboard( permission => 1 );
2305    }
2306    my $blog_id = $q->param('blog_id') || 0;
2307
2308    my $widget_loop = &build_template_table( $app,
2309        load_args => [ 
2310            { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2311            { sort => 'name', direction => 'ascend' }
2312        ],
2313    );
2314
2315    my $iter = $app->model('template')->load_iter(
2316        { type => 'widgetset', blog_id => $blog_id ? $blog_id : 0 },
2317        { sort => 'name', direction => 'ascend' }
2318    );
2319    my @widgetmanagers;
2320    while ( my $widgetset = $iter->() ) {
2321        next unless $widgetset;
2322        my $ws = { 
2323            id => $widgetset->id,
2324            widgetmanager => $widgetset->name,
2325        };
2326        if ( my $modulesets = $widgetset->modulesets ) {
2327            $ws->{widgets} = $modulesets;
2328            my @names;
2329            foreach my $module ( split ',', $modulesets ) { 
2330                my ( $widget ) = grep { $_->{id} eq $module } @$widget_loop;
2331                push @names, $widget->{name} if $widget;
2332            }
2333            $ws->{names} = join(', ', @names) if @names;
2334        }
2335        push @widgetmanagers, $ws;
2336    }
2337
2338    my @widget_loop;
2339    if ( $blog_id ) {
2340        # Remove system level widgets from the listing
2341        @widget_loop = grep { $_->{blog_id} == $blog_id } @$widget_loop;
2342    }
2343    else {
2344        @widget_loop = @$widget_loop;
2345    }
2346
2347    my $param = {
2348        @widgetmanagers ? ( object_loop  => \@widgetmanagers ) : (),
2349        @widget_loop    ? ( widget_table => \@widget_loop ) : (),
2350        object_type    => "template",
2351        search_type    => "template",
2352        search_label   => MT::Template->class_label_plural,
2353        listing_screen => 1,
2354        screen_id      => "list-widget-set",
2355        $blog_id ? ( blog_view => 1, blog_id => $blog_id ) : (),
2356        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2357        exists($opt{error}) ? ( error => $opt{error} ) : (),
2358        exists($opt{deleted}) ? ( saved => $opt{deleted} ) : ()
2359    };
2360
2361    $app->load_tmpl('list_widget.tmpl', $param);
2362}
2363
2364sub delete_widget {
2365    my $app  = shift;
2366    my $q    = $app->param;
2367    my $type = $q->param('_type');
2368
2369    return $app->errtrans("Invalid request.")
2370      unless $type;
2371
2372    return $app->error( $app->translate("Invalid request.") )
2373      if $app->request_method() ne 'POST';
2374
2375    $app->validate_magic() or return;
2376
2377    my $tmpl_class = $app->model('template');
2378
2379    for my $id ( $q->param('id') ) {
2380        next unless $id;    # avoid 'empty' ids
2381
2382        my $obj = $tmpl_class->load($id);
2383        next unless $obj;
2384        $app->run_callbacks( 'cms_delete_permission_filter.template',
2385            $app, $obj )
2386          || return $app->error(
2387            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2388
2389        $obj->remove
2390          or return $app->errtrans(
2391            'Removing [_1] failed: [_2]',
2392            $app->translate('template'),
2393