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

Revision 2052, 80.8 kB (checked in by fumiakiy, 19 months ago)

Integrated Widget Manager to the core. BugId:68750

Now a widgetset is another type of template. The widgets contained in a widgetset is stored in a meta field.

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