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

Revision 2047, 69.7 kB (checked in by fumiakiy, 19 months ago)

Search templates in templates listing and widgets listing. Thanks Jay Allen for the patch. BugId:79449

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