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

Revision 2096, 81.0 kB (checked in by fumiakiy, 19 months ago)

Redirect to dashboard if the specified type is not appropriate for system level template edit. BugId:68410

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