root/branches/release-39/lib/MT/CMS/Template.pm @ 2527

Revision 2527, 86.8 kB (checked in by fumiakiy, 18 months ago)

Added required "require" call.

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