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

Revision 2334, 84.9 kB (checked in by bchoate, 19 months ago)

Apply variables appropriate for archive type when previewing archive templates.

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