root/branches/release-34/lib/MT/CMS/Template.pm @ 1873

Revision 1873, 67.9 kB (checked in by bchoate, 20 months ago)

Applied patches from Ogawa-san to add an optimized 'exist' method for testing for existing rows. BugId:69661

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