root/branches/release-35/lib/MT/CMS/Template.pm @ 1913

Revision 1913, 69.0 kB (checked in by takayama, 20 months ago)

Fixed BugId:79313
* Changed to show included module's error

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