root/branches/release-36/lib/MT/CMS/Common.pm @ 2113

Revision 2113, 38.7 kB (checked in by bchoate, 19 months ago)

Fix for saving and publishing new templates. BugId:79510

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Common;
2
3use strict;
4
5use MT::Util qw( format_ts offset_time_list relative_date );
6
7sub save {
8    my $app  = shift;
9    my $q    = $app->param;
10    my $type = $q->param('_type');
11
12    return $app->errtrans("Invalid request.")
13      unless $type;
14
15    # being a general-purpose method, lets look for a mode handler
16    # that is specifically for editing this type. if we find it,
17    # reroute to it.
18
19    my $save_mode = 'save_' . $type;
20    if ( my $hdlrs = $app->handlers_for_mode($save_mode) ) {
21        return $app->forward($save_mode);
22    }
23
24    my $id = $q->param('id');
25    $q->param( 'allow_pings', 0 )
26      if ( $type eq 'category' ) && !defined( $q->param('allow_pings') );
27
28    $app->validate_magic() or return;
29    my $author = $app->user;
30
31    # Check permissions
32    my $perms = $app->permissions;
33
34    if ( !$author->is_superuser ) {
35        if ( ($type ne 'author') && ($type ne 'template') )
36        {    # for authors, blog-ctx $perms is not relevant
37            return $app->errtrans("Permisison denied.")
38              if !$perms && $id;
39        }
40
41        $app->run_callbacks( 'cms_save_permission_filter.' . $type, $app, $id )
42          || return $app->error(
43            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
44    }
45
46    my $param = {};
47    if ( $type eq 'author' ) {
48        if ( my $delim = $q->param('tag_delim') ) {
49            $param->{ 'auth_pref_tag_delim_' . $delim } = 1;
50            $param->{'auth_pref_tag_delim'} = $delim;
51        }
52        $param->{languages} =
53          $app->languages_list( $q->param('preferred_language') )
54          if $q->param('preferred_language');
55        $param->{create_personal_weblog} =
56          $q->param('create_personal_weblog') ? 1 : 0;
57        require MT::Permission;
58        my $sys_perms = MT::Permission->perms('system');
59        foreach (@$sys_perms) {
60            $param->{ 'perm_can_' . $_->[0] } = 1
61              if $q->param( 'can_' . $_->[0] );
62        }
63    }
64
65    my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app );
66
67    if ( !$filter_result ) {
68        my %param = (%$param);
69        $param{error}       = $app->errstr;
70        $param{return_args} = $app->param('return_args');
71
72        if ( ( $type eq 'notification' ) || ( $type eq 'banlist' ) ) {
73            return list( $app, \%param );
74        }
75        elsif ( ( $app->param('cfg_screen') || '' ) eq 'cfg_archives' ) {
76            return edit( $app, \%param );
77        }
78        else {
79            return $app->forward( 'view', \%param );
80        }
81    }
82
83    return $app->errtrans(
84        'The Template Name and Output File fields are required.')
85      if $type eq 'template' && !$q->param('name') && !$q->param('outfile');
86
87    if ( $type eq 'author' ) {
88        ## If we are saving a user profile, we need to do some
89        ## password maintenance. First make sure that the two
90        ## passwords match...
91        my $editing_other_profile;
92        if ( $id && ( $author->id != $id ) && ( $author->is_superuser ) ) {
93            $editing_other_profile = 1;
94        }
95        my %param = (%$param);
96        unless ($editing_other_profile) {
97            require MT::Auth;
98            my $error = MT::Auth->sanity_check($app);
99            if ($error) {
100                $param{error} = $error;
101                require MT::Log;
102                $app->log(
103                    {
104                        message  => $error,
105                        level    => MT::Log::ERROR(),
106                        class    => 'system',
107                        category => 'save_author_profile'
108                    }
109                );
110            }
111            if ( $param{error} ) {
112                $param{return_args} = $app->param('return_args');
113                my $qual = $id ? '' : 'author_state_';
114                for my $f (qw( name nickname email url state )) {
115                    $param{ $qual . $f } = $q->param($f);
116                }
117                return edit( $app, \%param );
118            }
119        }
120    }
121
122    if ( $type eq 'template' ) {
123
124        # check for autosave
125        if ( $q->param('_autosave') ) {
126            return $app->autosave_object();
127        }
128    }
129
130    my $class = $app->model($type)
131      or return $app->errtrans( "Invalid type [_1]", $type );
132    my ($obj);
133    if ($id) {
134        $obj = $class->load($id)
135            or return $app->error($app->translate("Invalid ID [_1]", $id));
136    }
137    else {
138        $obj = $class->new;
139    }
140
141    my $original = $obj->clone();
142    my $names    = $obj->column_names;
143    my %values   = map { $_ => ( scalar $q->param($_) ) } @$names;
144
145    if ( $type eq 'blog' ) {
146        unless ( $author->is_superuser
147            || ( $perms && $perms->can_administer_blog ) )
148        {
149            if ( $id && !( $perms->can_set_publish_paths ) ) {
150                delete $values{site_url};
151                delete $values{site_path};
152                delete $values{archive_url};
153                delete $values{archive_path};
154            }
155            if ( $id && !( $perms->can_edit_config ) ) {
156                delete $values{$_} foreach grep {
157                         $_ ne 'site_path'
158                      && $_ ne 'site_url'
159                      && $_ ne 'archive_path'
160                      && $_ ne 'archive_url'
161                } @$names;
162            }
163        }
164    }
165
166    if ( $type eq 'author' ) {
167
168        #FIXME: Legacy columns - remove them
169        my @cols = qw(is_superuser can_create_blog can_view_log can_edit_templates);
170        if ( !$author->is_superuser ) {
171            delete $values{$_} for @cols;
172        }
173        else {
174            if ( !$id || ( $author->id != $id ) ) {
175                # Assign the auth_type unless it was assigned
176                # through the form.
177                $obj->auth_type($app->config->AuthenticationModule)
178                    unless $obj->auth_type;
179                if ( $values{'status'} == MT::Author::ACTIVE() ) {
180                    my $sys_perms = MT::Permission->perms('system');
181                    if ( defined($q->param('is_superuser'))
182                      && $q->param('is_superuser')) {
183                        $obj->is_superuser(1);
184                    }
185                    else {
186                        foreach (@$sys_perms) {
187                            my $name = 'can_' . $_->[0];
188                            $name = 'is_superuser' if $name eq 'can_administer';
189                            if ( defined $q->param($name) ) {
190                                $obj->$name( $q->param($name) );
191                                delete $values{$name};
192                            }
193                            else {
194                                $obj->$name(0);
195                            }
196                        }
197                    }
198                }
199            }
200        }
201        delete $values{'password'};
202    }
203
204    if ( $type eq 'blog' ) {
205        # If this is a new blog, set the preferences, archive settings
206        # and template set to the defaults.
207        if ( !$obj->id ) {
208            $obj->language( $app->user->preferred_language );
209            $obj->nofollow_urls(1);
210            $obj->follow_auth_links(1);
211            $obj->page_layout('layout-wtt');
212            my @authenticators = qw( MovableType );
213            foreach my $auth (qw( Vox LiveJournal )) {
214                my $a = MT->commenter_authenticator($auth);
215                if ( !defined $a
216                    || ( exists $a->{condition} && ( !$a->{condition}->() ) ) )
217                {
218                    next;
219                }
220                push @authenticators, $auth;
221            }
222            $obj->commenter_authenticators( join ',', @authenticators );
223            my $set = $app->param('template_set') || 'mt_blog';
224            $obj->template_set( $set );
225        }
226
227        if ( $values{file_extension} ) {
228            $values{file_extension} =~ s/^\.*//
229              if ( $q->param('file_extension') || '' ) ne '';
230        }
231
232        unless ( $values{site_url} =~ m!/$! ) {
233            my $url = $values{site_url};
234            $values{site_url} = $url;
235        }
236    }
237
238    if ( $type eq 'entry' || $type eq 'page' ) {
239
240        # This has to happen prior to callbacks since callbacks may
241        # be affected by the translation...
242
243        # translates naughty words when PublishCharset is NOT UTF-8
244        $app->_translate_naughty_words($obj);
245    }
246
247    if ( $type eq 'template' ) {
248        if (   $q->param('type') eq 'archive'
249            && $q->param('archive_type') )
250        {
251            $values{type} = $q->param('archive_type');
252        }
253    }
254
255    delete $values{'id'} if exists( $values{'id'} ) && !$values{'id'};
256    $obj->set_values( \%values );
257
258    if ( $obj->properties->{audit} ) {
259        $obj->created_by( $author->id ) unless $obj->id;
260        $obj->modified_by( $author->id ) if $obj->id;
261    }
262
263    unless (
264        $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original ) )
265    {
266        if ( 'blog' eq $type ) {
267            my $meth = $q->param('cfg_screen');
268            if ( $meth && $app->handlers_for_mode($meth) ) {
269                $app->error(
270                    $app->translate( "Save failed: [_1]", $app->errstr ) );
271                return $app->$meth;
272            }
273        }
274        return edit( $app,
275            {
276                %$param,
277                error => $app->translate( "Save failed: [_1]", $app->errstr )
278            }
279        );
280    }
281
282    # Done pre-processing the record-to-be-saved; now save it.
283
284    $obj->touch() if ( $type eq 'blog' );
285
286    $obj->save
287      or return $app->error(
288        $app->translate( "Saving object failed: [_1]", $obj->errstr ) );
289
290    # Now post-process it.
291    $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original )
292      or return $app->error( $app->errstr() );
293
294    # Save NWC settings
295    my $screen = $q->param('cfg_screen') || '';
296    if ( $type eq 'blog' && $screen eq 'cfg_entry' ) {
297        my @fields;
298        push( @fields, 'title' )     if $q->param('nwc_title');
299        push( @fields, 'text' )      if $q->param('nwc_text');
300        push( @fields, 'text_more' ) if $q->param('nwc_text_more');
301        push( @fields, 'keywords' )  if $q->param('nwc_keywords');
302        push( @fields, 'excerpt' )   if $q->param('nwc_excerpt');
303        push( @fields, 'tags' )      if $q->param('nwc_tags');
304        my $fields = @fields ? join( ',', @fields ) : 0;
305        $obj->smart_replace_fields( $fields );
306        $obj->smart_replace( $q->param('nwc_smart_replace') );
307        $obj->save;
308    }
309
310    # Finally, decide where to go next, depending on the object type.
311    my $blog_id = $q->param('blog_id');
312    if ( $type eq 'blog' ) {
313        $blog_id = $obj->id;
314    }
315
316    # TODO: convert this to use $app->call_return();
317    # then templates can determine the page flow.
318    if ( $type eq 'notification' ) {
319        return $app->redirect(
320            $app->uri(
321                'mode' => 'list',
322                args   => {
323                    '_type' => 'notification',
324                    blog_id => $blog_id,
325                    saved   => $obj->email
326                }
327            )
328        );
329    }
330    elsif ( my $cfg_screen = $q->param('cfg_screen') ) {
331        if ( $cfg_screen eq 'cfg_publish_profile' ) {
332            my $dcty = $obj->custom_dynamic_templates || 'none';
333            if ( ( $dcty eq 'all' ) || ( $dcty eq 'archives' ) ) {
334                require MT::CMS::Blog;
335                my %param = ();
336                MT::CMS::Blog::_create_build_order( $app, $obj, \%param );
337                $q->param( 'single_template', 1 ); # to show tmpl full-screen
338                if ( $dcty eq 'all' ) {
339                    $q->param( 'type', $param{build_order} );
340                }
341                elsif ( $dcty eq 'archives' ) {
342                    my @ats = map { $_->{archive_type} } @{ $param{archive_type_loop} };
343                    $q->param( 'type', join( ',', @ats ) );
344                }
345                return MT::CMS::Blog::start_rebuild_pages($app);
346            }
347        }
348        if ( $cfg_screen eq 'cfg_templatemaps' ) {
349            $cfg_screen = 'cfg_archives';
350        }
351        my $site_path = $obj->site_path;
352        my $fmgr      = $obj->file_mgr;
353        unless ( $fmgr->exists($site_path) ) {
354            $fmgr->mkpath($site_path);
355        }
356        $app->add_return_arg( no_writedir => 1 )
357          unless $fmgr->exists($site_path) && $fmgr->can_write($site_path);
358    }
359    elsif ( $type eq 'banlist' ) {
360        return $app->redirect(
361            $app->uri(
362                'mode' => 'list',
363                args   => {
364                    '_type' => 'banlist',
365                    blog_id => $blog_id,
366                    saved   => $obj->ip
367                }
368            )
369        );
370    }
371    elsif ( $type eq 'template' && $q->param('rebuild') ) {
372        if ( !$id ) {
373            # add return argument for newly created templates
374            $app->add_return_arg( id => $obj->id );
375        }
376        $q->param( 'type',            'index-' . $obj->id );
377        $q->param( 'tmpl_id',         $obj->id );
378        $q->param( 'single_template', 1 );
379        require MT::CMS::Blog;
380        return MT::CMS::Blog::start_rebuild_pages($app);
381    }
382    elsif ( $type eq 'template' ) {
383        if (   $obj->type eq 'archive'
384            || $obj->type eq 'category'
385            || $obj->type eq 'page'
386            || $obj->type eq 'individual' )
387        {
388            require MT::TemplateMap;
389            my @maps = MT::TemplateMap->load(
390                {
391                    template_id => $obj->id,
392                    build_type  => MT::PublishOption::DYNAMIC()
393                }
394            );
395            my @ats = map { $_->archive_type } @maps;
396            if ($#ats >= 0) {
397                $q->param( 'type', join( ',', @ats ) );
398                $q->param( 'with_indexes', 1 );
399                $q->param( 'no_static', 1 );
400                $q->param( 'template_id', $obj->id );
401                $q->param( 'single_template', 1 );
402                require MT::CMS::Blog;
403                return MT::CMS::Blog::start_rebuild_pages($app);
404            }
405        }
406    }
407    elsif ( $type eq 'blog' ) {
408        return $app->redirect(
409            $app->uri(
410                'mode' => 'cfg_prefs',
411                args   => { blog_id => $blog_id, saved => 1 }
412            )
413        );
414    }
415    elsif ( $type eq 'author' ) {
416        # Delete the author's userpic thumb (if any); it'll be regenerated.
417        if ($original->userpic_asset_id != $obj->userpic_asset_id) {
418            my $thumb_file = $original->userpic_file();
419            my $fmgr = MT::FileMgr->new('Local');
420            if ($fmgr->exists($thumb_file)) {
421                $fmgr->delete($thumb_file);
422            }
423        }
424    }
425
426    $app->add_return_arg( 'id' => $obj->id ) if !$original->id;
427    $app->add_return_arg( 'saved' => 1 );
428    $app->call_return;
429}
430
431sub edit {
432    my $app  = shift;
433    my $q    = $app->param;
434    my $type = $q->param('_type');
435
436    return $app->errtrans("Invalid request.")
437      unless $type;
438
439    # being a general-purpose method, lets look for a mode handler
440    # that is specifically for editing this type. if we find it,
441    # reroute to it.
442
443    my $edit_mode = $app->mode . '_' . $type;
444    if ( my $hdlrs = $app->handlers_for_mode($edit_mode) ) {
445        return $app->forward($edit_mode, @_);
446    }
447
448    my %param = eval { $_[0] ? %{ $_[0] } : (); };
449    die Carp::longmess if $@;
450    my $class = $app->model($type) or return;
451    my $blog_id = $q->param('blog_id');
452
453    if ( defined($blog_id) && $blog_id ) {
454        return $app->error( $app->translate("Invalid parameter") )
455          unless ( $blog_id =~ m/\d+/ );
456    }
457
458    $app->remove_preview_file;
459
460    my $enc = $app->config->PublishCharset;
461    if ( $q->param('_recover') ) {
462        my $sess_obj = $app->autosave_session_obj;
463        if ($sess_obj) {
464            my $data = $sess_obj->thaw_data;
465            if ($data) {
466
467                # XMLHttpRequest always send text in UTF-8... right?
468                if ( 'utf-8' eq lc($enc) ) {
469                    $q->param( $_, $data->{$_} ) for keys %$data;
470                }
471                else {
472                    foreach ( keys %$data ) {
473                        my $encoded =
474                          MT::I18N::encode_text( $data->{$_}, 'utf-8', $enc );
475                        $q->param( $_, $encoded );
476                    }
477                }
478                $param{'recovered_object'} = 1;
479            }
480            else {
481                $param{'recovered_failed'} = 1;
482            }
483        }
484        else {
485            $param{'recovered_failed'} = 1;
486        }
487    }
488    elsif ( $q->param('qp') ) {
489        foreach (qw( title text )) {
490            my $data = $q->param($_);
491            my $encoded = MT::I18N::encode_text( $data, undef, $enc )
492              if $data;
493            $q->param( $_, $encoded );
494        }
495    }
496
497    $param{autosave_frequency} = $app->config->AutoSaveFrequency;
498
499    my $id     = $q->param('id');
500    my $perms  = $app->permissions;
501    my $author = $app->user;
502    my $cfg    = $app->config;
503    $param{styles} = '';
504    if ( $type eq 'author' ) {
505        if ( $perms || $blog_id ) {
506            return $app->return_to_dashboard( redirect => 1 );
507        }
508    }
509    else {
510        if ( ( !$perms || !$blog_id )
511            && ( $type eq 'entry' || $type eq 'page'
512                 || $type eq 'category' || $type eq 'folder'
513                 || $type eq 'comment'  || $type eq 'commenter'
514                 || $type eq 'ping' ) ) {
515            return $app->return_to_dashboard( redirect => 1 );
516        }
517    }
518
519    my $cols = $class->column_names;
520    require MT::Promise;
521    my $obj_promise = MT::Promise::delay(
522        sub {
523            return $class->load($id) || undef;
524        }
525    );
526
527    if ( !$author->is_superuser ) {
528        $app->run_callbacks( 'cms_view_permission_filter.' . $type,
529            $app, $id, $obj_promise )
530          || return $app->error(
531            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
532    }
533    my $obj;
534    my $blog;
535    my $blog_class = $app->model('blog');
536    if ($blog_id) {
537        $blog = $blog_class->load($blog_id);
538    }
539    else {
540        $blog_id = 0;
541    }
542
543    if ($id) {    # object exists, we're just editing it.
544          # Stash the object itself so we don't have to keep forcing the promise
545        $obj = $obj_promise->force()
546          or return $app->error(
547            $app->translate(
548                "Load failed: [_1]",
549                $class->errstr || $app->translate("(no reason given)")
550            )
551          );
552
553        # Populate the param hash with the object's own values
554        for my $col (@$cols) {
555            $param{$col} =
556              defined $q->param($col) ? $q->param($col) : $obj->$col();
557        }
558
559        # Make certain any blog-specific element matches the blog we're
560        # dealing with. If not, call shenanigans.
561        if (   ( exists $param{blog_id} )
562            && ( $blog_id != ($obj->blog_id || 0) ) )
563        {
564            return $app->return_to_dashboard( redirect => 1 );
565        }
566
567        if ( $class->properties->{audit} ) {
568            my $creator = MT::Author->load(
569                {
570                    id   => $obj->created_by(),
571                    type => MT::Author::AUTHOR()
572                }
573            );
574            if ($creator) {
575                $param{created_by} = $creator->name;
576            }
577            if ( my $mod_by = $obj->modified_by() ) {
578                my $modified = MT::Author->load(
579                    {
580                        id   => $mod_by,
581                        type => MT::Author::AUTHOR()
582                    }
583                );
584                if ($modified) {
585                    $param{modified_by} = $modified->name;
586                }
587                else {
588                    $param{modified_by} = $app->translate("(user deleted)");
589                }
590
591                # Since legacy MT installs will still have a
592                # timestamp type for their modified_on fields,
593                # we cannot reliably disaply a modified on date
594                # by default; we must only show the modification
595                # date IF there is also a modified_by value.
596                if ( my $ts = $obj->modified_on ) {
597                    $param{modified_on_ts} = $ts;
598                    $param{modified_on_formatted} =
599                      format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, undef, $app->user ? $app->user->preferred_language : undef );
600                }
601            }
602            if ( my $ts = $obj->created_on ) {
603                $param{created_on_ts} = $ts;
604                $param{created_on_formatted} =
605                  format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, undef, $app->user ? $app->user->preferred_language : undef );
606            }
607        }
608
609        $param{new_object} = 0;
610    }
611    else {    # object is new
612        $param{new_object} = 1;
613        for my $col (@$cols) {
614            $param{$col} = $q->param($col);
615        }
616    }
617
618    my $res = $app->run_callbacks('cms_edit.' . $type, $app, $id, $obj, \%param);
619    if (!$res) {
620        return $app->error($app->callback_errstr());
621    }
622
623    if ($param{autosave_support}) {
624        # autosave support, but don't bother if we're reediting
625        if ( !$app->param('reedit') ) {
626            my $sess_obj = $app->autosave_session_obj;
627            if ($sess_obj) {
628                $param{autosaved_object_exists} = 1;
629                $param{autosaved_object_ts} =
630                  MT::Util::epoch2ts( $blog, $sess_obj->start );
631            }
632        }
633    }
634
635    if ( ( $q->param('msg') || "" ) eq 'nosuch' ) {
636        $param{nosuch} = 1;
637    }
638    for my $p ( $q->param ) {
639        $param{$p} = $q->param($p) if $p =~ /^saved/;
640    }
641    $param{page_actions} = $app->page_actions($type, $obj);
642    if ( $class->can('class_label') ) {
643        $param{object_label} = $class->class_label;
644    }
645    if ( $class->can('class_label_plural') ) {
646        $param{object_label_plural} = $class->class_label_plural;
647    }
648
649    my $tmpl_file = $param{output} || "edit_${type}.tmpl";
650    $param{object_type} ||= $type;
651    $param{screen_id} ||= "edit-$type";
652    $param{screen_class} .= " edit-$type";
653    return $app->load_tmpl( $tmpl_file, \%param );
654}
655
656sub list {
657    my $app  = shift;
658    my $q    = $app->param;
659    my $type = $q->param('_type');
660
661    return $app->errtrans("Invalid request.")
662      unless $type;
663
664    # being a general-purpose method, lets look for a mode handler
665    # that is specifically for editing this type. if we find it,
666    # reroute to it.
667
668    my $list_mode = 'list_' . $type;
669    if ( my $hdlrs = $app->handlers_for_mode($list_mode) ) {
670        return $app->forward($list_mode);
671    }
672
673    my %param = $_[0] ? %{ $_[0] } : ();
674
675    my $perms = $app->permissions;
676    return $app->return_to_dashboard( redirect => 1 )
677      unless $perms;
678    if (
679        $perms
680        && (   ( $type eq 'blog' && !$perms->can_edit_config )
681            || ( $type eq 'template'     && !$perms->can_edit_templates )
682            || ( $type eq 'notification' && !$perms->can_edit_notifications ) )
683      )
684    {
685        return $app->return_to_dashboard( permission => 1 );
686    }
687    my $id        = $q->param('id');
688    my $class     = $app->model($type) or return;
689    my $blog_id   = $q->param('blog_id');
690    my $list_pref = $app->list_pref($type);
691    my ( %terms, %args );
692    %param = ( %param, %$list_pref );
693    my $cols   = $class->column_names;
694    my $limit  = $list_pref->{rows};
695    my $offset = $app->param('offset') || 0;
696
697    for my $name (@$cols) {
698        $terms{blog_id} = $blog_id, last
699          if $name eq 'blog_id';
700    }
701    if ( $type eq 'notification' ) {
702        $args{direction} = 'descend';
703        $args{offset}    = $offset;
704        $args{limit}     = $limit + 1;
705    }
706    elsif ( $type eq 'banlist' ) {
707        $param{use_plugins} = $app->config->UsePlugins;
708        $limit = 0;
709    }
710    my $iter = $class->load_iter( \%terms, \%args );
711
712    my (
713        @data,         @index_data,  @custom_data,
714        @archive_data, @system_data, @widget_data
715    );
716    my (%authors);
717    my $blog_class = $app->model('blog');
718    my $blog       = $blog_class->load($blog_id);
719    my $set        = $blog ? $blog->template_set : undef;
720    require MT::DefaultTemplates;
721    my $dtmpl = MT::DefaultTemplates->templates($set) || [];
722    my %dtmpl = map { $_->{type} => $_ } @$dtmpl;
723
724    while ( my $obj = $iter->() ) {
725        my $row = $obj->column_values;
726        if ( my $ts = $obj->created_on ) {
727            $row->{created_on_formatted} =
728              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
729            $row->{created_on_time_formatted} =
730              format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
731            $row->{created_on_relative} = relative_date( $ts, time, $blog );
732        }
733        if ( $type eq 'template' ) {
734            $row->{name} = '' if !defined $row->{name};
735            $row->{name} =~ s/^\s+|\s+$//g;
736            $row->{name} = "(" . $app->translate("No Name") . ")"
737              if $row->{name} eq '';
738
739            if ( $obj->type eq 'index' ) {
740                push @index_data, $row;
741                $row->{rebuild_me} =
742                  defined $row->{rebuild_me} ? $row->{rebuild_me} : 1;
743                my $published_url = $obj->published_url;
744                $row->{published_url} = $published_url if $published_url;
745            }
746            elsif ( $obj->type eq 'custom' ) {
747                push @custom_data, $row;
748            }
749            elsif ( $obj->type eq 'widget' ) {
750                push @widget_data, $row;
751            }
752            elsif ($obj->type eq 'archive'
753                || $obj->type eq 'category'
754                || $obj->type eq 'page'
755                || $obj->type eq 'individual' )
756            {
757
758                # FIXME: enumeration of types
759                push @archive_data, $row;
760            }
761            else {
762                if ( my $def_tmpl = $dtmpl{ $obj->type } ) {
763                    $row->{description} = $def_tmpl->{description_label};
764                }
765                else {
766
767                    # unknown system template; skip over it
768                    # or should we change it to a custom template
769                    # right now?
770                    next;
771                }
772                push @system_data, $row;
773            }
774            $param{search_label} = $app->translate('Templates');
775        }
776        else {
777            if ( $limit && ( scalar @data == $limit ) ) {
778                $param{next_offset} = 1;
779                last;
780            }
781            push @data, $row;
782        }
783        if ( $type eq 'ping' ) {
784            return $app->list_pings();
785            require MT::Trackback;
786            require MT::Entry;
787            my $tb_center = MT::Trackback->load( $obj->tb_id );
788            my $entry     = MT::Entry->load( $tb_center->entry_id )
789                or return $app->error($app->translate('Can\'t load entry #[_1].', $tb_center->entry_id));
790            if ( my $ts = $obj->created_on ) {
791                $row->{created_on_time_formatted} =
792                  format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
793                $row->{has_edit_access} = $perms->can_edit_all_posts
794                  || $app->user->id == $entry->author_id;
795            }
796        }
797    }    # end loop over the set of objects;
798         # NOW transform the @data array
799    if ( $type eq 'notification' ) {
800        $app->add_breadcrumb( $app->translate('Notification List') );
801        $param{nav_notifications} = 1;
802
803        #@data = sort { $a->{email} cmp $b->{email} } @data;
804        $param{object_type}        = 'notification';
805        $param{list_noncron}       = 1;
806        $param{notification_count} = scalar @data;
807        $param{search_type} = 'entry';
808    }
809    if ( $type eq 'template' ) {
810        $app->add_breadcrumb( $app->translate('Templates') );
811        $param{nav_templates} = 1;
812        for my $ref ( \@index_data, \@custom_data, \@archive_data ) {
813            @$ref = sort { $a->{name} cmp $b->{name} } @$ref;
814        }
815        my $tab = $app->param('tab') || 'index';
816        $param{template_group}      = $tab;
817        $param{"tab_$tab"}          = 1;
818        $param{object_index_loop}   = \@index_data;
819        $param{object_custom_loop}  = \@custom_data;
820        $param{object_widget_loop}  = \@widget_data;
821        $param{object_archive_loop} = \@archive_data;
822        $param{object_system_loop}  = \@system_data;
823        $param{object_type}         = 'template';
824    }
825    else {
826        $param{object_loop} = \@data;
827    }
828
829    # add any breadcrumbs
830    if ( $type eq 'banlist' ) {
831        $app->add_breadcrumb( $app->translate('IP Banning') );
832        $param{nav_config}                       = 1;
833        $param{object_type}                      = 'banlist';
834        $param{show_ip_info}                     = 1;
835        $param{list_noncron}                     = 1;
836        $param{search_type} = 'entry';
837        $param{can_edit_config_or_publish_paths} = $perms->can_edit_config
838          || $perms->can_set_publish_paths;
839    }
840    elsif ( $type eq 'ping' ) {
841        $app->add_breadcrumb( $app->translate('TrackBacks') );
842        $param{nav_trackbacks} = 1;
843        $param{object_type}    = 'ping';
844    }
845    $param{object_count} = scalar @data;
846
847    if ( $type ne 'template' ) {
848        $param{offset}     = $offset;
849        $param{list_start} = $offset + 1;
850        delete $args{limit};
851        delete $args{offset};
852        $param{list_total} = $class->count( \%terms, \%args );
853        $param{list_end}        = $offset + ( scalar @data );
854        $param{next_offset_val} = $offset + ( scalar @data );
855
856    #$param{next_offset} = $param{next_offset_val} < $param{list_total} ? 1 : 0;
857        $param{next_max} = $param{list_total} - $limit;
858        $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1;
859        if ( $offset > 0 ) {
860            $param{prev_offset}     = 1;
861            $param{prev_offset_val} = $offset - $limit;
862            $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0;
863        }
864    }
865
866    $app->load_list_actions( $type, \%param );
867
868    $param{saved}         = $q->param('saved');
869    $param{saved_deleted} = $q->param('saved_deleted');
870    $param{page_actions}  = $app->page_actions( 'list_' . $type );
871    unless ( $param{screen_class} ) {
872        $param{screen_class} = "list-$type";
873    }
874    $app->load_tmpl( "list_${type}.tmpl", \%param );
875}
876
877sub delete {
878    my $app  = shift;
879    my $q    = $app->param;
880    my $type = $q->param('_type');
881
882    return $app->errtrans("Invalid request.")
883      unless $type;
884
885    return $app->error( $app->translate("Invalid request.") )
886      if $app->request_method() ne 'POST';
887
888    # being a general-purpose method, lets look for a mode handler
889    # that is specifically for editing this type. if we find it,
890    # reroute to it.
891
892    my $delete_mode = 'delete_' . $type;
893    if ( my $hdlrs = $app->handlers_for_mode($delete_mode) ) {
894        return $app->forward($delete_mode);
895    }
896
897    my $parent  = $q->param('parent');
898    my $blog_id = $q->param('blog_id');
899    my $class   = $app->model($type) or return;
900    my $perms   = $app->permissions;
901    my $author  = $app->user;
902
903    $app->validate_magic() or return;
904
905    my ( $entry_id, $cat_id, $author_id ) = ( "", "", "" );
906    my %rebuild_entries;
907    my @rebuild_cats;
908    my $required_items = 0;
909    for my $id ( $q->param('id') ) {
910        next unless $id;    # avoid 'empty' ids
911        if ( ( $type eq 'association' ) && ( $id =~ /PSEUDO-/ ) ) {
912            $app->_delete_pseudo_association($id);
913            next;
914        }
915
916        my $obj = $class->load($id);
917        next unless $obj;
918        $app->run_callbacks( 'cms_delete_permission_filter.' . $type,
919            $app, $obj )
920          || return $app->error(
921            $app->translate( "Permission denied: [_1]", $app->errstr() ) );
922
923        if ( $type eq 'comment' ) {
924            $entry_id = $obj->entry_id;
925            $rebuild_entries{$entry_id} = 1 if $obj->visible;
926        }
927        elsif ( $type eq 'ping' || $type eq 'ping_cat' ) {
928            require MT::Trackback;
929            my $tb = MT::Trackback->load( $obj->tb_id );
930            if ($tb) {
931                $entry_id = $tb->entry_id;
932                $cat_id   = $tb->category_id;
933                if ( $obj->visible ) {
934                    $rebuild_entries{$entry_id} = 1 if $entry_id;
935                    push @rebuild_cats, $cat_id if $cat_id;
936                }
937            }
938        }
939        elsif ( $type eq 'tag' ) {
940
941            # if we're in a blog context, remove ONLY tags from that weblog
942            if ($blog_id) {
943                my $ot_class  = $app->model('objecttag');
944                my $obj_type  = $q->param('__type') || 'entry';
945                my $obj_class = $app->model($obj_type);
946                my $iter      = $ot_class->load_iter(
947                    {
948                        blog_id           => $blog_id,
949                        object_datasource => $obj_class->datasource,
950                        tag_id            => $id
951                    },
952                    {
953                        'join' => $obj_class->join_on(
954                            undef,
955                            {
956                                id => \'= objecttag_object_id',
957                                (
958                                    $obj_class =~ m/asset/i
959                                    ? ()
960                                    : ( class => $obj_class->class_type )
961                                )
962                            }
963                        )
964                    }
965                );
966
967                if ($iter) {
968                    my @ot;
969                    while ( my $obj = $iter->() ) {
970                        push @ot, $obj->id;
971                    }
972                    foreach (@ot) {
973                        my $obj = $ot_class->load($_);
974                        next unless $obj;
975                        $obj->remove
976                          or return $app->errtrans( 'Removing tag failed: [_1]',
977                            $obj->errstr );
978                    }
979                }
980
981                $app->run_callbacks( 'cms_post_delete.' . $type, $app, $obj );
982                next;
983
984            }
985        }
986        elsif ( $type eq 'category' ) {
987            my @kids = MT::Category->load( { parent => $id } );
988            return $app->errtrans(
989"You can't delete that category because it has sub-categories. Move or delete the sub-categories first if you want to delete this one."
990            ) if @kids;
991            if ( $app->config('DeleteFilesAtRebuild') ) {
992                require MT::Blog;
993                require MT::Entry;
994                require MT::Placement;
995                my $blog = MT::Blog->load($blog_id)
996                    or return $app->error($app->translate('Can\'t load blog #[_1].', $blog_id));
997                my $at   = $blog->archive_type;
998                if ( $at && $at ne 'None' ) {
999                    my @at = split /,/, $at;
1000                    for my $target (@at) {
1001                        my $archiver = $app->publisher->archiver($target);
1002                        next unless $archiver;
1003                        if ( $archiver->category_based ) {
1004                            if ( $archiver->date_based ) {
1005                                my @entries = MT::Entry->load(
1006                                    { status => MT::Entry::RELEASE() },
1007                                    {
1008                                        join => MT::Placement->join_on(
1009                                            'entry_id',
1010                                            { category_id => $id },
1011                                            { unqiue      => 1 }
1012                                        )
1013                                    }
1014                                );
1015                                for (@entries) {
1016                                    $app->publisher->remove_entry_archive_file(
1017                                        Category    => $obj,
1018                                        ArchiveType => $target,
1019                                        Entry       => $_
1020                                    );
1021                                }
1022                            }
1023                            else {
1024                                $app->publisher->remove_entry_archive_file(
1025                                    Category    => $obj,
1026                                    ArchiveType => $target
1027                                );
1028                            }
1029                        }
1030                    }
1031                }
1032            }
1033        }
1034        elsif ( $type eq 'page' ) {
1035            if ( $app->config('DeleteFilesAtRebuild') ) {
1036                $app->publisher->remove_entry_archive_file(
1037                    Entry       => $obj,
1038                    ArchiveType => 'Page'
1039                );
1040            }
1041        }
1042        elsif ( $type eq 'author' ) {
1043            if ( $app->config->ExternalUserManagement ) {
1044                require MT::LDAP;
1045                my $ldap = MT::LDAP->new
1046                  or return $app->error(
1047                    MT->translate(
1048                        "Loading MT::LDAP failed: [_1].",
1049                        MT::LDAP->errstr
1050                    )
1051                  );
1052                my $dn = $ldap->get_dn( $obj->name );
1053                if ($dn) {
1054                    $app->add_return_arg( author_ldap_found => 1 );
1055                }
1056            }
1057        }
1058
1059        # FIXME: enumeration of types
1060        if (   $type eq 'template'
1061            && $obj->type !~
1062            /(custom|index|archive|page|individual|category|widget|backup)/o )
1063        {
1064            $required_items++;
1065        }
1066        else {
1067            $obj->remove
1068              or return $app->errtrans(
1069                'Removing [_1] failed: [_2]',
1070                $app->translate($type),
1071                $obj->errstr
1072              );
1073            $app->run_callbacks( 'cms_post_delete.' . $type, $app, $obj );
1074        }
1075    }
1076    require MT::Entry;
1077    for my $entry_id ( keys %rebuild_entries ) {
1078        my $entry = MT::Entry->load($entry_id);
1079        $app->rebuild_entry( Entry => $entry, BuildDependencies => 1 )
1080            or return $app->publish_error();
1081    }
1082    for my $cat_id (@rebuild_cats) {
1083
1084        # FIXME: What about other category-based archives?
1085        # What if user is not publishing category archives?
1086        my $cat = MT::Category->load($cat_id);
1087        $app->rebuild(
1088            Category    => $cat,
1089            BlogID      => $blog_id,
1090            ArchiveType => 'Category'
1091        ) or return $app->publish_error();
1092    }
1093    $app->run_callbacks( 'rebuild', MT::Blog->load($blog_id) );
1094    $app->add_return_arg(
1095        $type eq 'ping'
1096        ? ( saved_deleted_ping => 1 )
1097        : ( saved_deleted => 1 )
1098    );
1099    if ( $q->param('is_power_edit') ) {
1100        $app->add_return_arg( is_power_edit => 1 );
1101    }
1102    if ($required_items) {
1103        $app->add_return_arg(
1104            error => $app->translate("System templates can not be deleted.") );
1105    }
1106    $app->call_return;
1107}
1108
1109sub not_junk_test {
1110    my ( $eh, $app, $obj ) = @_;
1111    require MT::JunkFilter;
1112    MT::JunkFilter->filter($obj);
1113    $obj->is_junk ? 0 : 1;
1114}
1115
11161;
Note: See TracBrowser for help on using the browser.