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

Revision 2323, 84.5 kB (checked in by auno, 19 months ago)

Return to edit screen after "Save & Publish" for archive templates. BugzID:79729

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