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

Revision 2506, 86.8 kB (checked in by takayama, 18 months ago)

Fixed BugId:79981
* When blog_id of loaded widgetset and parameter is unmatched, redirects to the dashboard.

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