root/branches/release-36/lib/MT/CMS/Template.pm @ 2124

Revision 2124, 83.5 kB (checked in by fumiakiy, 19 months ago)

r2096 was not a good fix for BugId:68410. Fixed the fix.

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