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

Revision 2119, 83.5 kB (checked in by bchoate, 19 months ago)

Support for category, date-based archive template previews. BugId:79519

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