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

Revision 2064, 80.8 kB (checked in by bchoate, 19 months ago)

Fixed widget listing when a global widget exists. BugId:79495

  • 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_expire_type}     = 0;
480            $param->{cache_expire_period}   = '';
481            $param->{cache_expire_interval} = 0;
482            $param->{ssi_type} = uc $blog->include_system;
483        }
484        if ($obj) {
485            $param->{include_with_ssi} = $obj->include_with_ssi
486              if defined $obj->include_with_ssi;
487            $param->{cache_path}       = $obj->cache_path
488              if defined $obj->cache_path;
489            $param->{cache_expire_type} = $obj->cache_expire_type
490              if defined $obj->cache_expire_type;
491            my ( $period, $interval ) =
492              _get_schedule( $obj->cache_expire_interval );
493            $param->{cache_expire_period}   = $period   if defined $period;
494            $param->{cache_expire_interval} = $interval if defined $interval;
495            my @events = split ',', ($obj->cache_expire_event || '');
496            foreach my $name (@events) {
497                $param->{ 'cache_expire_event_' . $name } = 1;
498            }
499        }
500    }
501
502    # if unset, default to 30 so if they choose to enable caching,
503    # it will be preset to something sane.
504    $param->{cache_expire_interval} ||= 30;
505
506    $param->{dirty} = 1
507        if $app->param('dirty');
508
509    $param->{can_preview} = 1
510        if (!$param->{is_special}) && (!$obj || ($obj && $obj->outfile !~ m/\.(css|xml|rss|js)$/));
511
512    1;
513}
514
515sub list {
516    my $app = shift;
517
518    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
519    return $app->return_to_dashboard( redirect => 1 )
520      unless $perms || $app->user->is_superuser;
521    if ( $perms && !$perms->can_edit_templates ) {
522        return $app->return_to_dashboard( permission => 1 );
523    }
524    my $blog = $app->blog;
525
526    require MT::Template;
527    my $blog_id = $app->param('blog_id') || 0;
528    my $terms = { blog_id => $blog_id };
529    my $args  = { sort    => 'name' };
530
531    my $hasher = sub {
532        my ( $obj, $row ) = @_;
533        my $template_type;
534        my $type = $row->{type} || '';
535        if ( $type =~ m/^(individual|page|category|archive)$/ ) {
536            $template_type = 'archive';
537            # populate context with templatemap loop
538            my $tblog = $obj->blog_id == $blog->id ? $blog : MT::Blog->load( $obj->blog_id );
539            if ($tblog) {
540                $row->{archive_types} = _populate_archive_loop( $app, $tblog, $obj );
541            }
542        }
543        elsif ( $type eq 'widget' ) {
544            $template_type = 'widget';
545        }
546        elsif ( $type eq 'index' ) {
547            $template_type = 'index';
548        }
549        elsif ( $type eq 'custom' ) {
550            $template_type = 'module';
551        }
552        elsif ( $type eq 'email' ) {
553            $template_type = 'email';
554        }
555        elsif ( $type eq 'backup' ) {
556            $template_type = 'backup';
557        }
558        else {
559            $template_type = 'system';
560        }
561        $row->{use_cache} = ( ($obj->cache_expire_type || 0) != 0 ) ? 1 : 0;
562        $row->{template_type} = $template_type;
563        $row->{type} = 'entry' if $type eq 'individual';
564        my $published_url = $obj->published_url;
565        $row->{published_url} = $published_url if $published_url;
566    };
567
568    my $params        = {};
569    my $filter = $app->param('filter_key');
570    my $template_type = $filter || '';
571    $template_type =~ s/_templates//;
572
573    $params->{screen_class} = "list-template";
574    $params->{listing_screen} = 1;
575
576    $app->load_list_actions( 'template', $params );
577    $params->{page_actions} = $app->page_actions('list_templates');
578    $params->{search_label} = $app->translate("Templates");
579    $params->{object_type} = 'template';
580    $params->{blog_view} = 1;
581    $params->{refreshed} = $app->param('refreshed');
582    $params->{published} = $app->param('published');
583    $params->{saved_copied} = $app->param('saved_copied');
584    $params->{saved_deleted} = $app->param('saved_deleted');
585    $params->{saved} = $app->param('saved');
586
587    # determine list of system template types:
588    my $scope;
589    my $set;
590    if ( $blog ) {
591        $set   = $blog->template_set;
592        $scope = 'system';
593    }
594    else {
595        $scope = 'global:system';
596    }
597    my @tmpl_path = ( $set && ($set ne 'mt_blog')) ? ("template_sets", $set, 'templates', $scope) : ("default_templates", $scope);
598    my $sys_tmpl = MT->registry(@tmpl_path) || {};
599
600    my @tmpl_loop;
601    my %types;
602    if ($template_type ne 'backup') {
603        if ($blog) {
604            # blog template listings
605            %types = ( 
606                'index' => {
607                    label => $app->translate("Index Templates"),
608                    type => 'index',
609                    order => 100,
610                },
611                'archive' => {
612                    label => $app->translate("Archive Templates"),
613                    type => ['archive', 'individual', 'page', 'category'],
614                    order => 200,
615                },
616                'module' => {
617                    label => $app->translate("Template Modules"),
618                    type => 'custom',
619                    order => 300,
620                },
621                'system' => {
622                    label => $app->translate("System Templates"),
623                    type => [ keys %$sys_tmpl ],
624                    order => 400,
625                },
626            );
627        } else {
628            # global template listings
629            %types = ( 
630                'module' => {
631                    label => $app->translate("Template Modules"),
632                    type => 'custom',
633                    order => 100,
634                },
635                'email' => {
636                    label => $app->translate("Email Templates"),
637                    type => 'email',
638                    order => 200,
639                },
640                'system' => {
641                    label => $app->translate("System Templates"),
642                    type => [ keys %$sys_tmpl ],
643                    order => 300,
644                },
645            );
646        }
647    } else {
648        # global template listings
649        %types = ( 
650            'backup' => {
651                label => $app->translate("Template Backups"),
652                type => 'backup',
653                order => 100,
654            },
655        );
656    }
657    my @types = sort { $types{$a}->{order} <=> $types{$b}->{order} } keys %types;
658    if ($template_type) {
659        @types = ( $template_type );
660    }
661    $app->delete_param('filter_key') if $filter;
662    foreach my $tmpl_type (@types) {
663        if ( $tmpl_type eq 'index' ) {
664            $app->param( 'filter_key', 'index_templates' );
665        }
666        elsif ( $tmpl_type eq 'archive' ) {
667            $app->param( 'filter_key', 'archive_templates' );
668        }
669        elsif ( $tmpl_type eq 'system' ) {
670            $app->param( 'filter_key', 'system_templates' );
671        }
672        elsif ( $tmpl_type eq 'email' ) {
673            $app->param( 'filter_key', 'email_templates' );
674        }
675        elsif ( $tmpl_type eq 'module' ) {
676            $app->param( 'filter_key', 'module_templates' );
677        }
678        $terms->{type} = $types{$tmpl_type}->{type};
679        my $tmpl_param = $app->listing(
680            {
681                type     => 'template',
682                terms    => $terms,
683                args     => $args,
684                no_limit => 1,
685                no_html  => 1,
686                code     => $hasher,
687            }
688        );
689
690        my $template_type_label = $types{$tmpl_type}->{label};
691        $tmpl_param->{template_type} = $tmpl_type;
692        $tmpl_param->{template_type_label} = $template_type_label;
693        push @tmpl_loop, $tmpl_param;
694    }
695    if ($filter) {
696        $params->{filter_key} = $filter;
697        $params->{filter_label} = $types{$template_type}{label}
698            if exists $types{$template_type};
699        $app->param('filter_key', $filter);
700    } else {
701        # restore filter_key param (we modified it for the
702        # sake of the individual table listings)
703        $app->delete_param('filter_key');
704    }
705
706    $params->{template_type_loop} = \@tmpl_loop;
707    $params->{screen_id} = "list-template";
708
709    return $app->load_tmpl('list_template.tmpl', $params);
710}
711
712sub preview {
713    my $app         = shift;
714    my $q           = $app->param;
715    my $blog_id     = $q->param('blog_id');
716    my $blog        = $app->blog;
717    my $id          = $q->param('id');
718    my $tmpl;
719    my $user_id = $app->user->id;
720
721    # We can only do previews on blog templates. Have to publish
722    # the preview file somewhere!
723    return $app->errtrans("Invalid request.") unless $blog;
724
725    require MT::Template;
726    if ($id) {
727        $tmpl = MT::Template->load( { id => $id, blog_id => $blog_id } )
728            or return $app->errtrans( "Invalid request." );
729    }
730    else {
731        $tmpl = MT::Template->new;
732        $tmpl->id(-1);
733        $tmpl->blog_id($blog_id);
734    }
735
736    my $names = $tmpl->column_names;
737    my %values = map { $_ => scalar $app->param($_) } @$names;
738    delete $values{'id'} unless $q->param('id');
739
740    ## Strip linefeed characters.
741    for my $col (qw( text )) {
742        $values{$col} =~ tr/\r//d if $values{$col};
743    }
744    $tmpl->set_values( \%values );
745
746    my $preview_basename = $app->preview_object_basename;
747
748    my $type = $tmpl->type;
749    my $preview_tmpl = $tmpl;
750    my $archive_file;
751    my $archive_url;
752    my %param;
753    my $blog_path = $blog->site_path;
754    my $blog_url = $blog->site_url;
755
756    if (($type eq 'custom') || ($type eq 'widget')) {
757        # determine 'host' template
758        $preview_tmpl = MT::Template->load({ blog_id => $blog_id, identifier => 'main_index' });
759        if (!$preview_tmpl) {
760            return $app->errtrans("Can't locate host template to preview module/widget.");
761        }
762        my $req = $app->request;
763        # stash this module so that it is selected through a
764        # MTInclude tag instead of the one in the database:
765        my $tmpl_name = $tmpl->name;
766        $tmpl_name =~ s/^Widget: // if $type eq 'widget';
767        my $stash_id = 'template_' . $type . '::' . $blog_id . '::' . $tmpl_name;
768        $req->stash($stash_id, [ $tmpl, $tmpl->tokens ]);
769    } elsif (($type eq 'individual') || ($type eq 'page')) {
770        my $ctx = $preview_tmpl->context;
771        my $entry_type = $type eq 'individual' ? 'entry' : 'page';
772        my $entry_class = $app->model($entry_type);
773        my $obj = $entry_class->load({
774            blog_id => $blog_id,
775            status => MT::Entry::RELEASE()
776        }, {
777            limit => 1,
778            direction => 'descend',
779            'sort' => 'authored_on'
780        });
781        unless ( $obj ) {
782            # create a dummy object
783            $obj = $entry_class->new;
784            $obj->blog_id($blog_id);
785            $obj->id(-1);
786            $obj->author_id( $app->user->id );
787            $obj->authored_on( $blog->current_timestamp );
788            $obj->status( MT::Entry::RELEASE() );
789            $obj->basename( $preview_basename );
790            $obj->title($app->translate("Lorem ipsum"));
791            my $preview_text = $app->translate('LOREM_IPSUM_TEXT');
792            if ($preview_text eq 'LOREM_IPSUM_TEXT') {
793                $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.};
794            }
795            my $preview_more = $app->translate('LORE_IPSUM_TEXT_MORE');
796            if ($preview_text eq 'LOREM_IPSUM_TEXT_MORE') {
797                $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;
798
799                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.
800
801                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.};
802            }
803            $obj->text($preview_text);
804            $obj->text_more($preview_more);
805            $obj->keywords(MT->translate("sample, entry, preview"));
806            $obj->tags(qw( lorem ipsum sample preview ));
807        }
808        $ctx->stash('entry', $obj);
809        $ctx->{current_archive_type} = $type eq 'individual' ? 'Individual' : 'Page';
810        if (($type eq 'individual') && $blog->archive_path) {
811            $blog_path = $blog->archive_path;
812            $blog_url = $blog->archive_url;
813        }
814        $archive_file = File::Spec->catfile( $blog_path, $obj->archive_file );
815        $archive_url = $obj->archive_url;
816    } elsif ($type eq 'archive') {
817        # some variety of archive template
818    } elsif ($type eq 'index') {
819    } else {
820        # for now, only index templates can be previewed
821        return $app->errtrans("Invalid request.");
822    }
823
824    my $orig_file;
825    my $path;
826
827    # Default case; works for index templates (other template types should
828    # have defined $archive_file by now).
829    $archive_file = File::Spec->catfile( $blog_path, $preview_tmpl->outfile )
830        unless defined $archive_file;
831
832    ( $orig_file, $path ) = File::Basename::fileparse( $archive_file );
833
834    $archive_url = MT::Util::caturl( $blog_url, $orig_file )
835        unless defined $archive_url;
836
837    my $file_ext;
838    require File::Basename;
839    $file_ext = $archive_file;
840    if ($file_ext =~ m/\.[a-z]+$/) {
841        $file_ext =~ s!.+\.!.!;
842    } else {
843        $file_ext = '';
844    }
845    $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext );
846
847    my @data;
848    $app->run_callbacks( 'cms_pre_preview.template', $app, $preview_tmpl, \@data );
849
850    my $has_hires = eval 'require Time::HiRes; 1' ? 1 : 0;
851    my $start_time = $has_hires ? Time::HiRes::time() : time;
852
853    my $ctx = $preview_tmpl->context;
854    my $html = $preview_tmpl->output;
855
856    $param{build_time} = $has_hires ? sprintf("%.3f", Time::HiRes::time() - $start_time ) : "~" . ( time - $start_time );
857
858    unless ( defined($html) ) {
859        return $app->error( $app->translate( "Publish error: [_1]",
860            MT::Util::encode_html( $preview_tmpl->errstr ) ) );
861    }
862
863    # If MT is configured to do 'local' previews, convert all
864    # the normal blog URLs into the domain used by MT itself (ie,
865    # blog is published to www.example.com, which is a different
866    # server from where MT runs, mt.example.com; previews therefore
867    # should occur locally, so replace all http://www.example.com/
868    # with http://mt.example.com/).
869    my ($old_url, $new_url);
870    if ($app->config('LocalPreviews')) {
871        $old_url = $blog_url;
872        $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!;
873        $new_url = $app->base . '/';
874        $html =~ s!\Q$old_url\E!$new_url!g;
875    }
876
877    my $fmgr = $blog->file_mgr;
878
879    ## Determine if we need to build directory structure,
880    ## and build it if we do. DirUmask determines
881    ## directory permissions.
882    require File::Basename;
883    $path =~ s!/$!!
884      unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
885    unless ( $fmgr->exists($path) ) {
886        $fmgr->mkpath($path);
887    }
888
889    if ( $fmgr->exists($path) && $fmgr->can_write($path) ) {
890        $param{preview_file} = $preview_basename;
891        my $preview_url = $archive_url;
892        $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x;
893
894        # We also have to translate the URL used for the
895        # published file to be on the MT app domain.
896        if (defined $new_url) {
897            $preview_url =~ s!^\Q$old_url\E!$new_url!;
898        }
899
900        $param{preview_url}  = $preview_url;
901
902        $fmgr->put_data( $html, $archive_file );
903
904        # we have to make a record of this preview just in case it
905        # isn't cleaned up by re-editing, saving or cancelling on
906        # by the user.
907        require MT::Session;
908        my $sess_obj = MT::Session->get_by_key(
909            {
910                id   => $preview_basename,
911                kind => 'TF',                # TF = Temporary File
912                name => $archive_file,
913            }
914        );
915        $sess_obj->start(time);
916        $sess_obj->save;
917    }
918    else {
919        return $app->error( $app->translate(
920            "Unable to create preview file in this location: [_1]", $path ) );
921    }
922
923    $param{id} = $id if $id;
924    $param{new_object} = $param{id} ? 0 : 1;
925    $param{name} = $tmpl->name;
926    my $cols = $tmpl->column_names;
927    for my $col (@$cols) {
928        push @data,
929          {
930            data_name  => $col,
931            data_value => scalar $q->param($col)
932          };
933    }
934    $param{template_loop} = \@data;
935    $param{object_type}  = $type;
936    return $app->load_tmpl( 'preview_template_strip.tmpl', \%param );
937}
938
939sub reset_blog_templates {
940    my $app   = shift;
941    my $q     = $app->param;
942    my $perms = $app->permissions
943      or return $app->error( $app->translate("No permissions") );
944    return $app->error( $app->translate("Permission denied.") )
945      unless $perms->can_edit_templates;
946    $app->validate_magic() or return;
947    my $blog = MT::Blog->load( $perms->blog_id )
948        or return $app->error($app->translate('Can\'t load blog #[_1].', $perms->blog_id));
949    require MT::Template;
950    my @tmpl = MT::Template->load( { blog_id => $blog->id } );
951
952    for my $tmpl (@tmpl) {
953        $tmpl->remove or return $app->error( $tmpl->errstr );
954    }
955    my $set = $blog ? $blog->template_set : undef;
956    require MT::DefaultTemplates;
957    my $tmpl_list = MT::DefaultTemplates->templates($set) || [];
958    my @arch_tmpl;
959    for my $val (@$tmpl_list) {
960        $val->{name} = $app->translate( $val->{name} );
961        $val->{text} = $app->translate_templatized( $val->{text} );
962        my $tmpl = MT::Template->new;
963        if ( ( 'widgetset' eq $val->{type} )
964          && ( exists $val->{modulesets} ) ) {
965            my $modulesets = delete $val->{modulesets};
966            $tmpl->modulesets( join ',', @$modulesets );
967        }
968        $tmpl->set_values($val);
969        $tmpl->build_dynamic(0);
970        $tmpl->blog_id( $blog->id );
971        $tmpl->save
972          or return $app->error(
973            $app->translate(
974                "Populating blog with default templates failed: [_1]",
975                $tmpl->errstr
976            )
977          );
978
979        # FIXME: enumeration of types
980        if (   $val->{type} eq 'archive'
981            || $val->{type} eq 'category'
982            || $val->{type} eq 'page'
983            || $val->{type} eq 'individual' )
984        {
985            push @arch_tmpl, $tmpl;
986        }
987    }
988
989    ## Set up mappings from new templates to archive types.
990    for my $tmpl (@arch_tmpl) {
991        my (@at);
992
993        # FIXME: enumeration of types
994        if ( $tmpl->type eq 'archive' ) {
995            @at = qw( Daily Weekly Monthly Category );
996        }
997        elsif ( $tmpl->type eq 'page' ) {
998            @at = qw( Page );
999        }
1000        elsif ( $tmpl->type eq 'individual' ) {
1001            @at = qw( Individual );
1002        }
1003        require MT::TemplateMap;
1004        for my $at (@at) {
1005            my $map = MT::TemplateMap->new;
1006            $map->archive_type($at);
1007            $map->is_preferred(1);
1008            $map->template_id( $tmpl->id );
1009            $map->blog_id( $tmpl->blog_id );
1010            $map->save
1011              or return $app->error(
1012                $app->translate(
1013                    "Setting up mappings failed: [_1]",
1014                    $map->errstr
1015                )
1016              );
1017        }
1018    }
1019    $app->redirect(
1020        $app->uri(
1021            'mode' => 'list',
1022            args =>
1023              { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 }
1024        )
1025    );
1026}
1027
1028sub _generate_map_table {
1029    my $app = shift;
1030    my ( $blog_id, $template_id ) = @_;
1031
1032    require MT::Template;
1033    require MT::Blog;
1034    my $blog     = MT::Blog->load($blog_id);
1035    my $template = MT::Template->load($template_id);
1036    my $tmpl     = $app->load_tmpl('include/archive_maps.tmpl');
1037    my $maps     = _populate_archive_loop( $app, $blog, $template );
1038    $tmpl->param( object_type => 'templatemap' );
1039    $tmpl->param( publish_queue_available => eval 'require List::Util; require Scalar::Util; 1;' );
1040    $tmpl->param( object_loop => $maps ) if @$maps;
1041    my $html = $tmpl->output();
1042
1043    if ( $html =~ m/<__trans / ) {
1044        $html = $app->translate_templatized($html);
1045    }
1046    $html;
1047}
1048
1049sub _populate_archive_loop {
1050    my $app = shift;
1051    my ( $blog, $obj ) = @_;
1052
1053    my $index = $app->config('IndexBasename');
1054    my $ext = $blog->file_extension || '';
1055    $ext = '.' . $ext if $ext ne '';
1056
1057    require MT::TemplateMap;
1058    my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } );
1059    my @maps;
1060    my %types;
1061    foreach my $map_obj (@tmpl_maps) {
1062        my $map = {};
1063        $map->{map_id}           = $map_obj->id;
1064        $map->{map_is_preferred} = $map_obj->is_preferred;
1065        # publish options
1066        $map->{map_build_type} = $map_obj->build_type;
1067        $map->{ 'map_build_type_' . ( $map_obj->build_type || 0 ) } = 1;
1068        my ( $period, $interval ) = _get_schedule( $map_obj->build_interval );
1069        $map->{ 'map_schedule_period_' . $period } = 1
1070            if defined $period;
1071        $map->{map_schedule_interval} = $interval
1072            if defined $interval;
1073
1074        my $at = $map->{archive_type} = $map_obj->archive_type;
1075        $types{$at}++;
1076        $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1
1077          if $blog->archive_type_preferred;
1078        $map->{file_template} = $map_obj->file_template
1079          if $map_obj->file_template;
1080
1081        my $archiver = $app->publisher->archiver($at);
1082        next unless $archiver;
1083        $map->{archive_label} = $archiver->archive_label;
1084        my $tmpls     = $archiver->default_archive_templates;
1085        my $tmpl_loop = [];
1086        foreach (@$tmpls) {
1087            my $name = $_->{label};
1088            $name =~ s/\.html$/$ext/;
1089            $name =~ s/index$ext$/$index$ext/;
1090            push @$tmpl_loop,
1091              {
1092                name    => $name,
1093                value   => $_->{template},
1094                default => ( $_->{default} || 0 ),
1095              };
1096        }
1097
1098        my $custom = 1;
1099
1100        foreach (@$tmpl_loop) {
1101            if (   ( !$map->{file_template} && $_->{default} )
1102                || ( $map->{file_template} eq $_->{value} ) )
1103            {
1104                $_->{selected}        = 1;
1105                $custom               = 0;
1106                $map->{file_template} = $_->{value}
1107                  if !$map->{file_template};
1108            }
1109        }
1110        if ($custom) {
1111            unshift @$tmpl_loop,
1112              {
1113                name     => $map->{file_template},
1114                value    => $map->{file_template},
1115                selected => 1,
1116              };
1117        }
1118
1119        $map->{archive_tmpl_loop} = $tmpl_loop;
1120        if (
1121            1 < MT::TemplateMap->count(
1122                { archive_type => $at, blog_id => $obj->blog_id }
1123            )
1124          )
1125        {
1126            $map->{has_multiple_archives} = 1;
1127        }
1128
1129        push @maps, $map;
1130    }
1131    @maps = sort { MT::App::CMS::archive_type_sorter( $a, $b ) } @maps;
1132    return \@maps;
1133}
1134
1135sub delete_map {
1136    my $app = shift;
1137    $app->validate_magic() or return;
1138    my $perms = $app->{perms}
1139      or return $app->error( $app->translate("No permissions") );
1140    my $q  = $app->param;
1141    my $id = $q->param('id');
1142
1143    require MT::TemplateMap;
1144    MT::TemplateMap->remove( { id => $id } );
1145    my $html =
1146      _generate_map_table( $app, $q->param('blog_id'),
1147        $q->param('template_id') );
1148    $app->{no_print_body} = 1;
1149    $app->send_http_header("text/plain");
1150    $app->print($html);
1151}
1152
1153sub add_map {
1154    my $app = shift;
1155    $app->validate_magic() or return;
1156    my $perms = $app->{perms}
1157      or return $app->error( $app->translate("No permissions") );
1158
1159    my $q = $app->param;
1160
1161    require MT::TemplateMap;
1162    my $blog_id = $q->param('blog_id');
1163    my $at      = $q->param('new_archive_type');
1164    my $exist   = MT::TemplateMap->exist(
1165        {
1166            blog_id      => $blog_id,
1167            archive_type => $at
1168        }
1169    );
1170    my $map = MT::TemplateMap->new;
1171    $map->is_preferred( $exist ? 0 : 1 );
1172    $map->template_id( scalar $q->param('template_id') );
1173    $map->blog_id($blog_id);
1174    $map->archive_type($at);
1175    $map->save
1176      or return $app->error(
1177        $app->translate( "Saving map failed: [_1]", $map->errstr ) );
1178    my $html =
1179      _generate_map_table( $app, $blog_id, scalar $q->param('template_id') );
1180    $app->{no_print_body} = 1;
1181    $app->send_http_header("text/plain");
1182    $app->print($html);
1183}
1184
1185sub can_view {
1186    my ( $eh, $app, $id ) = @_;
1187    my $perms = $app->permissions;
1188    return !$id || ($perms && $perms->can_edit_templates) || (!$app->blog && $app->user->can_edit_templates);
1189}
1190
1191sub can_save {
1192    my ( $eh, $app, $id ) = @_;
1193    my $perms = $app->permissions;
1194    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1195}
1196
1197sub can_delete {
1198    my ( $eh, $app, $obj ) = @_;
1199    return 1 if $app->user->is_superuser();
1200    my $perms = $app->permissions;
1201    return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates);
1202}
1203
1204sub pre_save {
1205    my $eh = shift;
1206    my ( $app, $obj ) = @_;
1207
1208    ## Strip linefeed characters.
1209    ( my $text = $obj->text ) =~ tr/\r//d;
1210
1211    if ($text =~ m/<(MT|_)_trans/i) {
1212        $text = $app->translate_templatized($text);
1213    }
1214
1215    $obj->text($text);
1216
1217    # update text heights if necessary
1218    if ( my $perms = $app->permissions ) {
1219        my $prefs = $perms->template_prefs || '';
1220        my $text_height = $app->param('text_height');
1221        if ( defined $text_height ) {
1222            my ($pref_text_height) = $prefs =~ m/\btext:(\d+)\b/;
1223            $pref_text_height ||= 0;
1224            if ( $text_height != $pref_text_height ) {
1225                if ( $prefs =~ m/\btext\b/ ) {
1226                    $prefs =~ s/\btext(:\d+)\b/text:$text_height/;
1227                }
1228                else {
1229                    $prefs = 'text:' . $text_height . ',' . $prefs;
1230                }
1231            }
1232        }
1233
1234        if ( $prefs ne ( $perms->template_prefs || '' ) ) {
1235            $perms->template_prefs($prefs);
1236            $perms->save;
1237        }
1238    }
1239
1240    # module caching
1241    $obj->include_with_ssi( $app->param('include_with_ssi') ? 1 : 0 );
1242    $obj->cache_path( $app->param('cache_path'));
1243    my $cache_expire_type = $app->param('cache_expire_type');
1244    $obj->cache_expire_type($cache_expire_type);
1245    my $period   = $app->param('cache_expire_period');
1246    my $interval = $app->param('cache_expire_interval');
1247    my $sec      = _get_interval( $period, $interval );
1248    $obj->cache_expire_interval($sec);
1249    my $q = $app->param;
1250    my @events;
1251
1252    foreach my $name ( $q->param('cache_expire_event') ) {
1253        push @events, $name;
1254    }
1255    $obj->cache_expire_event( join ',', @events );
1256    if ( $cache_expire_type == 1 ) {
1257        return $eh->error(
1258            $app->translate("You should not be able to enter 0 as the time.") )
1259          if $interval == 0;
1260    }
1261    elsif ( $cache_expire_type == 2 ) {
1262        return $eh->error(
1263            $app->translate("You must select at least one event checkbox.") )
1264          if !@events;
1265    }
1266
1267    require MT::PublishOption;
1268    my $build_type = $app->param('build_type');
1269
1270    if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1271        my $period   = $app->param('schedule_period');
1272        my $interval = $app->param('schedule_interval');
1273        my $sec      = _get_interval( $period, $interval );
1274        $obj->build_interval($sec);
1275    }
1276    my $rebuild_me = 1;
1277    if (   $build_type == MT::PublishOption::DISABLED()
1278        || $build_type == MT::PublishOption::MANUALLY() )
1279    {
1280        $rebuild_me = 0;
1281    }
1282    $obj->rebuild_me($rebuild_me);
1283    1;
1284}
1285
1286sub post_save {
1287    my $eh = shift;
1288    my ( $app, $obj, $original ) = @_;
1289
1290    my $sess_obj = $app->autosave_session_obj;
1291    $sess_obj->remove if $sess_obj;
1292
1293    my $dynamic = 0;
1294    my $q = $app->param;
1295    my $type = $q->param('type');
1296    # FIXME: enumeration of types
1297    if ( $type eq 'custom'
1298      || $type eq 'index'
1299      || $type eq 'widget'
1300      || $type eq 'widgetset' )
1301    {
1302        $dynamic = $obj->build_dynamic;
1303    }
1304    else
1305    {
1306        # archive template specific post_save tasks
1307        require MT::TemplateMap;
1308        my @p = $q->param;
1309        for my $p (@p) {
1310            my $map;
1311            if ( $p =~ /^archive_tmpl_preferred_(\w+)_(\d+)$/ ) {
1312                my $at     = $1;
1313                my $map_id = $2;
1314                $map    = MT::TemplateMap->load($map_id)
1315                    or next;
1316                $map->prefer( $q->param($p) );    # prefer method saves in itself
1317            }
1318            elsif ( $p =~ /^archive_file_tmpl_(\d+)$/ ) {
1319                my $map_id = $1;
1320                $map    = MT::TemplateMap->load($map_id)
1321                    or next;
1322                $map->file_template( $q->param($p) );
1323                $map->save;
1324            }
1325            elsif ( $p =~ /^map_build_type_(\d+)$/ ) {
1326                my $map_id     = $1;
1327                $map        = MT::TemplateMap->load($map_id)
1328                    or next;
1329                my $build_type = $q->param($p);
1330                require MT::PublishOption;
1331                $map->build_type($build_type);
1332                if ( $build_type == MT::PublishOption::SCHEDULED() ) {
1333                    my $period   = $q->param( 'map_schedule_period_' . $map_id );
1334                    my $interval = $q->param( 'map_schedule_interval_' . $map_id );
1335                    my $sec      = _get_interval( $period, $interval );
1336                    $map->build_interval($sec);
1337                }
1338                $map->save;
1339            }
1340            if ( !$dynamic
1341              && $map && $map->build_type == MT::PublishOption::DYNAMIC() )
1342            {
1343                $dynamic = 1;
1344            }
1345        }
1346    }
1347
1348    if ( !$original->id ) {
1349        $app->log(
1350            {
1351                message => $app->translate(
1352                    "Template '[_1]' (ID:[_2]) created by '[_3]'",
1353                    $obj->name, $obj->id, $app->user->name
1354                ),
1355                level    => MT::Log::INFO(),
1356                class    => 'template',
1357                category => 'new',
1358            }
1359        );
1360    }
1361
1362    if ( $dynamic ) {
1363        if ( $obj->type eq 'index' ) {
1364            $app->rebuild_indexes(
1365                BlogID   => $obj->blog_id,
1366                Template => $obj,
1367                NoStatic => 1,
1368            ) or return $app->publish_error();    # XXXX
1369        }
1370        if ( my $blog = $app->blog ) {
1371            require MT::CMS::Blog;
1372            my ( $path, $url );
1373            if ( $obj->type eq 'index' ) {
1374                $path = $blog->site_path;
1375                $url = $blog->site_url;
1376            }
1377            else {
1378                # must be archive since other types can't be dynamic
1379                if ( $path = $blog->archive_path ) {
1380                    $url = $blog->archive_url;
1381                }
1382                else {
1383                    $path = $blog->site_path;
1384                    $url = $blog->site_url;
1385                }
1386            }
1387            # specific arguments so not to overwrite mtview and htaccess
1388            MT::CMS::Blog::prepare_dynamic_publishing(
1389                $eh, 
1390                $blog,
1391                undef,
1392                undef,
1393                $path,
1394                $url
1395            );
1396        }
1397    }
1398    1;
1399}
1400
1401sub post_delete {
1402    my ( $eh, $app, $obj ) = @_;
1403
1404    $app->log(
1405        {
1406            message => $app->translate(
1407                "Template '[_1]' (ID:[_2]) deleted by '[_3]'",
1408                $obj->name, $obj->id, $app->user->name
1409            ),
1410            level    => MT::Log::INFO(),
1411            class    => 'system',
1412            category => 'delete'
1413        }
1414    );
1415}
1416
1417sub build_template_table {
1418    my $app = shift;
1419    my (%args) = @_;
1420
1421    my $perms     = $app->permissions;
1422    my $list_pref = $app->list_pref('template');
1423    my $limit     = $args{limit};
1424    my $param     = $args{param} || {};
1425    my $iter;
1426    if ( $args{load_args} ) {
1427        my $class = $app->model('template');
1428        $iter = $class->load_iter( @{ $args{load_args} } );
1429    }
1430    elsif ( $args{iter} ) {
1431        $iter = $args{iter};
1432    }
1433    elsif ( $args{items} ) {
1434        $iter = sub { pop @{ $args{items} } };
1435        $limit = scalar @{ $args{items} };
1436    }
1437    return [] unless $iter;
1438
1439    my @data;
1440    my $i;
1441    my %blogs;
1442    while ( my $tmpl = $iter->() ) {
1443        my $blog = $blogs{ $tmpl->blog_id } ||=
1444          MT::Blog->load( $tmpl->blog_id ) if $tmpl->blog_id;
1445
1446        my $row = $tmpl->column_values;
1447        $row->{name} = '' if !defined $row->{name};
1448        $row->{name} =~ s/^\s+|\s+$//g;
1449        $row->{name} = "(" . $app->translate("No Name") . ")"
1450          if $row->{name} eq '';
1451        my $published_url = $tmpl->published_url;
1452        $row->{published_url} = $published_url if $published_url;
1453        $row->{use_cache} = ( ($tmpl->cache_expire_type || 0) != 0 )  ? 1 : 0;
1454
1455        # FIXME: enumeration of types
1456        $row->{can_delete} = 1
1457          if $tmpl->type =~ m/(custom|index|archive|page|individual|category|widget)/;
1458        if ($blog) {
1459            $row->{weblog_name} = $blog->name;
1460        }
1461        elsif ($tmpl->blog_id) {
1462            $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *';
1463        }
1464        else {
1465            $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *';
1466        }
1467        $row->{object} = $tmpl;
1468        push @data, $row;
1469        last if defined($limit) && (@data > $limit);
1470    }
1471    return [] unless @data;
1472
1473    $param->{template_table}[0]              = {%$list_pref};
1474    $param->{template_table}[0]{object_loop} = \@data;
1475    $param->{template_table}[0]{object_type} = 'template';
1476    $app->load_list_actions( 'template', $param );
1477    $param->{object_loop} = \@data;
1478    \@data;
1479}
1480
1481sub dialog_publishing_profile {
1482    my $app = shift;
1483    $app->validate_magic or return;
1484
1485    my $blog = $app->blog;
1486    $app->assert( $blog ) or return;
1487
1488    # permission check
1489    my $perms = $app->permissions;
1490    return $app->errtrans("Permission denied.")
1491        unless $app->user->is_superuser ||
1492            $perms->can_administer_blog ||
1493            $perms->can_edit_templates;
1494
1495    my $param = {};
1496    $param->{dynamicity} = $blog->custom_dynamic_templates || 'none';
1497    $param->{screen_id} = "publishing-profile-dialog";
1498    $param->{return_args} = $app->param('return_args');
1499
1500    $app->build_page('dialog/publishing_profile.tmpl',
1501        $param);
1502}
1503
1504sub dialog_refresh_templates {
1505    my $app = shift;
1506    $app->validate_magic or return;
1507
1508    # permission check
1509    my $perms = $app->permissions;
1510    return $app->errtrans("Permission denied.")
1511        unless $app->user->is_superuser ||
1512            $perms->can_administer_blog ||
1513            $perms->can_edit_templates;
1514
1515    my $param = {};
1516    my $blog = $app->blog;
1517    $param->{return_args} = $app->param('return_args');
1518
1519    if ($blog) {
1520        $param->{blog_id} = $blog->id;
1521
1522        my $sets = $app->registry("template_sets");
1523        $sets->{$_}{key} = $_ for keys %$sets;
1524        $sets = $app->filter_conditional_list([ values %$sets ]);
1525
1526        no warnings; # some sets may not define an order
1527        @$sets = sort { $a->{order} <=> $b->{order} } @$sets;
1528        $param->{'template_set_loop'} = $sets;
1529
1530        my $existing_set = $blog->template_set || 'mt_blog';
1531        foreach (@$sets) {
1532            if ($_->{key} eq $existing_set) {
1533                $_->{selected} = 1;
1534            }
1535        }
1536        $param->{'template_set_index'} = $#$sets;
1537        $param->{'template_set_count'} = scalar @$sets;
1538
1539        $param->{template_sets} = $sets;
1540        $param->{screen_id} = "refresh-templates-dialog";
1541    }
1542
1543    # load template sets
1544    $app->build_page('dialog/refresh_templates.tmpl',
1545        $param);
1546}
1547
1548sub refresh_all_templates {
1549    my ($app) = @_;
1550
1551    my $backup = 0;
1552    if ($app->param('backup')) {
1553        # refresh templates dialog uses a 'backup' field
1554        $backup = 1;
1555    }
1556
1557    my $template_set = $app->param('template_set');
1558    my $refresh_type = $app->param('refresh_type') || 'refresh';
1559
1560    my $t = time;
1561
1562    my @id;
1563    if ($app->param('blog_id')) {
1564        @id = ( scalar $app->param('blog_id') );
1565    }
1566    else {
1567        @id = $app->param('id');
1568        if (! @id) {
1569            # refresh global templates
1570            @id = ( 0 );
1571        }
1572    }
1573
1574    require MT::Template;
1575    require MT::DefaultTemplates;
1576    require MT::Blog;
1577    require MT::Permission;
1578    require MT::Util;
1579
1580    foreach my $blog_id (@id) {
1581        my $blog;
1582        if ($blog_id) {
1583            $blog = MT::Blog->load($blog_id);
1584            next unless $blog;
1585        }
1586        if ( !$app->user->is_superuser() ) {
1587            my $perms = MT::Permission->load(
1588                { blog_id => $blog_id, author_id => $app->user->id } );
1589            if (
1590                !$perms
1591                || (   !$perms->can_edit_templates()
1592                    && !$perms->can_administer_blog() )
1593              )
1594            {
1595                next;
1596            }
1597        }
1598
1599        my $tmpl_list;
1600        if ($blog_id) {
1601
1602            if ($refresh_type eq 'clean') {
1603                # the user wants to back up all templates and
1604                # install the new ones
1605
1606                my @ts = MT::Util::offset_time_list( $t, $blog_id );
1607                my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
1608                    $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1609
1610                my $tmpl_iter = MT::Template->load_iter({
1611                    blog_id => $blog_id,
1612                    type => { not => 'backup' },
1613                });
1614
1615                while (my $tmpl = $tmpl_iter->()) {
1616                    if ($backup) {
1617                        # zap all template maps
1618                        require MT::TemplateMap;
1619                        MT::TemplateMap->remove({
1620                            template_id => $tmpl->id,
1621                        });
1622                        $tmpl->type('backup');
1623                        $tmpl->name(
1624                            $tmpl->name . ' (Backup from ' . $ts . ')' );
1625                        $tmpl->identifier(undef);
1626                        $tmpl->rebuild_me(0);
1627                        $tmpl->linked_file(undef);
1628                        $tmpl->outfile('');
1629                        $tmpl->save;
1630                    } else {
1631                        $tmpl->remove;
1632                    }
1633                }
1634
1635                # This also creates our template mappings
1636                $blog->create_default_templates( $template_set ||
1637                    $blog->template_set || 'mt_blog' );
1638
1639                if ($template_set) {
1640                    $blog->template_set( $template_set );
1641                    $blog->save;
1642                    $app->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1643                }
1644
1645                next;
1646            }
1647
1648            $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates();
1649        }
1650        else {
1651            $tmpl_list = MT::DefaultTemplates->templates();
1652        }
1653
1654        foreach my $val (@$tmpl_list) {
1655            if ($blog_id) {
1656                # when refreshing blog templates,
1657                # skip over global templates which
1658                # specify a blog_id of 0...
1659                next if $val->{global};
1660            }
1661            else {
1662                next unless exists $val->{global};
1663            }
1664
1665            if ( !$val->{orig_name} ) {
1666                $val->{orig_name} = $val->{name};
1667                $val->{name}      = $app->translate( $val->{name} );
1668                $val->{text}      = $app->translate_templatized( $val->{text} );
1669            }
1670
1671            my $orig_name = $val->{orig_name};
1672
1673            my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) );
1674            my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1675              $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1676
1677            my $terms = {};
1678            $terms->{blog_id} = $blog_id;
1679            $terms->{type} = $val->{type};
1680            if ( $val->{type} =~
1681                m/^(archive|individual|page|category|index|custom|widget)$/ )
1682            {
1683                $terms->{name} = $val->{name};
1684            }
1685            else {
1686                $terms->{identifier} = $val->{identifier};
1687            }
1688
1689            # this should only return 1 template; we're searching
1690            # within a given blog for a specific type of template (for
1691            # "system" templates; or for a type + name, which should be
1692            # unique for that blog.
1693            my $tmpl = MT::Template->load($terms);
1694            if ($tmpl && $backup) {
1695
1696                # check for default template text...
1697                # if it is a default template, then outright replace it
1698                my $text = $tmpl->text;
1699                $text =~ s/\s+//g;
1700
1701                my $def_text = $val->{text};
1702                $def_text =~ s/\s+//g;
1703
1704                # if it has been customized, back it up to a new tmpl record
1705                if ($def_text ne $text) {
1706                    my $backup = $tmpl->clone;
1707                    delete $backup->{column_values}
1708                      ->{id};    # make sure we don't overwrite original
1709                    delete $backup->{changed_cols}->{id};
1710                    $backup->name(
1711                        $backup->name . $app->translate( ' (Backup from [_1])', $ts ) );
1712                    $backup->type('backup');
1713                    # if ( $backup->type !~
1714                    #         m/^(archive|individual|page|category|index|custom|widget)$/ )
1715                    # {
1716                    #     $backup->type('custom')
1717                    #       ;      # system templates can't be created
1718                    # }
1719                    $backup->outfile('');
1720                    $backup->linked_file( $tmpl->linked_file );
1721                    $backup->identifier(undef);
1722                    $backup->rebuild_me(0);
1723                    $backup->build_dynamic(0);
1724                    $backup->save;
1725                }
1726            }
1727            if ($tmpl) {
1728                # we found that the previous template had not been
1729                # altered, so replace it with new default template...
1730                if ( ( 'widgetset' eq $val->{type} )
1731                  && ( exists $val->{widgets} ) ) {
1732                    my $modulesets = delete $val->{widgets};
1733                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1734                }
1735                $tmpl->text( $val->{text} );
1736                $tmpl->identifier( $val->{identifier} );
1737                $tmpl->type( $val->{type} )
1738                  ; # fixes mismatch of types for cases like "archive" => "individual"
1739                $tmpl->linked_file('');
1740                $tmpl->save;
1741            }
1742            else {
1743                # create this one...
1744                my $tmpl = new MT::Template;
1745                if ( ( 'widgetset' eq $val->{type} )
1746                  && ( exists $val->{widgets} ) ) {
1747                    my $modulesets = delete $val->{widgets};
1748                    $tmpl->modulesets( MT::Template->widgets_to_modulesets($modulesets, $blog_id) );
1749                }
1750                $tmpl->build_dynamic(0);
1751                $tmpl->set_values(
1752                    {
1753                        text       => $val->{text},
1754                        name       => $val->{name},
1755                        type       => $val->{type},
1756                        identifier => $val->{identifier},
1757                        outfile    => $val->{outfile},
1758                        rebuild_me => $val->{rebuild_me},
1759                    }
1760                );
1761                $tmpl->blog_id($blog_id);
1762                $tmpl->save
1763                  or return $app->error(
1764                        $app->translate("Error creating new template: ")
1765                      . $tmpl->errstr );
1766            }
1767        }
1768    }
1769
1770    $app->add_return_arg( 'refreshed' => 1 );
1771    $app->call_return;
1772}
1773
1774sub refresh_individual_templates {
1775    my ($app) = @_;
1776
1777    require MT::Util;
1778
1779    my $user = $app->user;
1780    my $perms = $app->permissions;
1781    return $app->error(
1782        $app->translate(
1783            "Permission denied.")
1784      )
1785      #TODO: system level-designer permission
1786      unless $user->is_superuser() || $user->can_edit_templates()
1787      || ( $perms
1788        && ( $perms->can_edit_templates()
1789          || $perms->can_administer_blog ) );
1790
1791    my $set;
1792    if ( my $blog_id = $app->param('blog_id') ) {
1793        my $blog = $app->model('blog')->load($blog_id)
1794            or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
1795        $set = $blog->template_set()
1796            if $blog;
1797    }
1798
1799    require MT::DefaultTemplates;
1800    my $tmpl_list = MT::DefaultTemplates->templates($set) or return;
1801
1802    my $tmpl_types = {};
1803    my $tmpl_ids   = {};
1804    my $tmpls      = {};
1805    foreach my $tmpl (@$tmpl_list) {
1806        $tmpl->{text} = $app->translate_templatized( $tmpl->{text} );
1807        $tmpl_ids->{ $tmpl->{identifier} } = $tmpl
1808            if $tmpl->{identifier};
1809        if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ )
1810        {
1811            $tmpl_types->{ $tmpl->{type} } = $tmpl;
1812        }
1813        else {
1814            $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl;
1815        }
1816    }
1817
1818    my $t = time;
1819
1820    my @msg;
1821    my @id = $app->param('id');
1822    require MT::Template;
1823    foreach my $tmpl_id (@id) {
1824        my $tmpl = MT::Template->load($tmpl_id);
1825        next unless $tmpl;
1826        my $blog_id = $tmpl->blog_id;
1827
1828        # FIXME: permission check -- for this blog_id
1829
1830        my @ts = MT::Util::offset_time_list( $t, $blog_id );
1831        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
1832          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
1833
1834        my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef )
1835          || $tmpl_types->{ $tmpl->type() }
1836          || $tmpls->{ $tmpl->type() }{ $tmpl->name };
1837        if ( !$val ) {
1838            push @msg,
1839              $app->translate(
1840"Skipping template '[_1]' since it appears to be a custom template.",
1841                $tmpl->name
1842              );
1843            next;
1844        }
1845
1846        my $text = $tmpl->text;
1847        $text =~ s/\s+//g;
1848
1849        my $def_text = $val->{text};
1850        $def_text =~ s/\s+//g;
1851
1852        if ($text ne $def_text) {
1853            # if it has been customized, back it up to a new tmpl record
1854            my $backup = $tmpl->clone;
1855            delete $backup->{column_values}
1856              ->{id};    # make sure we don't overwrite original
1857            delete $backup->{changed_cols}->{id};
1858            $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
1859            $backup->type('backup');
1860            $backup->outfile('');
1861            $backup->linked_file( $tmpl->linked_file );
1862            $backup->rebuild_me(0);
1863            $backup->build_dynamic(0);
1864            $backup->identifier(undef);
1865            $backup->save;
1866            push @msg,
1867              $app->translate(
1868    'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&amp;blog_id=[_1]&amp;_type=template&amp;id=[_2]">backup</a>',
1869                  $blog_id, $backup->id, $tmpl->name );
1870
1871            # we found that the previous template had not been
1872            # altered, so replace it with new default template...
1873            $tmpl->text( $val->{text} );
1874            $tmpl->identifier( $val->{identifier} );
1875            $tmpl->linked_file('');
1876            $tmpl->save;
1877        } else {
1878            push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name);
1879        }
1880    }
1881    my @msg_loop;
1882    push @msg_loop, { message => $_ } foreach @msg;
1883
1884    $app->build_page( 'refresh_results.tmpl',
1885        { message_loop => \@msg_loop, return_url => $app->return_uri } );
1886}
1887
1888sub clone_templates {
1889    my ($app) = @_;
1890
1891    my $user = $app->user;
1892    my $perms = $app->permissions;
1893    return $app->error(
1894        $app->translate(
1895            "Permission denied.")
1896      )
1897      #TODO: system level-designer permission
1898      unless $user->is_superuser() || $user->can_edit_templates()
1899      || ( $perms
1900        && ( $perms->can_edit_templates()
1901          || $perms->can_administer_blog ) );
1902
1903    my @id = $app->param('id');
1904    require MT::Template;
1905    foreach my $tmpl_id (@id) {
1906        my $tmpl = MT::Template->load($tmpl_id);
1907        next unless $tmpl;
1908
1909        my $new_tmpl = $tmpl->clone({
1910            Except => {
1911                id => 1,
1912                name => 1,
1913                identifier => 1,
1914            },
1915        });
1916
1917        my $new_basename = $app->translate("Copy of [_1]", $tmpl->name);
1918        my $new_name = $new_basename;
1919        my $i = 0;
1920        while (MT::Template->exist({ name => $new_name, blog_id => $tmpl->blog_id })) {
1921            $new_name = $new_basename . ' (' . ++$i . ')';
1922        }
1923
1924        $new_tmpl->name($new_name);
1925        $new_tmpl->save;
1926    }
1927
1928    $app->add_return_arg( 'saved_copied' => 1 );
1929    $app->call_return;
1930}
1931
1932sub publish_index_templates {
1933    my $app = shift;
1934    $app->validate_magic or return;
1935
1936    # permission check
1937    my $perms = $app->permissions;
1938    return $app->errtrans("Permission denied.")
1939        unless $app->user->is_superuser ||
1940            $perms->can_administer_blog ||
1941            $perms->can_rebuild;
1942
1943    my $blog = $app->blog;
1944    my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]);
1945    TEMPLATE: for my $tmpl (@$templates) {
1946        next TEMPLATE if !defined $tmpl;
1947        next TEMPLATE if $tmpl->blog_id != $blog->id;
1948        next TEMPLATE unless $tmpl->build_type;
1949
1950        $app->rebuild_indexes(
1951            Blog     => $blog,
1952            Template => $tmpl,
1953            Force    => 1,
1954        );
1955    }
1956
1957    $app->call_return( published => 1 );
1958}
1959
1960sub publish_archive_templates {
1961    my $app = shift;
1962    $app->validate_magic or return;
1963
1964    # permission check
1965    my $perms = $app->permissions;
1966    return $app->errtrans("Permission denied.")
1967      unless $app->user->is_superuser
1968      || $perms->can_administer_blog
1969      || $perms->can_rebuild;
1970
1971    my $blog = $app->blog;
1972    my $templates =
1973      MT->model('template')->lookup_multi( [ $app->param('id') ] );
1974    require MT::TemplateMap;
1975    # FIXME: Need multi-request support!
1976    TEMPLATE: for my $tmpl (@$templates) {
1977        next TEMPLATE if !defined $tmpl;
1978        next TEMPLATE if $tmpl->blog_id != $blog->id;
1979        my @tmpl_maps = MT::TemplateMap->load( { template_id => $tmpl->id } );
1980        foreach my $map (@tmpl_maps) {
1981            next unless $map->build_type;
1982            $app->rebuild(
1983                Blog        => $blog,
1984                ArchiveType => $map->archive_type,
1985                TemplateMap => $map,
1986                NoIndexes   => 1,
1987                Force       => 1,
1988            );
1989        }
1990    }
1991
1992    $app->call_return( published => 1 );
1993}
1994
1995sub save_widget {
1996    my $app = shift;
1997    my $q   = $app->param;
1998
1999    $app->validate_magic() or return;
2000    my $author = $app->user;
2001
2002    my $id = $q->param('id');
2003
2004    if ( !$author->is_superuser ) {
2005        $app->run_callbacks( 'cms_save_permission_filter.template', $app, $id )
2006          || return $app->error(
2007            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2008    }
2009
2010    my $filter_result = $app->run_callbacks( 'cms_save_filter.widgetset', $app );
2011
2012    if ( !$filter_result ) {
2013        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2014    }
2015
2016    my $class = $app->model('template');
2017    my $obj;
2018    if ( $id ) {
2019        $obj = $class->load($id)
2020            or return $app->error($app->translate("Invalid ID [_1]", $id));
2021    }
2022    else {
2023        $obj = $class->new;
2024    }
2025
2026    my $original = $obj->clone();
2027    $obj->name($q->param('name'));
2028    $obj->type('widgetset');
2029    $obj->blog_id( $q->param('blog_id') || 0 );
2030    $obj->modulesets($q->param('modules'));
2031
2032    unless (
2033        $app->run_callbacks( 'cms_pre_save.template', $app, $obj, $original ) )
2034    {
2035        return edit_widget( $app, { error => $app->translate( "Save failed: [_1]", $app->errstr ) } );
2036    }
2037
2038    $obj->save
2039      or return $app->error(
2040        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
2041
2042    $app->run_callbacks( 'cms_post_save.template', $app, $obj, $original )
2043      or return $app->error( $app->errstr() );
2044
2045    $app->redirect(
2046        $app->uri(
2047            'mode' => 'edit_widget',
2048            args =>
2049              { blog_id => $obj->blog_id, 'saved' => 1, rebuild => 1, id => $obj->id }
2050        )
2051    );
2052}
2053
2054sub edit_widget {
2055    my $app = shift;
2056    my (%opt) = @_;
2057
2058    my $q       = $app->param();
2059    my $id      = scalar($q->param('id')) || $opt{id};
2060    my $blog_id = scalar $q->param('blog_id') || 0;
2061
2062    my $tmpl_class = $app->model('template');
2063    require MT::Promise;
2064    my $obj_promise = MT::Promise::delay(
2065        sub {
2066            return $tmpl_class->load($id) || undef;
2067        }
2068    );
2069
2070    if ( !$app->user->is_superuser ) {
2071        $app->run_callbacks( 'cms_view_permission_filter.template',
2072            $app, $id, $obj_promise )
2073          || return $app->error(
2074            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2075    }
2076
2077    my $param = {
2078        blog_id      => $blog_id,
2079        search_type  => "template",
2080        search_label => MT::Template->class_label_plural,
2081        $id ? ( id => $id ) : (), 
2082        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2083        exists($opt{error}) ? ( error => $opt{error} ) : (),
2084        exists($opt{saved}) ? ( saved => $opt{saved} ) : ()
2085    };
2086    if ($blog_id) {
2087        my $blog = $app->blog;
2088        # include_system/include_cache are only applicable
2089        # to blog-level templates
2090        $param->{include_system} = $blog->include_system;
2091        $param->{include_cache} = $blog->include_cache;
2092        $param->{include_with_ssi}      = 0;
2093        $param->{cache_path}            = '';
2094        $param->{cache_enabled}         = 0;
2095        $param->{cache_expire_type}     = 0;
2096        $param->{cache_expire_period}   = '';
2097        $param->{cache_expire_interval} = 0;
2098        $param->{ssi_type} = uc $blog->include_system;
2099    }
2100   
2101    my $iter = $tmpl_class->load_iter(
2102        { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2103        { sort => 'name', direction => 'ascend' }
2104    );
2105
2106    my %all_widgets;
2107    while (my $m = $iter->()) {
2108        next unless $m;
2109        $all_widgets{ $m->id } = $m->name;
2110    }
2111
2112    my @inst_modules;
2113    my $wtmpl;
2114    if ( $id ) {
2115        $wtmpl = $obj_promise->force()
2116          or return $app->error(
2117            $app->translate(
2118                "Load failed: [_1]",
2119                $tmpl_class->errstr || $app->translate("(no reason given)")
2120            )
2121          );
2122        $param->{name} = $wtmpl->name;
2123        $param->{include_with_ssi} = $wtmpl->include_with_ssi
2124          if defined $wtmpl->include_with_ssi;
2125        $param->{cache_path}       = $wtmpl->cache_path
2126          if defined $wtmpl->cache_path;
2127        $param->{cache_expire_type} = $wtmpl->cache_expire_type
2128          if defined $wtmpl->cache_expire_type;
2129        my ( $period, $interval ) =
2130          _get_schedule( $wtmpl->cache_expire_interval );
2131        $param->{cache_expire_period}   = $period   if defined $period;
2132        $param->{cache_expire_interval} = $interval if defined $interval;
2133        my @events = split ',', $wtmpl->cache_expire_event;
2134        foreach my $name (@events) {
2135            $param->{ 'cache_expire_event_' . $name } = 1;
2136        }
2137        my $modulesets = $wtmpl->modulesets;
2138        if ( $modulesets ) {
2139            my @modules = split ',', $modulesets;
2140            foreach my $mid ( @modules ) {
2141                push @inst_modules, { id => $mid, name => $all_widgets{$mid} };
2142                delete $all_widgets{$mid};
2143            }
2144        }
2145    }
2146    $param->{installed} = \@inst_modules if @inst_modules;
2147    my @avail_modules = map { { id => $_, name => $all_widgets{$_} } }
2148        keys %all_widgets;
2149    $param->{available} = \@avail_modules;
2150
2151    my $res = $app->run_callbacks('cms_edit.widgetset', $app, $id, $wtmpl, $param);
2152    if (!$res) {
2153        return $app->error($app->callback_errstr());
2154    }
2155
2156    $app->load_tmpl('edit_widget.tmpl', $param);
2157}
2158
2159sub list_widget {
2160    my $app = shift;
2161    my (%opt) = @_;
2162    my $q = $app->param;
2163
2164    my $perms = $app->blog ? $app->permissions : $app->user->permissions;
2165    return $app->return_to_dashboard( redirect => 1 )
2166      unless $perms || $app->user->is_superuser;
2167    if ( $perms && !$perms->can_edit_templates ) {
2168        return $app->return_to_dashboard( permission => 1 );
2169    }
2170    my $blog_id = $q->param('blog_id') || 0;
2171
2172    my $widget_loop = &build_template_table( $app,
2173        load_args => [ 
2174            { type => 'widget', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2175            { sort => 'name', direction => 'ascend' }
2176        ],
2177    );
2178   
2179    my $param = {
2180        widget_table   => $widget_loop,
2181        object_type    => "widgetset",
2182        search_type    => "template",
2183        search_label   => MT::Template->class_label_plural,
2184        listing_screen => 1,
2185        screen_id      => "list-widget-set",
2186        $blog_id ? ( blog_view => 1, blog_id => $blog_id ) : (),
2187        exists($opt{rebuild}) ? ( rebuild => $opt{rebuild} ) : (),
2188        exists($opt{error}) ? ( error => $opt{error} ) : (),
2189        exists($opt{deleted}) ? ( saved => $opt{deleted} ) : ()
2190    };
2191
2192    my $iter = $app->model('template')->load_iter(
2193        { type => 'widgetset', blog_id => $blog_id ? [ $blog_id, 0 ] : 0 },
2194        { sort => 'name', direction => 'ascend' }
2195    );
2196    my @widgetmanagers;
2197    while ( my $widgetset = $iter->() ) {
2198        next unless $widgetset;
2199        my $ws = { 
2200            id => $widgetset->id,
2201            widgetmanager => $widgetset->name,
2202        };
2203        if ( my $modulesets = $widgetset->modulesets ) {
2204            $ws->{widgets} = $modulesets;
2205            my @names;
2206            foreach my $module ( split ',', $modulesets ) { 
2207                my ( $widget ) = grep { $_->{id} eq $module } @$widget_loop;
2208                push @names, $widget->{name} if $widget;
2209            }
2210            $ws->{names} = join(', ', @names) if @names;
2211        }
2212        push @widgetmanagers, $ws;
2213    }
2214    $param->{object_loop} = \@widgetmanagers if @widgetmanagers;
2215
2216    $app->load_tmpl('list_widget.tmpl', $param);
2217}
2218
2219sub delete_widget {
2220    my $app  = shift;
2221    my $q    = $app->param;
2222    my $type = $q->param('_type');
2223
2224    return $app->errtrans("Invalid request.")
2225      unless $type;
2226
2227    return $app->error( $app->translate("Invalid request.") )
2228      if $app->request_method() ne 'POST';
2229
2230    $app->validate_magic() or return;
2231
2232    my $tmpl_class = $app->model('template');
2233
2234    for my $id ( $q->param('id') ) {
2235        next unless $id;    # avoid 'empty' ids
2236
2237        my $obj = $tmpl_class->load($id);
2238        next unless $obj;
2239        $app->run_callbacks( 'cms_delete_permission_filter.template',
2240            $app, $obj )
2241          || return $app->error(
2242            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
2243
2244        $obj->remove
2245          or return $app->errtrans(
2246            'Removing [_1] failed: [_2]',
2247            $app->translate('template'),
2248            $obj->errstr
2249          );
2250        $app->run_callbacks( 'cms_post_delete.template', $app, $obj );
2251    }
2252    $app->call_return;
2253}
2254
2255sub restore_widgetmanagers {
2256    my ($cb, $objects, $deferred, $errors, $callback) = @_;
2257    my @keys = grep { $_ =~ /^MT::Template#/ } keys( %$objects );
2258    foreach my $key ( @keys ) {
2259        my $tmpl = $objects->{$key};
2260        next unless 'widgetset' eq $tmpl->type;
2261        my $modulesets = $tmpl->modulesets;
2262        next unless $modulesets;
2263        $callback->( MT->translate( 'Restoring widget set [_1]... ', $tmpl->name ) );
2264
2265        my @tmpl_ids = split ',', $modulesets;
2266        my @new_ids;
2267        foreach my $id ( @tmpl_ids ) {
2268            my $new_tmpl = $objects->{"MT::Template#$id"};
2269            next unless $new_tmpl;
2270            push @new_ids, $new_tmpl->id;
2271        }
2272        if ( @new_ids ) {
2273            $tmpl->modulesets( join(',', @new_ids) );
2274            $tmpl->save;
2275            $callback->( MT->translate("Done.") . "\n" );
2276        }
2277        else {
2278            $callback->( MT->translate("Failed.") . "\n" );
2279        }
2280    }
2281    1;
2282}
2283
2284{
2285    my @period_options = (
2286        {
2287            name => 'minutes',
2288            expr => 60,
2289        },
2290        {
2291            name => 'hours',
2292            expr => 60 * 60,
2293        },
2294        {
2295            name => 'days',
2296            expr => 24 * 60 * 60,
2297        },
2298    );
2299
2300    sub _get_schedule {
2301        my ($sec) = @_;
2302        return unless defined $sec;
2303        my ( $period, $interval );
2304        for (@period_options) {
2305            last if $sec % $_->{expr};
2306            $period   = $_->{name};
2307            $interval = $sec / $_->{expr};
2308        }
2309        ( $period, $interval );
2310    }
2311
2312    sub _get_interval {
2313        my ( $period, $interval ) = @_;
2314        return unless defined $period;
2315        my $sec = 0;
2316        for (@period_options) {
2317            if ( $_->{name} eq $period ) {
2318                $sec = $interval * $_->{expr};
2319                last;
2320            }
2321        }
2322        $sec;
2323    }
2324}
2325
23261;
Note: See TracBrowser for help on using the browser.