root/branches/release-38/lib/MT/CMS/Template.pm @ 2382

Revision 2382, 84.8 kB (checked in by auno, 19 months ago)

Fixed to redirect same screen after publishing for archive template. BugzID:79797

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