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

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

Recovered autosave feature in template edit screen. BugId:79389

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