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

Revision 2039, 69.6 kB (checked in by bsmith, 19 months ago)

bugzid:79445 - Simplify Module Caching Options

  • 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_enabled}         = 0;
480            $param->{cache_expire_type}     = 0;
481            $param->{cache_expire_period}   = '';
482            $param->{cache_expire_interval} = 0;
483            $param->{ssi_type} = uc $blog->include_system;
484        }
485        if ($obj) {
486            $param->{include_with_ssi} = $obj->include_with_ssi
487              if defined $obj->include_with_ssi;
488            $param->{cache_path}       = $obj->cache_path
489              if defined $obj->cache_path;
490            $param->{cache_enabled} = $obj->use_cache
491              if defined $obj->use_cache;
492            $param->{cache_expire_type} = $obj->cache_expire_type
493              if defined $obj->cache_expire_type;
494            my ( $period, $interval ) =
495              _get_schedule( $obj->cache_expire_interval );
496            $param->{cache_expire_period}   = $period   if defined $period;
497            $param->{cache_expire_interval} = $interval if defined $interval;
498            my @events = split ',', $obj->cache_expire_event;
499            foreach my $name (@events) {
500                $param->{ 'cache_expire_event_' . $name } = 1;
501            }
502        }
503    }
504
505    # if unset, default to 30 so if they choose to enable caching,
506    # it will be preset to something sane.
507    $param->{cache_expire_interval} ||= 30;
508
509    $param->{dirty} = 1
510        if $app->param('dirty');
511
512    $param->{can_preview} = 1
513        if (!$param->{is_special}) && (!$obj || ($obj && $obj->outfile !~ m/\.(css|xml|rss|js)$/));
514
515    1;
516}
517
518sub list {
519    my $app = shift;
520
521    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
522    return $app->return_to_dashboard( redirect => 1 )
523      unless $perms || $app->user->is_superuser;
524    if ( $perms && !$perms->can_edit_templates ) {
525        return $app->return_to_dashboard( permission => 1 );
526    }
527    my $blog = $app->blog;
528
529    require MT::Template;
530    my $blog_id = $app->param('blog_id') || 0;
531    my $terms = { blog_id => $blog_id };
532    my $args  = { sort    => 'name' };
533
534    my $hasher = sub {
535        my ( $obj, $row ) = @_;
536        my $template_type;
537        my $type = $row->{type} || '';
538        if ( $type =~ m/^(individual|page|category|archive)$/ ) {
539            $template_type = 'archive';
540            # populate context with templatemap loop
541            my $tblog = $obj->blog_id == $blog->id ? $blog : MT::Blog->load( $obj->blog_id );
542            if ($tblog) {
543                $row->{archive_types} = _populate_archive_loop( $app, $tblog, $obj );
544            }
545        }
546        elsif ( $type eq 'widget' ) {
547            $template_type = 'widget';
548        }
549        elsif ( $type eq 'index' ) {
550            $template_type = 'index';
551        }
552        elsif ( $type eq 'custom' ) {
553            $template_type = 'module';
554        }
555        elsif ( $type eq 'email' ) {
556            $template_type = 'email';
557        }
558        elsif ( $type eq 'backup' ) {
559            $template_type = 'backup';
560        }
561        else {
562            $template_type = 'system';
563        }
564        $row->{use_cache} = $obj->use_cache ? 1 : 0;
565        $row->{template_type} = $template_type;
566        $row->{type} = 'entry' if $type eq 'individual';
567        my $published_url = $obj->published_url;
568        $row->{published_url} = $published_url if $published_url;
569    };
570
571    my $params        = {};
572    my $filter = $app->param('filter_key');
573    my $template_type = $filter || '';
574    $template_type =~ s/_templates//;
575
576    $params->{screen_class} = "list-template";
577    $params->{listing_screen} = 1;
578
579    $app->load_list_actions( 'template', $params );
580    $params->{page_actions} = $app->page_actions('list_templates');
581    $params->{search_label} = $app->translate("Templates");
582    $params->{blog_view} = 1;
583    $params->{refreshed} = $app->param('refreshed');
584    $params->{published} = $app->param('published');
585    $params->{saved_copied} = $app->param('saved_copied');
586    $params->{saved_deleted} = $app->param('saved_deleted');
587    $params->{saved} = $app->param('saved');
588
589    # determine list of system template types:
590    my $scope;
591    my $set;
592    if ( $blog ) {
593        $set   = $blog->template_set;
594        $scope = 'system';
595    }
596    else {
597        $scope = 'global:system';
598    }
599    my @tmpl_path = ( $set && ($set ne 'mt_blog')) ? ("template_sets", $set, 'templates', $scope) : ("default_templates", $scope);
600    my $sys_tmpl = MT->registry(@tmpl_path) || {};
601
602    my @tmpl_loop;
603    my %types;
604    if ($template_type ne 'backup') {
605        if ($blog) {
606            # blog template listings
607            %types = ( 
608                'index' => {
609                    label => $app->translate("Index Templates"),
610                    type => 'index',
611                    order => 100,
612                },
613                'archive' => {
614                    label => $app->translate("Archive Templates"),
615                    type => ['archive', 'individual', 'page', 'category'],
616                    order => 200,
617                },
618                'module' => {
619                    label => $app->translate("Template Modules"),
620                    type => 'custom',
621                    order => 300,
622                },
623                'system' => {
624                    label => $app->translate("System Templates"),
625                    type => [ keys %$sys_tmpl ],
626                    order => 400,
627                },
628            );
629        } else {
630            # global template listings
631            %types = ( 
632                'module' => {
633                    label => $app->translate("Template Modules"),
634                    type => 'custom',
635                    order => 100,
636                },
637                'email' => {
638                    label => $app->translate("Email Templates"),
639                    type => 'email',
640                    order => 200,
641                },
642                'system' => {
643                    label => $app->translate("System Templates"),
644                    type => [ keys %$sys_tmpl ],
645                    order => 300,
646                },
647            );
648        }
649    } else {
650        # global template listings
651        %types = ( 
652            'backup' => {
653                label => $app->translate("Template Backups"),
654                type => 'backup',
655                order => 100,
656            },
657        );
658    }
659    my @types = sort { $types{$a}->{order} <=> $types{$b}->{order} } keys %types;
660    if ($template_type) {
661        @types = ( $template_type );
662    }
663    $app->delete_param('filter_key') if $filter;
664    foreach my $tmpl_type (@types) {
665        if ( $tmpl_type eq 'index' ) {
666            $app->param( 'filter_key', 'index_templates' );
667        }
668        elsif ( $tmpl_type eq 'archive' ) {
669            $app->param( 'filter_key', 'archive_templates' );
670        }
671        elsif ( $tmpl_type eq 'system' ) {
672            $app->param( 'filter_key', 'system_templates' );
673        }
674        elsif ( $tmpl_type eq 'email' ) {
675            $app->param( 'filter_key', 'email_templates' );
676        }
677        elsif ( $tmpl_type eq 'module' ) {
678            $app->param( 'filter_key', 'module_templates' );
679        }
680        $terms->{type} = $types{$tmpl_type}->{type};
681        my $tmpl_param = $app->listing(
682            {
683                type     => 'template',
684                terms    => $terms,
685                args     => $args,
686                no_limit => 1,
687                no_html  => 1,
688                code     => $hasher,
689            }
690        );
691
692        my $template_type_label = $types{$tmpl_type}->{label};
693        $tmpl_param->{template_type} = $tmpl_type;
694        $tmpl_param->{template_type_label} = $template_type_label;
695        push @tmpl_loop, $tmpl_param;
696    }
697    if ($filter) {
698        $params->{filter_key} = $filter;
699        $params->{filter_label} = $types{$template_type}{label}
700            if exists $types{$template_type};
701        $app->param('filter_key', $filter);
702    } else {
703        # restore filter_key param (we modified it for the
704        # sake of the individual table listings)
705        $app->delete_param('filter_key');
706    }
707
708    $params->{template_type_loop} = \@tmpl_loop;
709    $params->{screen_id} = "list-template";
710
711    return $app->load_tmpl('list_template.tmpl', $params);
712}
713
714sub preview {
715    my $app         = shift;
716    my $q           = $app->param;
717    my $blog_id     = $q->param('blog_id');
718    my $blog        = $app->blog;
719    my $id          = $q->param('id');
720    my $tmpl;
721    my $user_id = $app->user->id;
722
723    # We can only do previews on blog templates. Have to publish
724    # the preview file somewhere!
725    return $app->errtrans("Invalid request.") unless $blog;
726
727    require MT::Template;
728    if ($id) {
729        $tmpl = MT::Template->load( { id => $id, blog_id => $blog_id } )
730            or return $app->errtrans( "Invalid request." );
731    }
732    else {
733        $tmpl = MT::Template->new;
734        $tmpl->id(-1);
735        $tmpl->blog_id($blog_id);
736    }
737
738    my $names = $tmpl->column_names;
739    my %values = map { $_ => scalar $app->param($_) } @$names;
740    delete $values{'id'} unless $q->param('id');
741
742    ## Strip linefeed characters.
743    for my $col (qw( text )) {
744        $values{$col} =~ tr/\r//d if $values{$col};
745    }
746    $tmpl->set_values( \%values );
747
748    my $preview_basename = $app->preview_object_basename;
749
750    my $type = $tmpl->type;
751    my $preview_tmpl = $tmpl;
752    my $archive_file;
753    my $archive_url;
754    my %param;
755    my $blog_path = $blog->site_path;
756    my $blog_url = $blog->site_url;
757
758    if (($type eq 'custom') || ($type eq 'widget')) {
759        # determine 'host' template
760        $preview_tmpl = MT::Template->load({ blog_id => $blog_id, identifier => 'main_index' });
761        if (!$preview_tmpl) {
762            return $app->errtrans("Can't locate host template to preview module/widget.");
763        }
764        my $req = $app->request;
765        # stash this module so that it is selected through a
766        # MTInclude tag instead of the one in the database:
767        my $tmpl_name = $tmpl->name;
768        $tmpl_name =~ s/^Widget: // if $type eq 'widget';
769        my $stash_id = 'template_' . $type . '::' . $blog_id . '::' . $tmpl_name;
770        $req->stash($stash_id, [ $tmpl, $tmpl->tokens ]);
771    } elsif (($type eq 'individual') || ($type eq 'page')) {
772        my $ctx = $preview_tmpl->context;
773        my $entry_type = $type eq 'individual' ? 'entry' : 'page';
774        my $entry_class = $app->model($entry_type);
775        my $obj = $entry_class->load({
776            blog_id => $blog_id,
777            status => MT::Entry::RELEASE()
778        }, {
779            limit => 1,
780            direction => 'descend',
781            'sort' => 'authored_on'
782        });
783        unless ( $obj ) {
784            # create a dummy object
785            $obj = $entry_class->new;
786            $obj->blog_id($blog_id);
787            $obj->id(-1);
788            $obj->author_id( $app->user->id );
789            $obj->authored_on( $blog->current_timestamp );
790            $obj->status( MT::Entry::RELEASE() );
791            $obj->basename( $preview_basename );
792            $obj->title($app->translate("Lorem ipsum"));
793            my $preview_text = $app->translate('LOREM_IPSUM_TEXT');
794            if ($preview_text eq 'LOREM_IPSUM_TEXT') {
795                $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.};
796            }
797            my $preview_more = $app->translate('LORE_IPSUM_TEXT_MORE');
798            if ($preview_text eq 'LOREM_IPSUM_TEXT_MORE') {
799                $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;
800
801                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.
802
803                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.};
804            }
805            $obj->text($preview_text);
806            $obj->text_more($preview_more);
807            $obj->keywords(MT->translate("sample, entry, preview"));
808            $obj->tags(qw( lorem ipsum sample preview ));
809        }
810        $ctx->stash('entry', $obj);
811        $ctx->{current_archive_type} = $type eq 'individual' ? 'Individual' : 'Page';
812        if (($type eq 'individual') && $blog->archive_path) {
813            $blog_path = $blog->archive_path;
814            $blog_url = $blog->archive_url;
815        }
816        $archive_file = File::Spec->catfile( $blog_path, $obj->archive_file );
817        $archive_url = $obj->archive_url;
818    } elsif ($type eq 'archive') {
819        # some variety of archive template
820    } elsif ($type eq 'index') {
821    } else {
822        # for now, only index templates can be previewed
823        return $app->errtrans("Invalid request.");
824    }
825
826    my $orig_file;
827    my $path;
828
829    # Default case; works for index templates (other template types should
830    # have defined $archive_file by now).
831    $archive_file = File::Spec->catfile( $blog_path, $preview_tmpl->outfile )
832        unless defined $archive_file;
833
834    ( $orig_file, $path ) = File::Basename::fileparse( $archive_file );
835
836    $archive_url = MT::Util::caturl( $blog_url, $orig_file )
837        unless defined $archive_url;
838
839    my $file_ext;
840    require File::Basename;
841    $file_ext = $archive_file;
842    if ($file_ext =~ m/\.[a-z]+$/) {
843        $file_ext =~ s!.+\.!.!;
844    } else {
845        $file_ext = '';
846    }
847    $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext );
848
849    my @data;
850    $app->run_callbacks( 'cms_pre_preview.template', $app, $preview_tmpl, \@data );
851
852    my $has_hires = eval 'require Time::HiRes; 1' ? 1 : 0;
853    my $start_time = $has_hires ? Time::HiRes::time() : time;
854
855    my $ctx = $preview_tmpl->context;
856    my $html = $preview_tmpl->output;
857
858    $param{build_time} = $has_hires ? sprintf("%.3f", Time::HiRes::time() - $start_time ) : "~" . ( time - $start_time );
859
860    unless ( defined($html) ) {
861        return $app->error( $app->translate( "Publish error: [_1]",
862            MT::Util::encode_html( $preview_tmpl->errstr ) ) );
863    }
864
865    # If MT is configured to do 'local' previews, convert all
866    # the normal blog URLs into the domain used by MT itself (ie,
867    # blog is published to www.example.com, which is a different
868    # server from where MT runs, mt.example.com; previews therefore
869    # should occur locally, so replace all http://www.example.com/
870    # with http://mt.example.com/).
871    my ($old_url, $new_url);
872    if ($app->config('LocalPreviews')) {
873        $old_url = $blog_url;
874        $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!;
875        $new_url = $app->base . '/';
876        $html =~ s!\Q$old_url\E!$new_url!g;
877    }
878
879    my $fmgr = $blog->file_mgr;
880
881    ## Determine if we need to build directory structure,
882    ## and build it if we do. DirUmask determines
883    ## directory permissions.
884    require File::Basename;
885    $path =~ s!/$!!
886      unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
887    unless ( $fmgr->exists($path) ) {
888        $fmgr->mkpath($path);
889    }
890
891    if ( $fmgr->exists($path) && $fmgr->can_write($path) ) {
892        $param{preview_file} = $preview_basename;
893        my $preview_url = $archive_url;
894        $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x;
895
896        # We also have to translate the URL used for the
897        # published file to be on the MT app domain.
898        if (defined $new_url) {
899            $preview_url =~ s!^\Q$old_url\E!$new_url!;
900        }
901
902        $param{preview_url}  = $preview_url;
903
904        $fmgr->put_data( $html, $archive_file );
905
906        # we have to make a record of this preview just in case it
907        # isn't cleaned up by re-editing, saving or cancelling on
908        # by the user.
909        require MT::Session;
910        my $sess_obj = MT::Session->get_by_key(
911            {
912                id   => $preview_basename,
913                kind => 'TF',                # TF = Temporary File
914                name => $archive_file,
915            }
916        );
917        $sess_obj->start(time);
918        $sess_obj->save;
919    }
920    else {
921        return $app->error( $app->translate(
922            "Unable to create preview file in this location: [_1]", $path ) );
923    }
924
925    $param{id} = $id if $id;
926    $param{new_object} = $param{id} ? 0 : 1;
927    $param{name} = $tmpl->name;
928    my $cols = $tmpl->column_names;
929    for my $col (@$cols) {
930        push @data,
931          {
932            data_name  => $col,
933            data_value => scalar $q->param($col)
934          };
935    }
936    $param{template_loop} = \@data;
937    $param{object_type}  = $type;
938    return $app->load_tmpl( 'preview_template_strip.tmpl', \%param );
939}
940
941sub reset_blog_templates {
942    my $app   = shift;
943    my $q     = $app->param;
944    my $perms = $app->permissions
945      or return $app->error( $app->translate("No permissions") );
946    return $app->error( $app->translate("Permission denied.") )
947      unless $perms->can_edit_templates;
948    $app->validate_magic() or return;
949    my $blog = MT::Blog->load( $perms->blog_id )
950        or return $app->error($app->translate('Can\'t load blog #[_1].', $perms->blog_id));
951    require MT::Template;
952    my @tmpl = MT::Template->load( { blog_id => $blog->id } );
953
954    for my $tmpl (@tmpl) {
955        $tmpl->remove or return $app->error( $tmpl->errstr );
956    }
957    my $set = $blog ? $blog->template_set : undef;
958    require MT::DefaultTemplates;
959    my $tmpl_list = MT::DefaultTemplates->templates($set) || [];
960    my @arch_tmpl;
961    for my $val (@$tmpl_list) {
962        $val->{name} = $app->translate( $val->{name} );
963        $val->{text} = $app->translate_templatized( $val->{text} );
964        my $tmpl = MT::Template->new;
965        $tmpl->set_values($val);
966        $tmpl->build_dynamic(0);
967        $tmpl->blog_id( $blog->id );
968        $tmpl->save
969          or return $app->error(
970            $app->translate(
971                "Populating blog with default templates failed: [_1]",
972                $tmpl->errstr
973            )
974          );
975
976        # FIXME: enumeration of types
977        if (   $val->{type} eq 'archive'
978            || $val->{type} eq 'category'
979            || $val->{type} eq 'page'
980            || $val->{type} eq 'individual' )
981        {
982            push @arch_tmpl, $tmpl;
983        }
984    }
985
986    ## Set up mappings from new templates to archive types.
987    for my $tmpl (@arch_tmpl) {
988        my (@at);
989
990        # FIXME: enumeration of types
991        if ( $tmpl->type eq 'archive' ) {
992            @at = qw( Daily Weekly Monthly Category );
993        }
994        elsif ( $tmpl->type eq 'page' ) {
995            @at = qw( Page );
996        }
997        elsif ( $tmpl->type eq 'individual' ) {
998            @at = qw( Individual );
999        }
1000        require MT::TemplateMap;
1001        for my $at (@at) {
1002            my $map = MT::TemplateMap->new;
1003            $map->archive_type($at);
1004            $map->is_preferred(1);
1005            $map->template_id( $tmpl->id );
1006            $map->blog_id( $tmpl->blog_id );
1007            $map->save
1008              or return $app->error(
1009                $app->translate(
1010                    "Setting up mappings failed: [_1]",
1011                    $map->errstr
1012                )
1013              );
1014        }
1015    }
1016    $app->redirect(
1017        $app->uri(
1018            'mode' => 'list',
1019            args =>
1020              { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 }
1021        )
1022    );
1023}
1024
1025sub _generate_map_table {
1026    my $app = shift;
1027    my ( $blog_id, $template_id ) = @_;
1028
1029    require MT::Template;
1030    require MT::Blog;
1031    my $blog     = MT::Blog->load($blog_id);
1032    my $template = MT::Template->load($template_id);
1033    my $tmpl     = $app->load_tmpl('include/archive_maps.tmpl');
1034    my $maps     = _populate_archive_loop( $app, $blog, $template );
1035    $tmpl->param( object_type => 'templatemap' );
1036    $tmpl->param( publish_queue_available => eval 'require List::Util; require Scalar::Util; 1;' );
1037    $tmpl->param( object_loop => $maps ) if @$maps;
1038    my $html = $tmpl->output();
1039
1040    if ( $html =~ m/<__trans / ) {
1041        $html = $app->translate_templatized($html);
1042    }
1043    $html;
1044}
1045
1046sub _populate_archive_loop {
1047    my $app = shift;
1048    my ( $blog, $obj ) = @_;
1049
1050    my $index = $app->config('IndexBasename');
1051    my $ext = $blog->file_extension || '';
1052    $ext = '.' . $ext if $ext ne '';
1053
1054    require MT::TemplateMap;
1055    my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } );
1056    my @maps;
1057    my %types;
1058    foreach my $map_obj (@tmpl_maps) {
1059        my $map = {};
1060        $map->{map_id}           = $map_obj->id;
1061        $map->{map_is_preferred} = $map_obj->is_preferred;
1062        # publish options
1063        $map->{map_build_type} = $map_obj->build_type;
1064        $map->{ 'map_build_type_' . ( $map_obj->build_type || 0 ) } = 1;
1065        my ( $period, $interval ) = _get_schedule( $map_obj->build_interval );
1066        $map->{ 'map_schedule_period_' . $period } = 1
1067            if defined $period;
1068        $map->{map_schedule_interval} = $interval
1069            if defined $interval;
1070
1071        my $at = $map->{archive_type} = $map_obj->archive_type;
1072        $types{$at}++;
1073        $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1
1074          if $blog->archive_type_preferred;
1075        $map->{file_template} = $map_obj->file_template
1076          if $map_obj->file_template;
1077
1078        my $archiver = $app->publisher->archiver($at);
1079        next unless $archiver;
1080        $map->{archive_label} = $archiver->archive_label;
1081        my $tmpls     = $archiver->default_archive_templates;
1082        my $tmpl_loop = [];
1083        foreach (@$tmpls) {
1084            my $name = $_->{label};
1085            $name =~ s/\.html$/$ext/;
1086            $name =~ s/index$ext$/$index$ext/;
1087            push @$tmpl_loop,
1088              {
1089                name    => $name,
1090                value   => $_->{template},
1091                default => ( $_->{default} || 0 ),
1092              };
1093        }
1094
1095        my $custom = 1;
1096
1097        foreach (@$tmpl_loop) {
1098            if (   ( !$map->{file_template} && $_->{default} )
1099                || ( $map->{file_template} eq $_->{value} ) )
1100            {
1101                $_->{selected}        = 1;
1102                $custom               = 0;
1103                $map->{file_template} = $_->{value}
1104                  if !$map->{file_template};
1105            }
1106        }
1107        if ($custom) {
1108            unshift @$tmpl_loop,
1109              {
1110                name     => $map->{file_template},
1111                value    => $map->{file_template},
1112                selected => 1,
1113              };
1114        }
1115
1116        $map->{archive_tmpl_loop} = $tmpl_loop;
1117        if (
1118            1 < MT::TemplateMap->count(
1119                { archive_type => $at, blog_id => $obj->blog_id }
1120            )
1121          )
1122        {
1123            $map->{has_multiple_archives} = 1;
1124        }
1125
1126        push @maps, $map;
1127    }
1128    @maps = sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @maps;
1129    return \@maps;
1130}
1131
1132sub delete_map {
1133    my $app = shift;
1134    $app->validate_magic() or return;
1135    my $perms = $app->{perms}
1136      or return $app->error( $app->translate("No permissions") );
1137    my $q  = $app->param;
1138    my $id = $q->param('id');
1139
1140    require MT::TemplateMap;
1141    MT::TemplateMap->remove( { id => $id } );
1142    my $html =
1143      _generate_map_table( $app, $q->param('blog_id'),
1144        $q->param('template_id') );
1145    $app->{no_print_body} = 1;
1146    $app->send_http_header("text/plain");
1147    $app->print($html);
1148}
1149
1150sub add_map {
1151    my $app = shift;
1152    $app->validate_magic() or return;
1153    my $perms = $app->{perms}
1154      or return $app->error( $app->translate("No permissions") );
1155
1156    my $q = $app->param;
1157
1158    require MT::TemplateMap;
1159    my $blog_id = $q->param('blog_id');
1160    my $at      = $q->param('new_archive_type');
1161    my $exist   = MT::TemplateMap->exist(
1162        {
1163            blog_id      => $blog_id,
1164            archive_type => $at
1165        }
1166    );
1167    my $map = MT::TemplateMap->new;
1168    $map->is_preferred( $exist ? 0 : 1 );
1169    $map->template_id( scalar $q->param('template_id') );
1170    $map->blog_id($blog_id);
1171    $map->archive_type($at);
1172    $map->save
1173      or return $app->error(
1174        $app->translate( "Saving map failed: [_1]", $map->errstr ) );
1175    my $html =
1176      _generate_map_table( $app, $blog_id, scalar $q->param('template_id') );
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->cache_path( $app->param('cache_path'));
1240    $obj->use_cache( $app->param('cache_enabled')           ? 1 : 0 );
1241    my $cache_expire_type = $app->param('cache_expire_type');
1242    $obj->cache_expire_type($cache_expire_type);
1243    my $period   = $app->param('cache_expire_period');
1244    my $interval = $app->param('cache_expire_interval');
1245    my $sec      = _get_interval( $period, $interval );
1246    $obj->cache_expire_interval($sec);
1247    my $q = $app->param;
1248    my @events;
1249
1250    foreach my $name ( $q->param('cache_expire_event') ) {
1251        push @events, $name;
1252    }
1253    $obj->cache_expire_event( join ',', @events );
1254    if ( $cache_expire_type == 1 ) {
1255        return $eh->error(
1256            $app->translate("You should not be able to enter 0 as the time.") )
1257          if $interval == 0;
1258    }
1259    elsif ( $cache_expire_type == 2 ) {
1260        return $eh->error(
1261            $app->translate("You must select at least one event checkbox.") )
1262          if !@events;
1263    }
1264
1265    require MT::PublishOption;
1266    my $build_type = $app->param('build_type');
1267
1268    if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1269        my $period   = $app->param('schedule_period');
1270        my $interval = $app->param('schedule_interval');
1271        my $sec      = _get_interval( $period, $interval );
1272        $obj->build_interval($sec);
1273    }
1274    my $rebuild_me = 1;
1275    if (   $build_type == MT::PublishOption::DISABLED()
1276        || $build_type == MT::PublishOption::MANUALLY() )
1277    {
1278        $rebuild_me = 0;
1279    }
1280    $obj->rebuild_me($rebuild_me);
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        $row->{use_cache} = $tmpl->use_cache ? 1 : 0;
1413
1414        # FIXME: enumeration of types
1415        $row->{can_delete} = 1
1416          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1417        if ($blog) {
1418            $row->{weblog_name} = $blog->name;
1419        }
1420        elsif ($tmpl->blog_id) {
1421            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1422        }
1423        else {
1424            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1425        }
1426        $row->{object} = $tmpl;
1427        push @data, $row;
1428        last if defined($limit) && (@data > $limit);
1429    }
1430    return [] unless @data;
1431
1432    $param->{template_table}[0]              = {%$list_pref};
1433    $param->{template_table}[0]{object_loop} = \@data;
1434    $param->{template_table}[0]{object_type} = 'template';
1435    $app->load_list_actions( 'template', $param );
1436    $param->{object_loop} = \@data;
1437    \@data;
1438}
1439
1440sub dialog_publishing_profile {
1441    my $app = shift;
1442    $app->validate_magic or return;
1443
1444    my $blog = $app->blog;
1445    $app->assert( $blog ) or return;
1446
1447    # permission check
1448    my $perms = $app->permissions;
1449    return $app->errtrans("Permission denied.")
1450        unless $app->user->is_superuser ||
1451            $perms->can_administer_blog ||
1452            $perms->can_edit_templates;
1453
1454    my $param = {};
1455    $param->{dynamicity} = $blog->custom_dynamic_templates || 'none';
1456    $param->{screen_id} = "publishing-profile-dialog";
1457    $param->{return_args} = $app->param('return_args');
1458
1459    $app->build_page('dialog/publishing_profile.tmpl',
1460        $param);
1461}
1462
1463sub dialog_refresh_templates {
1464    my $app = shift;
1465    $app->validate_magic or return;
1466
1467    # permission check
1468    my $perms = $app->permissions;
1469    return $app->errtrans("Permission denied.")
1470        unless $app->user->is_superuser ||
1471            $perms->can_administer_blog ||
1472            $perms->can_edit_templates;
1473
1474    my $param = {};
1475    my $blog = $app->blog;
1476    $param->{return_args} = $app->param('return_args');
1477
1478    if ($blog) {
1479        $param->{blog_id} = $blog->id;
1480
1481        my $sets = $app->registry("template_sets");
1482        $sets->{$_}{key} = $_ for keys %$sets;
1483        $sets = $app->filter_conditional_list([ values %$sets ]);
1484
1485        no warnings; # some sets may not define an order
1486        @$sets = sort { $a->{order} <=> $b->{order} } @$sets;
1487        $param->{'template_set_loop'} = $sets;
1488
1489        my $existing_set = $blog->template_set || 'mt_blog';
1490        foreach (@$sets) {
1491            if ($_->{key} eq $existing_set) {
1492                $_->{selected} = 1;
1493            }
1494        }
1495        $param->{'template_set_index'} = $#$sets;
1496        $param->{'template_set_count'} = scalar @$sets;
1497
1498        $param->{template_sets} = $sets;
1499        $param->{screen_id} = "refresh-templates-dialog";
1500    }
1501
1502    # load template sets
1503    $app->build_page('dialog/refresh_templates.tmpl',
1504        $param);
1505}
1506
1507sub refresh_all_templates {
1508    my ($app) = @_;
1509
1510    my $backup = 0;
1511    if ($app->param('backup')) {
1512        # refresh templates dialog uses a 'backup' field
1513        $backup = 1;
1514    }
1515
1516    my $template_set = $app->param('template_set');
1517    my $refresh_type = $app->param('refresh_type') || 'refresh';
1518
1519    my $t = time;
1520
1521    my @id;
1522    if ($app->param('blog_id')) {
1523        @id = ( scalar $app->param('blog_id') );
1524    }
1525    else {
1526        @id = $app->param('id');
1527        if (! @id) {
1528            # refresh global templates
1529            @id = ( 0 );
1530        }
1531    }
1532
1533    require MT::Template;
1534    require MT::DefaultTemplates;
1535    require MT::Blog;
1536    require MT::Permission;
1537    require MT::Util;
1538
1539    foreach my $blog_id (@id) {
1540        my $blog;
1541        if ($blog_id) {
1542            $blog = MT::Blog->load($blog_id);
1543            next unless $blog;
1544        }
1545        if ( !$app->user->is_superuser() ) {
1546            my $perms = MT::Permission->load(
1547                { blog_id => $blog_id, author_id => $app->user->id } );
1548            if (
1549                !$perms
1550                || (   !$perms->can_edit_templates()
1551                    && !$perms->can_administer_blog() )
1552              )
1553            {
1554                next;
1555            }
1556        }
1557
1558        my $tmpl_list;
1559        if ($blog_id) {
1560
1561            if ($refresh_type eq 'clean') {
1562                # the user wants to back up all templates and
1563                # install the new ones
1564
1565                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1566                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1567                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1568
1569                my $tmpl_iter = MT::Template->load_iter({
1570                    blog_id => $blog_id,
1571                    type => { not => 'backup' },
1572                });
1573
1574                while (my $tmpl = $tmpl_iter->()) {
1575                    if ($backup) {
1576                        # zap all template maps
1577                        require MT::TemplateMap;
1578                        MT::TemplateMap->remove({
1579                            template_id => $tmpl->id,
1580                        });
1581                        $tmpl->type('backup');
1582                        $tmpl->name(
1583                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1584                        $tmpl->identifier(undef);
1585                        $tmpl->rebuild_me(0);
1586                        $tmpl->linked_file(undef);
1587                        $tmpl->outfile('');
1588                        $tmpl->save;
1589                    } else {
1590                        $tmpl->remove;
1591                    }
1592                }
1593
1594                # This also creates our template mappings
1595                $blog->create_default_templates( $template_set ||
1596                    $blog->template_set || 'mt_blog' );
1597
1598                if ($template_set) {
1599                    $blog->template_set( $template_set );
1600                    $blog->save;
1601                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1602                }
1603
1604                next;
1605            }
1606
1607            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1608        }
1609        else {
1610            $tmpl_list = MT::DefaultTemplates->templates();
1611        }
1612
1613        foreach my $val (@$tmpl_list) {
1614            if ($blog_id) {
1615                # when refreshing blog templates,
1616                # skip over global templates which
1617                # specify a blog_id of 0...
1618                next if $val->{global};
1619            }
1620            else {
1621                next unless exists $val->{global};
1622            }
1623
1624            if ( !$val->{orig_name} ) {
1625                $val->{orig_name} = $val->{name};
1626                $val->{name}      = $app->translate( $val->{name} );
1627                $val->{text}      = $app->translate_templatized( $val->{text} );
1628            }
1629
1630            my $orig_name = $val->{orig_name};
1631
1632            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1633            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1634              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1635
1636            my $terms = {};
1637            $terms->{blog_id} = $blog_id;
1638            $terms->{type} = $val->{type};
1639            if ( $val->{type} =~
1640                m/^(archive|individual|page|category|index|custom|widget)$/ )
1641            {
1642                $terms->{name} = $val->{name};
1643            }
1644            else {
1645                $terms->{identifier} = $val->{identifier};
1646            }
1647
1648            # this should only return 1 template; we're searching
1649            # within a given blog for a specific type of template (for
1650            # "system" templates; or for a type + name, which should be
1651            # unique for that blog.
1652            my $tmpl = MT::Template->load($terms);
1653            if ($tmpl && $backup) {
1654
1655                # check for default template text...
1656                # if it is a default template, then outright replace it
1657                my $text = $tmpl->text;
1658                $text =~ s/\s+//g;
1659
1660                my $def_text = $val->{text};
1661                $def_text =~ s/\s+//g;
1662
1663                # if it has been customized, back it up to a new tmpl record
1664                if ($def_text ne $text) {
1665                    my $backup = $tmpl->clone;
1666                    delete $backup->{column_values}
1667                      ->{id};    # make sure we don't overwrite original
1668                    delete $backup->{changed_cols}->{id};
1669                    $backup->name(
1670                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1671                    $backup->type('backup');
1672                    # if ( $backup->type !~
1673                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1674                    # {
1675                    #     $backup->type('custom')
1676                    #       ;      # system templates can't be created
1677                    # }
1678                    $backup->outfile('');
1679                    $backup->linked_file( $tmpl->linked_file );
1680                    $backup->identifier(undef);
1681                    $backup->rebuild_me(0);
1682                    $backup->build_dynamic(0);
1683                    $backup->save;
1684                }
1685            }
1686            if ($tmpl) {
1687                # we found that the previous template had not been
1688                # altered, so replace it with new default template...
1689                $tmpl->text( $val->{text} );
1690                $tmpl->identifier( $val->{identifier} );
1691                $tmpl->type( $val->{type} )
1692                  ; # fixes mismatch of types for cases like "archive" => "individual"
1693                $tmpl->linked_file('');
1694                $tmpl->save;
1695            }
1696            else {
1697                # create this one...
1698                my $tmpl = new MT::Template;
1699                $tmpl->build_dynamic(0);
1700                $tmpl->set_values(
1701                    {
1702                        text       => $val->{text},
1703                        name       => $val->{name},
1704                        type       => $val->{type},
1705                        identifier => $val->{identifier},
1706                        outfile    => $val->{outfile},
1707                        rebuild_me => $val->{rebuild_me},
1708                    }
1709                );
1710                $tmpl->blog_id($blog_id);
1711                $tmpl->save
1712                  or return $app->error(
1713                        $app->translate("Error creating new template: ")
1714                      . $tmpl->errstr );
1715            }
1716        }
1717    }
1718
1719    $app->add_return_arg( 'refreshed' => 1 );
1720    $app->call_return;
1721}
1722
1723sub refresh_individual_templates {
1724    my ($app) = @_;
1725
1726    require MT::Util;
1727
1728    my $user = $app->user;
1729    my $perms = $app->permissions;
1730    return $app->error(
1731        $app->translate(
1732            "Permission denied.")
1733      )
1734      #TODO: system level-designer permission
1735      unless $user->is_superuser() || $user->can_edit_templates()
1736      || ( $perms
1737        && ( $perms->can_edit_templates()
1738          || $perms->can_administer_blog ) );
1739
1740    my $set;
1741    if ( my $blog_id = $app->param('blog_id') ) {
1742        my $blog = $app->model('blog')->load($blog_id)
1743            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1744        $set = $blog->template_set()
1745            if $blog;
1746    }
1747
1748    require MT::DefaultTemplates;
1749    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1750
1751    my $tmpl_types = {};
1752    my $tmpl_ids   = {};
1753    my $tmpls      = {};
1754    foreach my $tmpl (@$tmpl_list) {
1755        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1756        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1757            if $tmpl->{identifier};
1758        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1759        {
1760            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1761        }
1762        else {
1763            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1764        }
1765    }
1766
1767    my $t = time;
1768
1769    my @msg;
1770    my @id = $app->param('id');
1771    require MT::Template;
1772    foreach my $tmpl_id (@id) {
1773        my $tmpl = MT::Template->load($tmpl_id);
1774        next unless $tmpl;
1775        my $blog_id = $tmpl->blog_id;
1776
1777        # FIXME: permission check -- for this blog_id
1778
1779        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1780        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1781          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1782
1783        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1784          || $tmpl_types->{ $tmpl->type() }
1785          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1786        if ( !$val ) {
1787            push @msg,
1788              $app->translate(
1789"Skipping template '[_1]' since it appears to be a custom template.",
1790                $tmpl->name
1791              );
1792            next;
1793        }
1794
1795        my $text = $tmpl->text;
1796        $text =~ s/\s+//g;
1797
1798        my $def_text = $val->{text};
1799        $def_text =~ s/\s+//g;
1800
1801        if ($text ne $def_text) {
1802            # if it has been customized, back it up to a new tmpl record
1803            my $backup = $tmpl->clone;
1804            delete $backup->{column_values}
1805              ->{id};    # make sure we don't overwrite original
1806            delete $backup->{changed_cols}->{id};
1807            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1808            $backup->type('backup');
1809            $backup->outfile('');
1810            $backup->linked_file( $tmpl->linked_file );
1811            $backup->rebuild_me(0);
1812            $backup->build_dynamic(0);
1813            $backup->identifier(undef);
1814            $backup->save;
1815            push @msg,
1816              $app->translate(
1817    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1818                  $blog_id, $backup->id, $tmpl->name );
1819
1820            # we found that the previous template had not been
1821            # altered, so replace it with new default template...
1822            $tmpl->text( $val->{text} );
1823            $tmpl->identifier( $val->{identifier} );
1824            $tmpl->linked_file('');
1825            $tmpl->save;
1826        } else {
1827            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
1828        }
1829    }
1830    my @msg_loop;
1831    push @msg_loop, { message => $_ } foreach @msg;
1832
1833    $app->build_page( 'refresh_results.tmpl',
1834        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1835}
1836
1837sub clone_templates {
1838    my ($app) = @_;
1839
1840    my $user = $app->user;
1841    my $perms = $app->permissions;
1842    return $app->error(
1843        $app->translate(
1844            "Permission denied.")
1845      )
1846      #TODO: system level-designer permission
1847      unless $user->is_superuser() || $user->can_edit_templates()
1848      || ( $perms
1849        && ( $perms->can_edit_templates()
1850          || $perms->can_administer_blog ) );
1851
1852    my @id = $app->param('id');
1853    require MT::Template;
1854    foreach my $tmpl_id (@id) {
1855        my $tmpl = MT::Template->load($tmpl_id);
1856        next unless $tmpl;
1857
1858        my $new_tmpl = $tmpl->clone({
1859            Except => {
1860                id => 1,
1861                name => 1,
1862                identifier => 1,
1863            },
1864        });
1865
1866        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
1867        my $new_name = $new_basename;
1868        my $i = 0;
1869        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
1870            $new_name = $new_basename . ' (' . ++$i . ')';
1871        }
1872
1873        $new_tmpl->name($new_name);
1874        $new_tmpl->save;
1875    }
1876
1877    $app->add_return_arg( 'saved_copied' => 1 );
1878    $app->call_return;
1879}
1880
1881sub publish_index_templates {
1882    my $app = shift;
1883    $app->validate_magic or return;
1884
1885    # permission check
1886    my $perms = $app->permissions;
1887    return $app->errtrans("Permission denied.")
1888        unless $app->user->is_superuser ||
1889            $perms->can_administer_blog ||
1890            $perms->can_rebuild;
1891
1892    my $blog = $app->blog;
1893    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
1894    TEMPLATE: for my $tmpl (@$templates) {
1895        next TEMPLATE if !defined $tmpl;
1896        next TEMPLATE if $tmpl->blog_id != $blog->id;
1897        next TEMPLATE unless $tmpl->build_type;
1898
1899        $app->rebuild_indexes(
1900            Blog     => $blog,
1901            Template => $tmpl,
1902            Force    => 1,
1903        );
1904    }
1905
1906    $app->call_return( published => 1 );
1907}
1908
1909sub publish_archive_templates {
1910    my $app = shift;
1911    $app->validate_magic or return;
1912
1913    # permission check
1914    my $perms = $app->permissions;
1915    return $app->errtrans("Permission denied.")
1916      unless $app->user->is_superuser
1917      || $perms->can_administer_blog
1918      || $perms->can_rebuild;
1919
1920    my $blog = $app->blog;
1921    my $templates =
1922      MT->model('template')->lookup_multi( [ $app->param('id') ] );
1923    require MT::TemplateMap;
1924    # FIXME: Need multi-request support!
1925    TEMPLATE: for my $tmpl (@$templates) {
1926        next TEMPLATE if !defined $tmpl;
1927        next TEMPLATE if $tmpl->blog_id != $blog->id;
1928        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl->id } );
1929        foreach my $map (@tmpl_maps) {
1930            next unless $map->build_type;
1931            $app->rebuild(
1932                Blog        => $blog,
1933                ArchiveType => $map->archive_type,
1934                TemplateMap => $map,
1935                NoIndexes   => 1,
1936                Force       => 1,
1937            );
1938        }
1939    }
1940
1941    $app->call_return( published => 1 );
1942}
1943
1944{
1945    my @period_options = (
1946        {
1947            name => 'minutes',
1948            expr => 60,
1949        },
1950        {
1951            name => 'hours',
1952            expr => 60 * 60,
1953        },
1954        {
1955            name => 'days',
1956            expr => 24 * 60 * 60,
1957        },
1958    );
1959
1960    sub _get_schedule {
1961        my ($sec) = @_;
1962        return unless defined $sec;
1963        my ( $period, $interval );
1964        for (@period_options) {
1965            last if $sec % $_->{expr};
1966            $period   = $_->{name};
1967            $interval = $sec / $_->{expr};
1968        }
1969        ( $period, $interval );
1970    }
1971
1972    sub _get_interval {
1973        my ( $period, $interval ) = @_;
1974        return unless defined $period;
1975        my $sec = 0;
1976        for (@period_options) {
1977            if ( $_->{name} eq $period ) {
1978                $sec = $interval * $_->{expr};
1979                last;
1980            }
1981        }
1982        $sec;
1983    }
1984}
1985
19861;
Note: See TracBrowser for help on using the browser.