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

Revision 1972, 69.4 kB (checked in by takayama, 20 months ago)

Fixed BugId:79276
* Exclude 'clone template' from list action in template editing screen when template type is system or email.

  • 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        my $filter = $app->param('filter_key');
104        if ($param->{template_group} eq 'email') {
105            $app->param( 'filter_key', 'email_templates' );
106        }elsif  ($param->{template_group} eq 'system') {
107            $app->param( 'filter_key', 'system_templates' );
108        }
109        $app->load_list_actions( 'template', $param );
110        $app->param( 'filter_key', $filter );
111
112        $obj->compile;
113        if ( $obj->{errors} && @{ $obj->{errors} } ) {
114            $param->{error} = $app->translate(
115                "One or more errors were found in this template.");
116            $param->{error} .= "<ul>\n";
117            foreach my $err ( @{ $obj->{errors} } ) {
118                $param->{error} .= "<li>"
119                  . MT::Util::encode_html( $err->{message} )
120                  . "</li>\n";
121            }
122            $param->{error} .= "</ul>\n";
123        }
124
125        # Populate list of included templates
126        if ( my $includes = $obj->getElementsByTagName('Include') ) {
127            my @includes;
128            my @widgets;
129            my %seen;
130            foreach my $tag (@$includes) {
131                my $include = {};
132                my $mod = $include->{include_module} = $tag->[1]->{module} || $tag->[1]->{widget};
133                next unless $mod;
134                my $type = $tag->[1]->{widget} ? 'widget' : 'custom';
135                next if exists $seen{$type}{$mod};
136                $seen{$type}{$mod} = 1;
137                my $other = MT::Template->load(
138                    {
139                        blog_id => [ $obj->blog_id, 0 ],
140                        name    => $mod,
141                        type    => $type,
142                    }, {
143                        sort      => 'blog_id',
144                        direction => 'descend',
145                    }
146                );
147                if ($other) {
148                    $include->{include_link} = $app->mt_uri(
149                        mode => 'view',
150                        args => {
151                            blog_id => $other->blog_id || 0,
152                            '_type' => 'template',
153                            id      => $other->id
154                        }
155                    );
156                    # Try to compile template module if using MTInclude in this template.
157                    $other->compile;
158                    if ( $other->{errors} && @{ $other->{errors} } ) {
159                        $param->{error} = $app->translate(
160                            "One or more errors were found in included template module (".$other->name.").");
161                        $param->{error} .= "<ul>\n";
162                        foreach my $err ( @{ $other->{errors} } ) {
163                            $param->{error} .= "<li>"
164                              . MT::Util::encode_html( $err->{message} )
165                              . "</li>\n";
166                        }
167                        $param->{error} .= "</ul>\n";
168                    }
169                }
170                else {
171                    $include->{create_link} = $app->mt_uri(
172                        mode => 'view',
173                        args => {
174                            blog_id => $obj->blog_id,
175                            '_type' => 'template',
176                            type    => $type,
177                            name    => $mod,
178                        }
179                    );
180                }
181                if ($type eq 'widget') {
182                    push @widgets, $include;
183                } else {
184                    push @includes, $include;
185                }
186            }
187            $param->{include_loop} = \@includes if @includes;
188            $param->{widget_loop} = \@widgets if @widgets;
189        }
190        my @sets = ( @{ $obj->getElementsByTagName('WidgetSet') || [] }, @{ $obj->getElementsByTagName('WidgetManager') || [] } );
191        if ( @sets ) {
192            my @widget_sets;
193            my %seen;
194            foreach my $set (@sets) {
195                my $name = $set->[1]->{name};
196                next unless $name;
197                next if $seen{$name};
198                $seen{$name} = 1;
199                push @widget_sets, {
200                    include_link => $app->mt_uri(
201                        mode => 'edit_widget',
202                        args => {
203                            blog_id => $obj->blog_id,
204                            widgetmanager => $name,
205                        },
206                    ),
207                    include_module => $name,
208                };
209            }
210            $param->{widget_set_loop} = \@widget_sets if @widget_sets;
211        }
212        $param->{have_includes} = 1 if $param->{widget_set_loop} || $param->{include_loop} || $param->{widget_loop};
213        # Populate archive types for creating new map
214        my $obj_type = $obj->type;
215        if (   $obj_type eq 'individual'
216            || $obj_type eq 'page'
217            || $obj_type eq 'author'
218            || $obj_type eq 'category'
219            || $obj_type eq 'archive' )
220        {
221            my @at = $app->publisher->archive_types;
222            my @archive_types;
223            for my $at (@at) {
224                my $archiver      = $app->publisher->archiver($at);
225                my $archive_label = $archiver->archive_label;
226                $archive_label = $at unless $archive_label;
227                $archive_label = $archive_label->()
228                  if ( ref $archive_label ) eq 'CODE';
229                if (   ( $obj_type eq 'archive' )
230                    || ( $obj_type eq 'author' )
231                    || ( $obj_type eq 'category' ) )
232                {
233
234                    # only include if it is NOT an entry-based archive type
235                    next if $archiver->entry_based;
236                }
237                elsif ( $obj_type eq 'page' ) {
238                    # only include if it is a entry-based archive type and page
239                    next unless $archiver->entry_based;
240                    next if $archiver->entry_class ne 'page';
241                }
242                elsif ( $obj_type eq 'individual' ) {
243                    # only include if it is a entry-based archive type and entry
244                    next unless $archiver->entry_based;
245                    next if $archiver->entry_class eq 'page';
246                }
247                push @archive_types,
248                  {
249                    archive_type_translated => $archive_label,
250                    archive_type            => $at,
251                  };
252                @archive_types =
253                  sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @archive_types;
254            }
255            $param->{archive_types} = \@archive_types;
256
257            # Populate template maps for this template
258            my $maps = _populate_archive_loop( $app, $blog, $obj );
259            if (@$maps) {
260                $param->{object_loop} = $param->{template_map_loop} = $maps
261                  if @$maps;
262                my %archive_types = map { $_->{archive_label} => () } @$maps;
263                $param->{enabled_archive_types} = join(", ", sort keys %archive_types);
264            }
265        }
266        # publish options
267        $param->{publish_queue_available} = eval 'require List::Util; require Scalar::Util; 1;';
268        $param->{build_type} = $obj->build_type;
269        $param->{ 'build_type_' . ( $obj->build_type || 0 ) } = 1;
270        #my ( $period, $interval ) = _get_schedule( $obj->build_interval );
271        #$param->{ 'schedule_period_' . $period } = 1;
272        #$param->{schedule_interval} = $interval;
273        $param->{type} = 'custom' if $param->{type} eq 'module';
274    } else {
275        my $new_tmpl = $q->param('create_new_template');
276        my $template_type;
277        if ($new_tmpl) {
278            if ( $new_tmpl =~ m/^blank:(.+)/ ) {
279                $template_type = $1;
280                $param->{type} = $1;
281            }
282            elsif ( $new_tmpl =~ m/^default:([^:]+):(.+)/ ) {
283                $template_type = $1;
284                $template_type = 'custom' if $template_type eq 'module';
285                my $template_id = $2;
286                my $set = $blog ? $blog->template_set : undef;
287                require MT::DefaultTemplates;
288                my $def_tmpl = MT::DefaultTemplates->templates($set) || [];
289                my ($tmpl) =
290                  grep { $_->{identifier} eq $template_id } @$def_tmpl;
291                $param->{text} = $app->translate_templatized( $tmpl->{text} )
292                  if $tmpl;
293                $param->{type} = $template_type;
294            }
295        }
296        else {
297            $template_type = $q->param('type');
298            $template_type = 'custom' if 'module' eq $template_type;
299            $param->{type}   = $template_type;
300        }
301        return $app->errtrans("Create template requires type")
302          unless $template_type;
303        $param->{nav_templates} = 1;
304        my $tab;
305
306        # FIXME: enumeration of types
307        if ( $template_type eq 'index' ) {
308            $tab = 'index';
309            $param->{template_group_trans} = $app->translate('index');
310        }
311        elsif ($template_type eq 'archive'
312            || $template_type eq 'individual'
313            || $template_type eq 'category'
314            || $template_type eq 'page' )
315        {
316            $tab                         = 'archive';
317            $param->{template_group_trans} = $app->translate('archive');
318            $param->{type_archive}         = 1;
319            my @types = (
320                {
321                    key   => 'archive',
322                    label => $app->translate('Archive')
323                },
324                {
325                    key   => 'individual',
326                    label => $app->translate('Entry or Page')
327                },
328            );
329            $param->{new_archive_types} = \@types;
330        }
331        elsif ( $template_type eq 'custom' ) {
332            $tab = 'module';
333            $param->{template_group_trans} = $app->translate('module');
334        }
335        elsif ( $template_type eq 'widget' ) {
336            $tab = 'widget';
337            $param->{template_group_trans} = $app->translate('widget');
338        }
339        else {
340            $tab = 'system';
341            $param->{template_group_trans} = $app->translate('system');
342        }
343        $param->{template_group} = $tab;
344        $app->translate($tab);
345        $app->add_breadcrumb( $app->translate('New Template') );
346
347        # FIXME: enumeration of types
348             $param->{has_name} = $template_type eq 'index'
349          || $template_type eq 'custom'
350          || $template_type eq 'widget'
351          || $template_type eq 'archive'
352          || $template_type eq 'category'
353          || $template_type eq 'page'
354          || $template_type eq 'individual';
355        $param->{has_outfile} = $template_type eq 'index';
356        $param->{has_rebuild} =
357          (      ( $template_type eq 'index' )
358              && ( ( $blog->custom_dynamic_templates || "" ) ne 'all' ) );
359        $param->{custom_dynamic} =
360          $blog && $blog->custom_dynamic_templates eq 'custom';
361        $param->{has_build_options} =
362             $blog && ($blog->custom_dynamic_templates eq 'custom'
363          || $param->{has_rebuild});
364
365        # FIXME: enumeration of types
366             $param->{is_special} = $param->{type} ne 'index'
367          && $param->{type} ne 'archive'
368          && $param->{type} ne 'category'
369          && $param->{type} ne 'page'
370          && $param->{type} ne 'individual';
371             $param->{has_build_options} = $param->{has_build_options}
372          && $param->{type} ne 'custom'
373          && $param->{type} ne 'widget'
374          && !$param->{is_special};
375
376        $param->{rebuild_me} = 1;
377        $param->{name}       = MT::Util::decode_url( $app->param('name') )
378          if $app->param('name');
379    }
380    my $set = $blog ? $blog->template_set : undef;
381    require MT::DefaultTemplates;
382    my $tmpls = MT::DefaultTemplates->templates($set);
383    my @tmpl_ids;
384    foreach my $dtmpl (@$tmpls) {
385        if ( !$param->{has_name} ) {
386            if ($obj->type eq 'email') {
387                if ($dtmpl->{identifier} eq $obj->identifier) {
388                    $param->{template_name_label} = $dtmpl->{label};
389                    $param->{template_name}       = $dtmpl->{name};
390                }
391            }
392            else {
393                if ( $dtmpl->{type} eq $obj->type ) {
394                    $param->{template_name_label} = $dtmpl->{label};
395                    $param->{template_name}       = $dtmpl->{name};
396                }
397            }
398        }
399        if ( $dtmpl->{type} eq 'index' ) {
400            push @tmpl_ids,
401              {
402                label    => $dtmpl->{label},
403                key      => $dtmpl->{key},
404                selected => $dtmpl->{key} eq
405                  ( ( $obj ? $obj->identifier : undef ) || '' ),
406              };
407        }
408    }
409    $param->{index_identifiers} = \@tmpl_ids;
410
411    $param->{"type_$param->{type}"} = 1;
412    if ($perms) {
413        my $pref_param =
414          $app->load_template_prefs( $perms->template_prefs );
415        %$param = ( %$param, %$pref_param );
416    }
417
418    # Populate structure for template snippets
419    if ( my $snippets = $app->registry('template_snippets') || {} ) {
420        my @snippets;
421        for my $snip_id ( keys %$snippets ) {
422            my $label = $snippets->{$snip_id}{label};
423            $label = $label->() if ref($label) eq 'CODE';
424            push @snippets,
425              {
426                id      => $snip_id,
427                trigger => $snippets->{$snip_id}{trigger},
428                label   => $label,
429                content => $snippets->{$snip_id}{content},
430              };
431        }
432        @snippets = sort { $a->{label} cmp $b->{label} } @snippets;
433        $param->{template_snippets} = \@snippets;
434    }
435
436    # Populate structure for tag documentation
437    my $all_tags = MT::Component->registry("tags");
438    my $tag_docs = {};
439    foreach my $tag_set (@$all_tags) {
440        my $url = $tag_set->{help_url};
441        $url = $url->() if ref($url) eq 'CODE';
442        # hey, at least give them a google search
443        $url ||= 'http://www.google.com/search?q=mt%t';
444        my $tag_list = '';
445        foreach my $type (qw( block function )) {
446            my $tags = $tag_set->{$type} or next;
447            $tag_list .= ($tag_list eq '' ? '' : ',') . join(",", keys(%$tags));
448        }
449        $tag_list =~ s/(^|,)plugin(,|$)/,/;
450        if (exists $tag_docs->{$url}) {
451            $tag_docs->{$url} .= ',' . $tag_list;
452        }
453        else {
454            $tag_docs->{$url} = $tag_list;
455        }
456    }
457    $param->{tag_docs} = $tag_docs;
458    $param->{link_doc} = $app->help_url('appendices/tags/');
459
460    $param->{screen_id} = "edit-template-" . $param->{type};
461    if (("custom" eq $param->{type}) || ("custom" eq $param->{type})) {
462        $param->{screen_class} .= "edit-template-cache";
463    }
464
465    # template language
466    $param->{template_lang} = 'html';
467    if ( $obj && $obj->outfile ) {
468        if ( $obj->outfile =~ m/\.(css|js|html|php|pl|asp)$/ ) {
469            $param->{template_lang} = {
470                css => 'css',
471                js => 'javascript',
472                html => 'html',
473                php => 'php',
474                pl => 'perl',
475                asp => 'asp',
476            }->{$1};
477        }
478    }
479
480    if (($param->{type} eq 'custom') || ($param->{type} eq 'widget')) {
481        if ($blog) {
482            $param->{include_with_ssi}      = 0;
483            $param->{cache_path}            = '';
484            $param->{cache_enabled}         = 0;
485            $param->{cache_expire_type}     = 0;
486            $param->{cache_expire_period}   = '';
487            $param->{cache_expire_interval} = 0;
488            $param->{ssi_type} = uc $blog->include_system;
489        }
490        if ($obj) {
491            $param->{include_with_ssi} = $obj->include_with_ssi
492              if defined $obj->include_with_ssi;
493            $param->{cache_path}       = $obj->cache_path
494              if defined $obj->cache_path;
495            $param->{cache_enabled} = $obj->use_cache
496              if defined $obj->use_cache;
497            $param->{cache_expire_type} = $obj->cache_expire_type
498              if defined $obj->cache_expire_type;
499            my ( $period, $interval ) =
500              _get_schedule( $obj->cache_expire_interval );
501            $param->{cache_expire_period}   = $period   if defined $period;
502            $param->{cache_expire_interval} = $interval if defined $interval;
503            my @events = split ',', $obj->cache_expire_event;
504            foreach my $name (@events) {
505                $param->{ 'cache_expire_event_' . $name } = 1;
506            }
507        }
508    }
509
510    # if unset, default to 30 so if they choose to enable caching,
511    # it will be preset to something sane.
512    $param->{cache_expire_interval} ||= 30;
513
514    $param->{dirty} = 1
515        if $app->param('dirty');
516
517    $param->{can_preview} = 1
518        if (!$param->{is_special}) && (!$obj || ($obj && $obj->outfile !~ m/\.(css|xml|rss|js)$/));
519
520    1;
521}
522
523sub list {
524    my $app = shift;
525
526    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
527    return $app->return_to_dashboard( redirect => 1 )
528      unless $perms || $app->user->is_superuser;
529    if ( $perms && !$perms->can_edit_templates ) {
530        return $app->return_to_dashboard( permission => 1 );
531    }
532    my $blog = $app->blog;
533
534    require MT::Template;
535    my $blog_id = $app->param('blog_id') || 0;
536    my $terms = { blog_id => $blog_id };
537    my $args  = { sort    => 'name' };
538
539    my $hasher = sub {
540        my ( $obj, $row ) = @_;
541        my $template_type;
542        my $type = $row->{type} || '';
543        if ( $type =~ m/^(individual|page|category|archive)$/ ) {
544            $template_type = 'archive';
545            # populate context with templatemap loop
546            my $tblog = $obj->blog_id == $blog->id ? $blog : MT::Blog->load( $obj->blog_id );
547            if ($tblog) {
548                $row->{archive_types} = _populate_archive_loop( $app, $tblog, $obj );
549            }
550        }
551        elsif ( $type eq 'widget' ) {
552            $template_type = 'widget';
553        }
554        elsif ( $type eq 'index' ) {
555            $template_type = 'index';
556        }
557        elsif ( $type eq 'custom' ) {
558            $template_type = 'module';
559        }
560        elsif ( $type eq 'email' ) {
561            $template_type = 'email';
562        }
563        elsif ( $type eq 'backup' ) {
564            $template_type = 'backup';
565        }
566        else {
567            $template_type = 'system';
568        }
569        $row->{template_type} = $template_type;
570        $row->{type} = 'entry' if $type eq 'individual';
571        my $published_url = $obj->published_url;
572        $row->{published_url} = $published_url if $published_url;
573    };
574
575    my $params        = {};
576    my $filter = $app->param('filter_key');
577    my $template_type = $filter || '';
578    $template_type =~ s/_templates//;
579
580    $params->{screen_class} = "list-template";
581    $params->{listing_screen} = 1;
582
583    $app->load_list_actions( 'template', $params );
584    $params->{page_actions} = $app->page_actions('list_templates');
585    $params->{search_label} = $app->translate("Templates");
586    $params->{blog_view} = 1;
587    $params->{refreshed} = $app->param('refreshed');
588    $params->{published} = $app->param('published');
589    $params->{saved_copied} = $app->param('saved_copied');
590    $params->{saved_deleted} = $app->param('saved_deleted');
591    $params->{saved} = $app->param('saved');
592
593    # determine list of system template types:
594    my $scope;
595    my $set;
596    if ( $blog ) {
597        $set   = $blog->template_set;
598        $scope = 'system';
599    }
600    else {
601        $scope = 'global:system';
602    }
603    my @tmpl_path = ( $set && ($set ne 'mt_blog')) ? ("template_sets", $set, 'templates', $scope) : ("default_templates", $scope);
604    my $sys_tmpl = MT->registry(@tmpl_path) || {};
605
606    my @tmpl_loop;
607    my %types;
608    if ($template_type ne 'backup') {
609        if ($blog) {
610            # blog template listings
611            %types = ( 
612                'index' => {
613                    label => $app->translate("Index Templates"),
614                    type => 'index',
615                    order => 100,
616                },
617                'archive' => {
618                    label => $app->translate("Archive Templates"),
619                    type => ['archive', 'individual', 'page', 'category'],
620                    order => 200,
621                },
622                'module' => {
623                    label => $app->translate("Template Modules"),
624                    type => 'custom',
625                    order => 300,
626                },
627                'system' => {
628                    label => $app->translate("System Templates"),
629                    type => [ keys %$sys_tmpl ],
630                    order => 400,
631                },
632            );
633        } else {
634            # global template listings
635            %types = ( 
636                'module' => {
637                    label => $app->translate("Template Modules"),
638                    type => 'custom',
639                    order => 100,
640                },
641                'email' => {
642                    label => $app->translate("Email Templates"),
643                    type => 'email',
644                    order => 200,
645                },
646                'system' => {
647                    label => $app->translate("System Templates"),
648                    type => [ keys %$sys_tmpl ],
649                    order => 300,
650                },
651            );
652        }
653    } else {
654        # global template listings
655        %types = ( 
656            'backup' => {
657                label => $app->translate("Template Backups"),
658                type => 'backup',
659                order => 100,
660            },
661        );
662    }
663    my @types = sort { $types{$a}->{order} <=> $types{$b}->{order} } keys %types;
664    if ($template_type) {
665        @types = ( $template_type );
666    }
667    $app->delete_param('filter_key') if $filter;
668    foreach my $tmpl_type (@types) {
669        if ( $tmpl_type eq 'index' ) {
670            $app->param( 'filter_key', 'index_templates' );
671        }
672        elsif ( $tmpl_type eq 'archive' ) {
673            $app->param( 'filter_key', 'archive_templates' );
674        }
675        elsif ( $tmpl_type eq 'system' ) {
676            $app->param( 'filter_key', 'system_templates' );
677        }
678        elsif ( $tmpl_type eq 'email' ) {
679            $app->param( 'filter_key', 'email_templates' );
680        }
681        elsif ( $tmpl_type eq 'module' ) {
682            $app->param( 'filter_key', 'module_templates' );
683        }
684        $terms->{type} = $types{$tmpl_type}->{type};
685        my $tmpl_param = $app->listing(
686            {
687                type     => 'template',
688                terms    => $terms,
689                args     => $args,
690                no_limit => 1,
691                no_html  => 1,
692                code     => $hasher,
693            }
694        );
695
696        my $template_type_label = $types{$tmpl_type}->{label};
697        $tmpl_param->{template_type} = $tmpl_type;
698        $tmpl_param->{template_type_label} = $template_type_label;
699        push @tmpl_loop, $tmpl_param;
700    }
701    if ($filter) {
702        $params->{filter_key} = $filter;
703        $params->{filter_label} = $types{$template_type}{label}
704            if exists $types{$template_type};
705        $app->param('filter_key', $filter);
706    } else {
707        # restore filter_key param (we modified it for the
708        # sake of the individual table listings)
709        $app->delete_param('filter_key');
710    }
711
712    $params->{template_type_loop} = \@tmpl_loop;
713    $params->{screen_id} = "list-template";
714
715    return $app->load_tmpl('list_template.tmpl', $params);
716}
717
718sub preview {
719    my $app         = shift;
720    my $q           = $app->param;
721    my $blog_id     = $q->param('blog_id');
722    my $blog        = $app->blog;
723    my $id          = $q->param('id');
724    my $tmpl;
725    my $user_id = $app->user->id;
726
727    # We can only do previews on blog templates. Have to publish
728    # the preview file somewhere!
729    return $app->errtrans("Invalid request.") unless $blog;
730
731    require MT::Template;
732    if ($id) {
733        $tmpl = MT::Template->load( { id => $id, blog_id => $blog_id } )
734            or return $app->errtrans( "Invalid request." );
735    }
736    else {
737        $tmpl = MT::Template->new;
738        $tmpl->id(-1);
739        $tmpl->blog_id($blog_id);
740    }
741
742    my $names = $tmpl->column_names;
743    my %values = map { $_ => scalar $app->param($_) } @$names;
744    delete $values{'id'} unless $q->param('id');
745
746    ## Strip linefeed characters.
747    for my $col (qw( text )) {
748        $values{$col} =~ tr/\r//d if $values{$col};
749    }
750    $tmpl->set_values( \%values );
751
752    my $preview_basename = $app->preview_object_basename;
753
754    my $type = $tmpl->type;
755    my $preview_tmpl = $tmpl;
756    my $archive_file;
757    my $archive_url;
758    my %param;
759    my $blog_path = $blog->site_path;
760    my $blog_url = $blog->site_url;
761
762    if (($type eq 'custom') || ($type eq 'widget')) {
763        # determine 'host' template
764        $preview_tmpl = MT::Template->load({ blog_id => $blog_id, identifier => 'main_index' });
765        if (!$preview_tmpl) {
766            return $app->errtrans("Can't locate host template to preview module/widget.");
767        }
768        my $req = $app->request;
769        # stash this module so that it is selected through a
770        # MTInclude tag instead of the one in the database:
771        my $tmpl_name = $tmpl->name;
772        $tmpl_name =~ s/^Widget: // if $type eq 'widget';
773        my $stash_id = 'template_' . $type . '::' . $blog_id . '::' . $tmpl_name;
774        $req->stash($stash_id, [ $tmpl, $tmpl->tokens ]);
775    } elsif (($type eq 'individual') || ($type eq 'page')) {
776        my $ctx = $preview_tmpl->context;
777        my $entry_type = $type eq 'individual' ? 'entry' : 'page';
778        my $entry_class = $app->model($entry_type);
779        my $obj = $entry_class->load({
780            blog_id => $blog_id,
781            status => MT::Entry::RELEASE()
782        }, {
783            limit => 1,
784            direction => 'descend',
785            'sort' => 'authored_on'
786        });
787        unless ( $obj ) {
788            # create a dummy object
789            $obj = $entry_class->new;
790            $obj->blog_id($blog_id);
791            $obj->id(-1);
792            $obj->author_id( $app->user->id );
793            $obj->authored_on( $blog->current_timestamp );
794            $obj->status( MT::Entry::RELEASE() );
795            $obj->basename( $preview_basename );
796            $obj->title($app->translate("Lorem ipsum"));
797            my $preview_text = $app->translate('LOREM_IPSUM_TEXT');
798            if ($preview_text eq 'LOREM_IPSUM_TEXT') {
799                $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.};
800            }
801            my $preview_more = $app->translate('LORE_IPSUM_TEXT_MORE');
802            if ($preview_text eq 'LOREM_IPSUM_TEXT_MORE') {
803                $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;
804
805                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.
806
807                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.};
808            }
809            $obj->text($preview_text);
810            $obj->text_more($preview_more);
811            $obj->keywords(MT->translate("sample, entry, preview"));
812            $obj->tags(qw( lorem ipsum sample preview ));
813        }
814        $ctx->stash('entry', $obj);
815        $ctx->{current_archive_type} = $type eq 'individual' ? 'Individual' : 'Page';
816        if (($type eq 'individual') && $blog->archive_path) {
817            $blog_path = $blog->archive_path;
818            $blog_url = $blog->archive_url;
819        }
820        $archive_file = File::Spec->catfile( $blog_path, $obj->archive_file );
821        $archive_url = $obj->archive_url;
822    } elsif ($type eq 'archive') {
823        # some variety of archive template
824    } elsif ($type eq 'index') {
825    } else {
826        # for now, only index templates can be previewed
827        return $app->errtrans("Invalid request.");
828    }
829
830    my $orig_file;
831    my $path;
832
833    # Default case; works for index templates (other template types should
834    # have defined $archive_file by now).
835    $archive_file = File::Spec->catfile( $blog_path, $preview_tmpl->outfile )
836        unless defined $archive_file;
837
838    ( $orig_file, $path ) = File::Basename::fileparse( $archive_file );
839
840    $archive_url = MT::Util::caturl( $blog_url, $orig_file )
841        unless defined $archive_url;
842
843    my $file_ext;
844    require File::Basename;
845    $file_ext = $archive_file;
846    if ($file_ext =~ m/\.[a-z]+$/) {
847        $file_ext =~ s!.+\.!.!;
848    } else {
849        $file_ext = '';
850    }
851    $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext );
852
853    my @data;
854    $app->run_callbacks( 'cms_pre_preview.template', $app, $preview_tmpl, \@data );
855
856    my $has_hires = eval 'require Time::HiRes; 1' ? 1 : 0;
857    my $start_time = $has_hires ? Time::HiRes::time() : time;
858
859    my $ctx = $preview_tmpl->context;
860    my $html = $preview_tmpl->output;
861
862    $param{build_time} = $has_hires ? sprintf("%.3f", Time::HiRes::time() - $start_time ) : "~" . ( time - $start_time );
863
864    unless ( defined($html) ) {
865        return $app->error( $app->translate( "Publish error: [_1]",
866            MT::Util::encode_html( $preview_tmpl->errstr ) ) );
867    }
868
869    # If MT is configured to do 'local' previews, convert all
870    # the normal blog URLs into the domain used by MT itself (ie,
871    # blog is published to www.example.com, which is a different
872    # server from where MT runs, mt.example.com; previews therefore
873    # should occur locally, so replace all http://www.example.com/
874    # with http://mt.example.com/).
875    my ($old_url, $new_url);
876    if ($app->config('LocalPreviews')) {
877        $old_url = $blog_url;
878        $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!;
879        $new_url = $app->base . '/';
880        $html =~ s!\Q$old_url\E!$new_url!g;
881    }
882
883    my $fmgr = $blog->file_mgr;
884
885    ## Determine if we need to build directory structure,
886    ## and build it if we do. DirUmask determines
887    ## directory permissions.
888    require File::Basename;
889    $path =~ s!/$!!
890      unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
891    unless ( $fmgr->exists($path) ) {
892        $fmgr->mkpath($path);
893    }
894
895    if ( $fmgr->exists($path) && $fmgr->can_write($path) ) {
896        $param{preview_file} = $preview_basename;
897        my $preview_url = $archive_url;
898        $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x;
899
900        # We also have to translate the URL used for the
901        # published file to be on the MT app domain.
902        if (defined $new_url) {
903            $preview_url =~ s!^\Q$old_url\E!$new_url!;
904        }
905
906        $param{preview_url}  = $preview_url;
907
908        $fmgr->put_data( $html, $archive_file );
909
910        # we have to make a record of this preview just in case it
911        # isn't cleaned up by re-editing, saving or cancelling on
912        # by the user.
913        require MT::Session;
914        my $sess_obj = MT::Session->get_by_key(
915            {
916                id   => $preview_basename,
917                kind => 'TF',                # TF = Temporary File
918                name => $archive_file,
919            }
920        );
921        $sess_obj->start(time);
922        $sess_obj->save;
923    }
924    else {
925        return $app->error( $app->translate(
926            "Unable to create preview file in this location: [_1]", $path ) );
927    }
928
929    $param{id} = $id if $id;
930    $param{new_object} = $param{id} ? 0 : 1;
931    $param{name} = $tmpl->name;
932    my $cols = $tmpl->column_names;
933    for my $col (@$cols) {
934        push @data,
935          {
936            data_name  => $col,
937            data_value => scalar $q->param($col)
938          };
939    }
940    $param{template_loop} = \@data;
941    $param{object_type}  = $type;
942    return $app->load_tmpl( 'preview_template_strip.tmpl', \%param );
943}
944
945sub reset_blog_templates {
946    my $app   = shift;
947    my $q     = $app->param;
948    my $perms = $app->permissions
949      or return $app->error( $app->translate("No permissions") );
950    return $app->error( $app->translate("Permission denied.") )
951      unless $perms->can_edit_templates;
952    $app->validate_magic() or return;
953    my $blog = MT::Blog->load( $perms->blog_id )
954        or return $app->error($app->translate('Can\'t load blog #[_1].', $perms->blog_id));
955    require MT::Template;
956    my @tmpl = MT::Template->load( { blog_id => $blog->id } );
957
958    for my $tmpl (@tmpl) {
959        $tmpl->remove or return $app->error( $tmpl->errstr );
960    }
961    my $set = $blog ? $blog->template_set : undef;
962    require MT::DefaultTemplates;
963    my $tmpl_list = MT::DefaultTemplates->templates($set) || [];
964    my @arch_tmpl;
965    for my $val (@$tmpl_list) {
966        $val->{name} = $app->translate( $val->{name} );
967        $val->{text} = $app->translate_templatized( $val->{text} );
968        my $tmpl = MT::Template->new;
969        $tmpl->set_values($val);
970        $tmpl->build_dynamic(0);
971        $tmpl->blog_id( $blog->id );
972        $tmpl->save
973          or return $app->error(
974            $app->translate(
975                "Populating blog with default templates failed: [_1]",
976                $tmpl->errstr
977            )
978          );
979
980        # FIXME: enumeration of types
981        if (   $val->{type} eq 'archive'
982            || $val->{type} eq 'category'
983            || $val->{type} eq 'page'
984            || $val->{type} eq 'individual' )
985        {
986            push @arch_tmpl, $tmpl;
987        }
988    }
989
990    ## Set up mappings from new templates to archive types.
991    for my $tmpl (@arch_tmpl) {
992        my (@at);
993
994        # FIXME: enumeration of types
995        if ( $tmpl->type eq 'archive' ) {
996            @at = qw( Daily Weekly Monthly Category );
997        }
998        elsif ( $tmpl->type eq 'page' ) {
999            @at = qw( Page );
1000        }
1001        elsif ( $tmpl->type eq 'individual' ) {
1002            @at = qw( Individual );
1003        }
1004        require MT::TemplateMap;
1005        for my $at (@at) {
1006            my $map = MT::TemplateMap->new;
1007            $map->archive_type($at);
1008            $map->is_preferred(1);
1009            $map->template_id( $tmpl->id );
1010            $map->blog_id( $tmpl->blog_id );
1011            $map->save
1012              or return $app->error(
1013                $app->translate(
1014                    "Setting up mappings failed: [_1]",
1015                    $map->errstr
1016                )
1017              );
1018        }
1019    }
1020    $app->redirect(
1021        $app->uri(
1022            'mode' => 'list',
1023            args =>
1024              { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 }
1025        )
1026    );
1027}
1028
1029sub _generate_map_table {
1030    my $app = shift;
1031    my ( $blog_id, $template_id ) = @_;
1032
1033    require MT::Template;
1034    require MT::Blog;
1035    my $blog     = MT::Blog->load($blog_id);
1036    my $template = MT::Template->load($template_id);
1037    my $tmpl     = $app->load_tmpl('include/archive_maps.tmpl');
1038    my $maps     = _populate_archive_loop( $app, $blog, $template );
1039    $tmpl->param( { object_type => 'templatemap' } );
1040    $tmpl->param( { object_loop => $maps } ) if @$maps;
1041    my $html = $tmpl->output();
1042
1043    if ( $html =~ m/<__trans / ) {
1044        $html = $app->translate_templatized($html);
1045    }
1046    $html;
1047}
1048
1049sub _populate_archive_loop {
1050    my $app = shift;
1051    my ( $blog, $obj ) = @_;
1052
1053    my $index = $app->config('IndexBasename');
1054    my $ext = $blog->file_extension || '';
1055    $ext = '.' . $ext if $ext ne '';
1056
1057    require MT::TemplateMap;
1058    my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } );
1059    my @maps;
1060    my %types;
1061    foreach my $map_obj (@tmpl_maps) {
1062        my $map = {};
1063        $map->{map_id}           = $map_obj->id;
1064        $map->{map_is_preferred} = $map_obj->is_preferred;
1065        # publish options
1066        $map->{map_build_type} = $map_obj->build_type;
1067        $map->{ 'map_build_type_' . ( $map_obj->build_type || 0 ) } = 1;
1068        my ( $period, $interval ) = _get_schedule( $map_obj->build_interval );
1069        $map->{ 'map_schedule_period_' . $period } = 1
1070            if defined $period;
1071        $map->{map_schedule_interval} = $interval
1072            if defined $interval;
1073
1074        my $at = $map->{archive_type} = $map_obj->archive_type;
1075        $types{$at}++;
1076        $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1
1077          if $blog->archive_type_preferred;
1078        $map->{file_template} = $map_obj->file_template
1079          if $map_obj->file_template;
1080
1081        my $archiver = $app->publisher->archiver($at);
1082        next unless $archiver;
1083        $map->{archive_label} = $archiver->archive_label;
1084        my $tmpls     = $archiver->default_archive_templates;
1085        my $tmpl_loop = [];
1086        foreach (@$tmpls) {
1087            my $name = $_->{label};
1088            $name =~ s/\.html$/$ext/;
1089            $name =~ s/index$ext$/$index$ext/;
1090            push @$tmpl_loop,
1091              {
1092                name    => $name,
1093                value   => $_->{template},
1094                default => ( $_->{default} || 0 ),
1095              };
1096        }
1097
1098        my $custom = 1;
1099
1100        foreach (@$tmpl_loop) {
1101            if (   ( !$map->{file_template} && $_->{default} )
1102                || ( $map->{file_template} eq $_->{value} ) )
1103            {
1104                $_->{selected}        = 1;
1105                $custom               = 0;
1106                $map->{file_template} = $_->{value}
1107                  if !$map->{file_template};
1108            }
1109        }
1110        if ($custom) {
1111            unshift @$tmpl_loop,
1112              {
1113                name     => $map->{file_template},
1114                value    => $map->{file_template},
1115                selected => 1,
1116              };
1117        }
1118
1119        $map->{archive_tmpl_loop} = $tmpl_loop;
1120        if (
1121            1 < MT::TemplateMap->count(
1122                { archive_type => $at, blog_id => $obj->blog_id }
1123            )
1124          )
1125        {
1126            $map->{has_multiple_archives} = 1;
1127        }
1128
1129        push @maps, $map;
1130    }
1131    @maps = sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @maps;
1132    return \@maps;
1133}
1134
1135sub delete_map {
1136    my $app = shift;
1137    $app->validate_magic() or return;
1138    my $perms = $app->{perms}
1139      or return $app->error( $app->translate("No permissions") );
1140    my $q  = $app->param;
1141    my $id = $q->param('id');
1142
1143    require MT::TemplateMap;
1144    MT::TemplateMap->remove( { id => $id } );
1145    my $html =
1146      _generate_map_table( $app, $q->param('blog_id'),
1147        $q->param('template_id') );
1148    $app->{no_print_body} = 1;
1149    $app->send_http_header("text/plain");
1150    $app->print($html);
1151}
1152
1153sub add_map {
1154    my $app = shift;
1155    $app->validate_magic() or return;
1156    my $perms = $app->{perms}
1157      or return $app->error( $app->translate("No permissions") );
1158
1159    my $q = $app->param;
1160
1161    require MT::TemplateMap;
1162    my $blog_id = $q->param('blog_id');
1163    my $at      = $q->param('new_archive_type');
1164    my $exist   = MT::TemplateMap->exist(
1165        {
1166            blog_id      => $blog_id,
1167            archive_type => $at
1168        }
1169    );
1170    my $map = MT::TemplateMap->new;
1171    $map->is_preferred( $exist ? 0 : 1 );
1172    $map->template_id( scalar $q->param('template_id') );
1173    $map->blog_id($blog_id);
1174    $map->archive_type($at);
1175    $map->save
1176      or return $app->error(
1177        $app->translate( "Saving map failed: [_1]", $map->errstr ) );
1178    my $html =
1179      _generate_map_table( $app, $blog_id, scalar $q->param('template_id') );
1180    $app->rebuild(
1181        BlogID      => $blog_id,
1182        ArchiveType => $at,
1183        TemplateMap => $map,
1184        TemplateID  => scalar $q->param('template_id'),
1185        NoStatic    => 1
1186    ) or return $app->publish_error();
1187    $app->{no_print_body} = 1;
1188    $app->send_http_header("text/plain");
1189    $app->print($html);
1190}
1191
1192sub can_view {
1193    my ( $eh, $app, $id ) = @_;
1194    my $perms = $app->permissions;
1195    return !$id || ($perms && $perms->can_edit_templates) || (!$app->blog && $app->user->can_edit_templates);
1196}
1197
1198sub can_save {
1199    my ( $eh, $app, $id ) = @_;
1200    my $perms = $app->permissions;
1201    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1202}
1203
1204sub can_delete {
1205    my ( $eh, $app, $obj ) = @_;
1206    return 1 if $app->user->is_superuser();
1207    my $perms = $app->permissions;
1208    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1209}
1210
1211sub pre_save {
1212    my $eh = shift;
1213    my ( $app, $obj ) = @_;
1214
1215    ## Strip linefeed characters.
1216    ( my $text = $obj->text ) =~ tr/\r//d;
1217
1218    if ($text =~ m/<(MT|_)_trans/i) {
1219        $text = $app->translate_templatized($text);
1220    }
1221
1222    $obj->text($text);
1223
1224    # update text heights if necessary
1225    if ( my $perms = $app->permissions ) {
1226        my $prefs = $perms->template_prefs || '';
1227        my $text_height = $app->param('text_height');
1228        if ( defined $text_height ) {
1229            my ($pref_text_height) = $prefs =~ m/\btext:(\d+)\b/;
1230            $pref_text_height ||= 0;
1231            if ( $text_height != $pref_text_height ) {
1232                if ( $prefs =~ m/\btext\b/ ) {
1233                    $prefs =~ s/\btext(:\d+)\b/text:$text_height/;
1234                }
1235                else {
1236                    $prefs = 'text:' . $text_height . ',' . $prefs;
1237                }
1238            }
1239        }
1240
1241        if ( $prefs ne ( $perms->template_prefs || '' ) ) {
1242            $perms->template_prefs($prefs);
1243            $perms->save;
1244        }
1245    }
1246
1247    # module caching
1248    $obj->include_with_ssi( $app->param('include_with_ssi') ? 1 : 0 );
1249    $obj->cache_path( $app->param('cache_path'));
1250    $obj->use_cache( $app->param('cache_enabled')           ? 1 : 0 );
1251    my $cache_expire_type = $app->param('cache_expire_type');
1252    $obj->cache_expire_type($cache_expire_type);
1253    my $period   = $app->param('cache_expire_period');
1254    my $interval = $app->param('cache_expire_interval');
1255    my $sec      = _get_interval( $period, $interval );
1256    $obj->cache_expire_interval($sec);
1257    my $q = $app->param;
1258    my @events;
1259
1260    foreach my $name ( $q->param('cache_expire_event') ) {
1261        push @events, $name;
1262    }
1263    $obj->cache_expire_event( join ',', @events );
1264    if ( $cache_expire_type == 1 ) {
1265        return $eh->error(
1266            $app->translate("You should not be able to enter 0 as the time.") )
1267          if $interval == 0;
1268    }
1269    elsif ( $cache_expire_type == 2 ) {
1270        return $eh->error(
1271            $app->translate("You must select at least one event checkbox.") )
1272          if !@events;
1273    }
1274
1275    require MT::PublishOption;
1276    my $build_type = $app->param('build_type');
1277
1278    if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1279        my $period   = $app->param('schedule_period');
1280        my $interval = $app->param('schedule_interval');
1281        my $sec      = _get_interval( $period, $interval );
1282        $obj->build_interval($sec);
1283    }
1284    my $rebuild_me = 1;
1285    if (   $build_type == MT::PublishOption::DISABLED()
1286        || $build_type == MT::PublishOption::MANUALLY() )
1287    {
1288        $rebuild_me = 0;
1289    }
1290    $obj->rebuild_me($rebuild_me);
1291    1;
1292}
1293
1294sub post_save {
1295    my $eh = shift;
1296    my ( $app, $obj, $original ) = @_;
1297
1298    my $sess_obj = $app->autosave_session_obj;
1299    $sess_obj->remove if $sess_obj;
1300
1301    require MT::TemplateMap;
1302    my $q = $app->param;
1303    my @p = $q->param;
1304    for my $p (@p) {
1305        if ( $p =~ /^archive_tmpl_preferred_(\w+)_(\d+)$/ ) {
1306            my $at     = $1;
1307            my $map_id = $2;
1308            my $map    = MT::TemplateMap->load($map_id)
1309                or next;
1310            $map->prefer( $q->param($p) );    # prefer method saves in itself
1311        }
1312        elsif ( $p =~ /^archive_file_tmpl_(\d+)$/ ) {
1313            my $map_id = $1;
1314            my $map    = MT::TemplateMap->load($map_id)
1315                or next;
1316            $map->file_template( $q->param($p) );
1317            $map->save;
1318        }
1319        elsif ( $p =~ /^map_build_type_(\d+)$/ ) {
1320            my $map_id     = $1;
1321            my $map        = MT::TemplateMap->load($map_id)
1322                or next;
1323            my $build_type = $q->param($p);
1324            require MT::PublishOption;
1325            $map->build_type($build_type);
1326            if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1327                my $period   = $q->param( 'map_schedule_period_' . $map_id );
1328                my $interval = $q->param( 'map_schedule_interval_' . $map_id );
1329                my $sec      = _get_interval( $period, $interval );
1330                $map->build_interval($sec);
1331            }
1332            $map->save;
1333        }
1334    }
1335
1336    if ( !$original->id ) {
1337        $app->log(
1338            {
1339                message => $app->translate(
1340                    "Template '[_1]' (ID:[_2]) created by '[_3]'",
1341                    $obj->name, $obj->id, $app->user->name
1342                ),
1343                level    => MT::Log::INFO(),
1344                class    => 'template',
1345                category => 'new',
1346            }
1347        );
1348    }
1349
1350    if ( $obj->build_dynamic ) {
1351        if ( $obj->type eq 'index' ) {
1352            $app->rebuild_indexes(
1353                BlogID   => $obj->blog_id,
1354                Template => $obj,
1355                NoStatic => 1,
1356            ) or return $app->publish_error();    # XXXX
1357        }
1358        else {
1359            $app->rebuild(
1360                BlogID     => $obj->blog_id,
1361                TemplateID => $obj->id,
1362                NoStatic   => 1,
1363            ) or return $app->publish_error();
1364        }
1365    }
1366    1;
1367}
1368
1369sub post_delete {
1370    my ( $eh, $app, $obj ) = @_;
1371
1372    $app->log(
1373        {
1374            message => $app->translate(
1375                "Template '[_1]' (ID:[_2]) deleted by '[_3]'",
1376                $obj->name, $obj->id, $app->user->name
1377            ),
1378            level    => MT::Log::INFO(),
1379            class    => 'system',
1380            category => 'delete'
1381        }
1382    );
1383}
1384
1385sub build_template_table {
1386    my $app = shift;
1387    my (%args) = @_;
1388
1389    my $perms     = $app->permissions;
1390    my $list_pref = $app->list_pref('template');
1391    my $limit     = $args{limit};
1392    my $param     = $args{param} || {};
1393    my $iter;
1394    if ( $args{load_args} ) {
1395        my $class = $app->model('template');
1396        $iter = $class->load_iter( @{ $args{load_args} } );
1397    }
1398    elsif ( $args{iter} ) {
1399        $iter = $args{iter};
1400    }
1401    elsif ( $args{items} ) {
1402        $iter = sub { pop @{ $args{items} } };
1403        $limit = scalar @{ $args{items} };
1404    }
1405    return [] unless $iter;
1406
1407    my @data;
1408    my $i;
1409    my %blogs;
1410    while ( my $tmpl = $iter->() ) {
1411        my $blog = $blogs{ $tmpl->blog_id } ||=
1412          MT::Blog->load( $tmpl->blog_id );
1413        return $app->error($app->translate('Can\'t load blog #[_1].', $tmpl->blog_id)) unless $blog;
1414
1415        my $row = $tmpl->column_values;
1416        $row->{name} = '' if !defined $row->{name};
1417        $row->{name} =~ s/^\s+|\s+$//g;
1418        $row->{name} = "(" . $app->translate("No Name") . ")"
1419          if $row->{name} eq '';
1420        my $published_url = $tmpl->published_url;
1421        $row->{published_url} = $published_url if $published_url;
1422
1423        # FIXME: enumeration of types
1424        $row->{can_delete} = 1
1425          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1426        if ($blog) {
1427            $row->{weblog_name} = $blog->name;
1428        }
1429        elsif ($tmpl->blog_id) {
1430            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1431        }
1432        else {
1433            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1434        }
1435        $row->{object} = $tmpl;
1436        push @data, $row;
1437        last if defined($limit) && (@data > $limit);
1438    }
1439    return [] unless @data;
1440
1441    $param->{template_table}[0]              = {%$list_pref};
1442    $param->{template_table}[0]{object_loop} = \@data;
1443    $param->{template_table}[0]{object_type} = 'template';
1444    $app->load_list_actions( 'template', $param );
1445    $param->{object_loop} = \@data;
1446    \@data;
1447}
1448
1449sub dialog_publishing_profile {
1450    my $app = shift;
1451    $app->validate_magic or return;
1452
1453    my $param = {};
1454    my $blog = $app->blog;
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        $app->rebuild_indexes(
1898            Blog     => $blog,
1899            Template => $tmpl,
1900        );
1901    }
1902
1903    $app->call_return( published => 1 );
1904}
1905
1906sub publish_archive_templates {
1907    my $app = shift;
1908    $app->validate_magic or return;
1909
1910    # permission check
1911    my $perms = $app->permissions;
1912    return $app->errtrans("Permission denied.")
1913      unless $app->user->is_superuser
1914      || $perms->can_administer_blog
1915      || $perms->can_rebuild;
1916
1917    my $blog = $app->blog;
1918    my $templates =
1919      MT->model('template')->lookup_multi( [ $app->param('id') ] );
1920    use MT::TemplateMap;
1921    TEMPLATE: for my $tmpl (@$templates) {
1922        next TEMPLATE if !defined $tmpl;
1923        next TEMPLATE if $tmpl->blog_id != $blog->id;
1924        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl->id } );
1925        foreach my $map (@tmpl_maps) {
1926            $app->rebuild(
1927                BlogID      => $blog->id,
1928                ArchiveType => $map->archive_type,
1929                NoIndexes   => 1,
1930            );
1931        }
1932    }
1933
1934    $app->call_return( published => 1 );
1935}
1936
1937{
1938    my @period_options = (
1939        {
1940            name => 'minutes',
1941            expr => 60,
1942        },
1943        {
1944            name => 'hours',
1945            expr => 60 * 60,
1946        },
1947        {
1948            name => 'days',
1949            expr => 24 * 60 * 60,
1950        },
1951    );
1952
1953    sub _get_schedule {
1954        my ($sec) = @_;
1955        return unless defined $sec;
1956        my ( $period, $interval );
1957        for (@period_options) {
1958            last if $sec % $_->{expr};
1959            $period   = $_->{name};
1960            $interval = $sec / $_->{expr};
1961        }
1962        ( $period, $interval );
1963    }
1964
1965    sub _get_interval {
1966        my ( $period, $interval ) = @_;
1967        return unless defined $period;
1968        my $sec = 0;
1969        for (@period_options) {
1970            if ( $_->{name} eq $period ) {
1971                $sec = $interval * $_->{expr};
1972                last;
1973            }
1974        }
1975        $sec;
1976    }
1977}
1978
19791;
Note: See TracBrowser for help on using the browser.