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

Revision 2110, 81.1 kB (checked in by bchoate, 19 months ago)

Fix for template edit links for global templates. BugId:79513

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