root/branches/release-41/lib/MT/CMS/Template.pm @ 2676

Revision 2676, 87.2 kB (checked in by auno, 17 months ago)

Revised parameter to use blog_id rather than blog_name. BugzID:80385

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