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

Revision 2502, 86.1 kB (checked in by fumiakiy, 18 months ago)

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