root/branches/release-30/lib/MT/CMS/Entry.pm @ 1383

Revision 1383, 76.4 kB (checked in by bchoate, 21 months ago)

Fixed calls to ping continuation from MT::CMS::Entry.

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Entry;
2
3use strict;
4use MT::Util qw( format_ts relative_date remove_html encode_html encode_js
5    encode_url archive_file_for offset_time_list );
6use MT::I18N qw( substr_text const length_text wrap_text encode_text
7    break_up_text first_n_text guess_encoding );
8
9sub edit {
10    my $cb = shift;
11    my ($app, $id, $obj, $param) = @_;
12
13    my $q = $app->param;
14    my $type = $q->param('_type');
15    my $perms = $app->permissions;
16    my $blog_class = $app->model('blog');
17    my $blog = $app->blog;
18    my $blog_id = $blog->id;
19    my $author = $app->user;
20    my $class = $app->model($type);
21
22    # to trigger autosave logic in main edit routine
23    $param->{autosave_support} = 1;
24
25    if ($id) {
26        return $app->error( $app->translate("Invalid parameter") )
27          if $obj->class ne $type;
28
29        $param->{nav_entries} = 1;
30        $param->{entry_edit}  = 1;
31        if ( $type eq 'entry' ) {
32            $app->add_breadcrumb(
33                $app->translate('Entries'),
34                $app->uri(
35                    'mode' => 'list_entries',
36                    args   => { blog_id => $blog_id }
37                )
38            );
39        }
40        elsif ( $type eq 'page' ) {
41            $app->add_breadcrumb(
42                $app->translate('Pages'),
43                $app->uri(
44                    'mode' => 'list_pages',
45                    args   => { blog_id => $blog_id }
46                )
47            );
48        }
49        $app->add_breadcrumb( $obj->title
50              || $app->translate('(untitled)') );
51        ## Don't pass in author_id, because it will clash with the
52        ## author_id parameter of the author currently logged in.
53        delete $param->{'author_id'};
54        unless ( defined $q->param('category_id') ) {
55            delete $param->{'category_id'};
56            if ( my $cat = $obj->category ) {
57                $param->{category_id} = $cat->id;
58            }
59        }
60        $blog_id = $obj->blog_id;
61        my $blog = $app->model('blog')->load($blog_id);
62        my $status = $q->param('status') || $obj->status;
63        $param->{ "status_" . MT::Entry::status_text($status) } = 1;
64        $param->{ "allow_comments_"
65              . ( $q->param('allow_comments') || $obj->allow_comments || 0 )
66          } = 1;
67        $param->{'authored_on_date'} = $q->param('authored_on_date')
68          || format_ts( "%Y-%m-%d", $obj->authored_on, $blog, $app->user ? $app->user->preferred_language : undef );
69        $param->{'authored_on_time'} = $q->param('authored_on_time')
70          || format_ts( "%H:%M:%S", $obj->authored_on, $blog, $app->user ? $app->user->preferred_language : undef );
71
72        require MT::Comment;
73        $param->{num_comments} = MT::Comment->count({ entry_id => $id, visible => 1 });
74
75        require MT::Trackback;
76        my $tb = MT::Trackback->load( { entry_id => $obj->id } );
77        require MT::TBPing;
78        $param->{num_pings} = $tb ? MT::TBPing->count({ tb_id => $tb->id, visible => 1 }) : 0;
79
80        # Check permission to send notifications and if the
81        # blog has notification list subscribers
82        if (   $perms->can_send_notifications
83            && $obj->status == MT::Entry::RELEASE() )
84        {
85            my $not_class = $app->model('notification');
86            $param->{can_send_notifications} = 1;
87            $param->{has_subscribers} =
88              $not_class->count( { blog_id => $blog_id } );
89        }
90
91        ## Load next and previous entries for next/previous links
92        if ( my $next = $obj->next ) {
93            $param->{next_entry_id} = $next->id;
94        }
95        if ( my $prev = $obj->previous ) {
96            $param->{previous_entry_id} = $prev->id;
97        }
98
99        $param->{has_any_pinged_urls} = ( $obj->pinged_urls || '' ) =~ m/\S/;
100        $param->{ping_errors}         = $q->param('ping_errors');
101        $param->{can_view_log}        = $app->user->can_view_log;
102        $param->{entry_permalink}     = $obj->permalink;
103        $param->{'mode_view_entry'}   = 1;
104        $param->{'basename_old'}      = $obj->basename;
105
106        if ( my $ts = $obj->authored_on ) {
107            $param->{authored_on_ts} = $ts;
108            $param->{authored_on_formatted} =
109              format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
110        }
111
112        $app->load_list_actions( $type, $param );
113    } else {
114        $param->{entry_edit} = 1;
115        if ($blog_id) {
116            if ( $type eq 'entry' ) {
117                $app->add_breadcrumb(
118                    $app->translate('Entries'),
119                    $app->uri(
120                        'mode' => 'list_entries',
121                        args   => { blog_id => $blog_id }
122                    )
123                );
124                $app->add_breadcrumb( $app->translate('New Entry') );
125                $param->{nav_new_entry} = 1;
126            }
127            elsif ( $type eq 'page' ) {
128                $app->add_breadcrumb(
129                    $app->translate('Pages'),
130                    $app->uri(
131                        'mode' => 'list_pages',
132                        args   => { blog_id => $blog_id }
133                    )
134                );
135                $app->add_breadcrumb( $app->translate('New Page') );
136                $param->{nav_new_page} = 1;
137            }
138        }
139
140        # (if there is no blog_id parameter, this is a
141        # bookmarklet post and doesn't need breadcrumbs.)
142        delete $param->{'author_id'};
143        delete $param->{'pinged_urls'};
144        my $blog_timezone = 0;
145        if ($blog_id) {
146            my $blog = $blog_class->load($blog_id);
147            $blog_timezone = $blog->server_offset();
148            if ( $type eq 'entry' ) {
149
150                # We only use new entry defaults on new entries.
151                my $def_status = $q->param('status')
152                  || $blog->status_default;
153                if ($def_status) {
154                    $param->{ "status_"
155                          . MT::Entry::status_text($def_status) } = 1;
156                }
157                $param->{
158                    'allow_comments_'
159                      . (
160                        defined $q->param('allow_comments')
161                        ? $q->param('allow_comments')
162                        : $blog->allow_comments_default
163                      )
164                  }
165                  = 1;
166                $param->{allow_comments} = $blog->allow_comments_default
167                  unless defined $q->param('allow_comments');
168                $param->{allow_pings} = $blog->allow_pings_default
169                  unless defined $q->param('allow_pings');
170            }
171        }
172
173        require POSIX;
174        my @now = offset_time_list( time, $blog );
175        $param->{authored_on_date} = $q->param('authored_on_date')
176          || POSIX::strftime( "%Y-%m-%d", @now );
177        $param->{authored_on_time} = $q->param('authored_on_time')
178          || POSIX::strftime( "%H:%M:%S", @now );
179    }
180
181    ## Load categories and process into loop for category pull-down.
182    require MT::Placement;
183    my $cat_id = $param->{category_id};
184    my $depth  = 0;
185    my %places;
186
187    # set the dirty flag in js?
188    $param->{dirty} = $q->param('dirty') ? 1 : 0;
189
190    if ($id) {
191        my @places =
192          MT::Placement->load( { entry_id => $id, is_primary => 0 } );
193        %places = map { $_->category_id => 1 } @places;
194    }
195    my $cats = $q->param('category_ids');
196    if ( defined $cats ) {
197        if ( my @cats = split /,/, $cats ) {
198            $cat_id = $cats[0];
199            %places = map { $_ => 1 } @cats;
200        }
201    }
202    if ( $q->param('reedit') ) {
203        $param->{reedit} = 1;
204        if ( !$q->param('basename_manual') ) {
205            $param->{'basename'} = '';
206        }
207    }
208    if ($blog) {
209        $param->{file_extension} = $blog->file_extension || '';
210        $param->{file_extension} = '.' . $param->{file_extension}
211          if $param->{file_extension} ne '';
212    }
213    else {
214        $param->{file_extension} = 'html';
215    }
216
217    ## Now load user's preferences and customization for new/edit
218    ## entry page.
219    if ($perms) {
220        my $pref_param = $app->load_entry_prefs( $perms->entry_prefs );
221        %$param = ( %$param, %$pref_param );
222        $param->{disp_prefs_bar_colspan} = $param->{new_object} ? 1 : 2;
223
224        # Completion for tags
225        my $auth_prefs = $author->entry_prefs;
226        if ( my $delim = chr( $auth_prefs->{tag_delim} ) ) {
227            if ( $delim eq ',' ) {
228                $param->{'auth_pref_tag_delim_comma'} = 1;
229            }
230            elsif ( $delim eq ' ' ) {
231                $param->{'auth_pref_tag_delim_space'} = 1;
232            }
233            else {
234                $param->{'auth_pref_tag_delim_other'} = 1;
235            }
236            $param->{'auth_pref_tag_delim'} = $delim;
237        }
238
239        require MT::ObjectTag;
240        my $count = MT::Tag->count(
241            undef,
242            {
243                'join' => MT::ObjectTag->join_on(
244                    'tag_id',
245                    {
246                        blog_id           => $blog_id,
247                        object_datasource => MT::Entry->datasource
248                    },
249                    { unique => 1 }
250                )
251            }
252        );
253        if ( $count > 1000 ) {    # FIXME: Configurable limit?
254            $param->{defer_tag_load} = 1;
255        }
256        else {
257            require JSON;
258            $param->{tags_js} =
259              JSON::objToJson(
260                MT::Tag->cache( blog_id => $blog_id, class => 'MT::Entry', private => 1 )
261              );
262        }
263
264        $param->{can_edit_categories} = $perms->can_edit_categories;
265    }
266
267    my $data = $app->_build_category_list(
268        blog_id => $blog_id,
269        markers => 1,
270        type    => $class->container_type,
271    );
272    my $top_cat = $cat_id;
273    my @sel_cats;
274    my $cat_tree = [];
275    if ( $type eq 'page' ) {
276        push @$cat_tree,
277          {
278            id    => -1,
279            label => '/',
280            basename => '/',
281            path  => [],
282          };
283        $top_cat ||= -1;
284    }
285    foreach (@$data) {
286        next unless exists $_->{category_id};
287        if ( $type eq 'page' ) {
288            $_->{category_path_ids} ||= [];
289            unshift @{ $_->{category_path_ids} }, -1;
290        }
291        push @$cat_tree,
292          {
293            id => $_->{category_id},
294            label => $_->{category_label} . ( $type eq 'page' ? '/' : '' ),
295            basename => $_->{category_basename} . ( $type eq 'page' ? '/' : '' ),
296            path => $_->{category_path_ids} || [],
297          };
298        push @sel_cats, $_->{category_id}
299          if $places{ $_->{category_id} }
300          && $_->{category_id} != $cat_id;
301    }
302    $param->{category_tree} = $cat_tree;
303    unshift @sel_cats, $top_cat if defined $top_cat && $top_cat ne "";
304    $param->{selected_category_loop}   = \@sel_cats;
305    $param->{have_multiple_categories} = scalar @$data > 1;
306
307    $param->{basename_limit} = ( $blog ? $blog->basename_limit : 0 ) || 30;
308
309    if ( $q->param('tags') ) {
310        $param->{tags} = $q->param('tags');
311    }
312    else {
313        if ($obj) {
314            my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
315            require MT::Tag;
316            my $tags = MT::Tag->join( $tag_delim, $obj->tags );
317            $param->{tags} = $tags;
318        }
319    }
320
321    ## Load text filters if user displays them
322    my %entry_filters;
323    if ( defined( my $filter = $q->param('convert_breaks') ) ) {
324        $entry_filters{$filter} = 1;
325    }
326    elsif ($obj) {
327        %entry_filters = map { $_ => 1 } @{ $obj->text_filters };
328    }
329    elsif ($blog) {
330        my $cb = $author->text_format || $blog->convert_paras;
331        $cb = '__default__' if $cb eq '1';
332        $entry_filters{$cb} = 1;
333        $param->{convert_breaks} = $cb;
334    }
335    my $filters = MT->all_text_filters;
336    $param->{text_filters} = [];
337    for my $filter ( keys %$filters ) {
338        push @{ $param->{text_filters} },
339          {
340            filter_key      => $filter,
341            filter_label    => $filters->{$filter}{label},
342            filter_selected => $entry_filters{$filter},
343            filter_docs     => $filters->{$filter}{docs},
344          };
345    }
346    $param->{text_filters} =
347      [ sort { $a->{filter_key} cmp $b->{filter_key} }
348          @{ $param->{text_filters} } ];
349    unshift @{ $param->{text_filters} },
350      {
351        filter_key      => '0',
352        filter_label    => $app->translate('None'),
353        filter_selected => ( !keys %entry_filters ),
354      };
355
356    if ($blog) {
357        if ( !defined $param->{convert_breaks} ) {
358            my $cb = $blog->convert_paras;
359            $cb = '__default__' if $cb eq '1';
360            $param->{convert_breaks} = $cb;
361        }
362        my $ext = ( $blog->file_extension || '' );
363        $ext = '.' . $ext if $ext ne '';
364        $param->{blog_file_extension} = $ext;
365    }
366
367    my $rte;
368    if ($param->{convert_breaks} eq 'richtext') {
369        ## Rich Text editor
370        $rte = lc($app->config('RichTextEditor'));
371    }
372    else {
373        $rte = 'archetype';
374    }
375    my $editors = $app->registry("richtext_editors");
376    my $edit_reg = $editors->{$rte} || $editors->{archetype};
377    my $rich_editor_tmpl;
378    if ($rich_editor_tmpl = $edit_reg->{plugin}->load_tmpl($edit_reg->{template})) {
379        $param->{rich_editor} = $rte;
380        $param->{rich_editor_tmpl} = $rich_editor_tmpl;
381    }
382
383    $param->{object_type}  = $type;
384    $param->{quickpost_js} = MT::CMS::Entry::quickpost_js($app, $type);
385    if ( 'page' eq $type ) {
386        $param->{search_label} = $app->translate('pages');
387        $param->{output}       = 'edit_entry.tmpl';
388        $param->{screen_class} = 'edit-page edit-entry';
389    }
390    $param->{sitepath_configured} = $blog && $blog->site_path ? 1 : 0;
391    1;
392}
393
394sub list {
395    my $app = shift;
396    my ($param) = @_;
397    $param ||= {};
398
399    require MT::Entry;
400    my $type = $param->{type} || MT::Entry->class_type;
401    my $pkg = $app->model($type) or return "Invalid request.";
402
403    my $q     = $app->param;
404    my $perms = $app->permissions;
405    if ( $type eq 'page' ) {
406        if ( $perms
407            && ( !$perms->can_manage_pages ) )
408        {
409            return $app->errtrans("Permission denied.");
410        }
411    }
412    else {
413        if (
414            $perms
415            && (   !$perms->can_edit_all_posts
416                && !$perms->can_create_post
417                && !$perms->can_publish_post )
418          )
419        {
420            return $app->errtrans("Permission denied.");
421        }
422    }
423
424    my $list_pref = $app->list_pref($type);
425    my %param     = %$list_pref;
426    my $blog_id   = $q->param('blog_id');
427    my %terms;
428    $terms{blog_id} = $blog_id if $blog_id;
429    $terms{class} = $type;
430    my $limit = $list_pref->{rows};
431    my $offset = $app->param('offset') || 0;
432
433    if ( !$blog_id && !$app->user->is_superuser ) {
434        require MT::Permission;
435        $terms{blog_id} = [
436            map { $_->blog_id }
437              grep { $_->can_create_post || $_->can_edit_all_posts }
438              MT::Permission->load( { author_id => $app->user->id } )
439        ];
440    }
441
442    my %arg;
443    my $filter_key = $q->param('filter_key') || '';
444    my $filter_col = $q->param('filter')     || '';
445    my $filter_val = $q->param('filter_val');
446
447    # check blog_id for deciding to apply category filter or not
448    my ( $filter_name, $filter_value );    # human-readable versions
449    if ( !exists( $terms{$filter_col} ) ) {
450        if ( $filter_col eq 'category_id' ) {
451            $filter_name = $app->translate('Category');
452            require MT::Category;
453            my $cat = MT::Category->load($filter_val);
454            return $app->errtrans( "Load failed: [_1]", MT::Category->errstr )
455              unless $cat;
456            if ( $cat->blog_id != $blog_id ) {
457                $filter_key = '';
458                $filter_col = '';
459                $filter_val = '';
460            }
461            $filter_value = $cat->label;
462        }
463        elsif ( $filter_col eq 'asset_id' ) {
464            $filter_name = $app->translate('Asset');
465            my $asset_class = MT->model('asset');
466            my $asset = $asset_class->load($filter_val);
467            return $app->errtrans( "Load failed: [_1]", $asset_class->errstr )
468              unless $asset;
469            if ($asset->blog_id != $blog_id) {
470                $filter_key = '';
471                $filter_col = '';
472                $filter_val = '';
473            }
474            $filter_value = $param{asset_label} = $asset->label;
475        }
476    }
477    if ( $filter_col && $filter_val ) {
478        if ( 'power_edit' eq $filter_col ) {
479            $filter_col = 'id';
480            unless ( 'ARRAY' eq ref($filter_val) ) {
481                my @values = $app->param('filter_val');
482                $filter_val = \@values;
483            }
484        }
485        if ( !exists( $terms{$filter_col} ) ) {
486            if ( $filter_col eq 'category_id' ) {
487                $arg{'join'} = MT::Placement->join_on(
488                    'entry_id',
489                    { category_id => $filter_val },
490                    { unique      => 1 }
491                );
492            }
493            elsif ( $filter_col eq 'asset_id' ) {
494                $arg{'join'} = MT->model('objectasset')->join_on(
495                    'object_id',
496                    { object_ds => $type,
497                      asset_id  => $filter_val },
498                    { unique    => 1 }
499                );
500            }
501            elsif (( $filter_col eq 'normalizedtag' )
502                || ( $filter_col eq 'exacttag' ) )
503            {
504                my $normalize = ( $filter_col eq 'normalizedtag' );
505                require MT::Tag;
506                require MT::ObjectTag;
507                my $tag_delim   = chr( $app->user->entry_prefs->{tag_delim} );
508                my @filter_vals = MT::Tag->split( $tag_delim, $filter_val );
509                my @filter_tags = @filter_vals;
510                if ($normalize) {
511                    push @filter_tags, MT::Tag->normalize($_)
512                      foreach @filter_vals;
513                }
514                my @tags = MT::Tag->load( { name => [@filter_tags] },
515                    { binary => { name => 1 } } );
516                my @tag_ids;
517                foreach (@tags) {
518                    push @tag_ids, $_->id;
519                    if ($normalize) {
520                        my @more = MT::Tag->load(
521                            { n8d_id => $_->n8d_id ? $_->n8d_id : $_->id } );
522                        push @tag_ids, $_->id foreach @more;
523                    }
524                }
525                @tag_ids = (0) unless @tags;
526                $arg{'join'} = MT::ObjectTag->join_on(
527                    'object_id',
528                    {
529                        tag_id            => \@tag_ids,
530                        object_datasource => $pkg->datasource
531                    },
532                    { unique => 1 }
533                );
534            }
535            else {
536                $terms{$filter_col} = $filter_val;
537            }
538            $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($filter_val);
539
540            if (   ( $filter_col eq 'normalizedtag' )
541                || ( $filter_col eq 'exacttag' ) )
542            {
543                $filter_name  = $app->translate('Tag');
544                $filter_value = $filter_val;
545            }
546            elsif ( $filter_col eq 'author_id' ) {
547                $filter_name = $app->translate('User');
548                my $author = MT::Author->load($filter_val);
549                return $app->errtrans( "Load failed: [_1]", MT::Author->errstr )
550                  unless $author;
551                $filter_value = $author->name;
552            }
553            elsif ( $filter_col eq 'status' ) {
554                $filter_name = $app->translate('Entry Status');
555                $filter_value =
556                  $app->translate( MT::Entry::status_text($filter_val) );
557            }
558            if ( $filter_name && $filter_value ) {
559                $param{filter}                        = $filter_col;
560                $param{ 'filter_col_' . $filter_col } = 1;
561                $param{filter_val}                    = $filter_val;
562            }
563        }
564        $param{filter_unpub} = $filter_col eq 'status';
565    }
566    elsif ($filter_key) {
567        my $filters = $app->registry("list_filters", "entry") || {};
568        if ( my $filter = $filters->{$filter_key} ) {
569            if ( my $code = $filter->{code}
570                || $app->handler_to_coderef( $filter->{handler} ) )
571            {
572                $param{filter_key}   = $filter_key;
573                $param{filter_label} = $filter->{label};
574                $code->( \%terms, \%arg );
575            }
576        }
577    }
578    require MT::Category;
579    require MT::Placement;
580
581    my $total = $pkg->count( \%terms, \%arg ) || 0;
582    $arg{'sort'} = $type eq 'page' ? 'modified_on' : 'authored_on';
583    $arg{direction} = 'descend';
584    $arg{limit}     = $limit + 1;
585    if ( $total && $offset > $total - 1 ) {
586        $arg{offset} = $offset = $total - $limit;
587    }
588    elsif ( $offset && ( ( $offset < 0 ) || ( $total - $offset < $limit ) ) ) {
589        $arg{offset} = $offset = $total - $offset;
590    }
591    else {
592        $arg{offset} = $offset if $offset;
593    }
594
595    my $iter = $pkg->load_iter( \%terms, \%arg );
596
597    my $is_power_edit = $q->param('is_power_edit');
598    if ($is_power_edit) {
599        $param{has_expanded_mode} = 0;
600        delete $param{view_expanded};
601    }
602    else {
603        $param{has_expanded_mode} = 1;
604    }
605    my $data = build_entry_table( $app,
606        iter          => $iter,
607        is_power_edit => $is_power_edit,
608        param         => \%param,
609        type          => $type
610    );
611    delete $param{entry_table} unless @$data;
612
613    ## We tried to load $limit + 1 entries above; if we actually got
614    ## $limit + 1 back, we know we have another page of entries.
615    my $have_next_entry = @$data > $limit;
616    pop @$data while @$data > $limit;
617    if ($offset) {
618        $param{prev_offset}     = 1;
619        $param{prev_offset_val} = $offset - $limit;
620        $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0;
621    }
622    if ($have_next_entry) {
623        $param{next_offset}     = 1;
624        $param{next_offset_val} = $offset + $limit;
625    }
626
627    $iter = MT::Author->load_iter(
628        { type => MT::Author::AUTHOR() },
629        {
630            'join' => $pkg->join_on(
631                'author_id',
632                { $blog_id ? ( blog_id => $blog_id ) : () },
633                {
634                    'unique'  => 1,
635                    'sort'    => 'authored_on',
636                    direction => 'descend'
637                }
638            ),
639            limit => 51,
640        }
641    );
642    my %seen;
643    my @authors;
644    while ( my $au = $iter->() ) {
645        next if $seen{ $au->id };
646        $seen{ $au->id } = 1;
647        my $row = {
648            author_name => $au->name,
649            author_id   => $au->id
650        };
651        push @authors, $row;
652        if ( @authors == 50 ) {
653            $iter->('finish');
654            last;
655        }
656    }
657    $param{entry_author_loop} = \@authors;
658
659    $iter = $app->model('asset')->load_iter(
660        { class => '*', blog_id => $blog_id },
661        {
662            'join' => MT->model('objectasset')->join_on(
663                'asset_id',
664                {},
665                { unique => 1 }
666            ),
667            'sort'    => 'created_on',
668            direction => 'descend',
669        }
670    );
671    %seen = ();
672    my @assets;
673    while ( my $asset = $iter->() ) {
674        next if $seen{ $asset->id };
675        $seen{ $asset->id } = 1;
676        my $row = {
677            asset_label => $asset->label,
678            asset_id    => $asset->id,
679        };
680        push @assets, $row;
681        if ( @assets == 50 ) {
682            $iter->('finish');
683            last;
684        }
685    }
686    if ($filter_col eq 'asset_id' && !$seen{$filter_val}) {
687        push @assets, {
688            asset_label => $param{asset_label},
689            asset_id    => $filter_val,
690        };
691    }
692    $param{entry_asset_loop} = \@assets;
693
694    $param{page_actions}        = $app->page_actions( $app->mode );
695    $param{list_filters}        = $app->list_filters('entry');
696    $param{can_power_edit}      = $blog_id && !$is_power_edit;
697    $param{can_republish}       = $blog_id ? $perms->can_rebuild : 1;
698    $param{is_power_edit}       = $is_power_edit;
699    $param{saved_deleted}       = $q->param('saved_deleted');
700    $param{saved}               = $q->param('saved');
701    $param{limit}               = $limit;
702    $param{offset}              = $offset;
703    $param{object_type}         = $type;
704    $param{object_label}        = $pkg->class_label;
705    $param{object_label_plural} = $param{search_label} =
706      $pkg->class_label_plural;
707    $param{list_start}  = $offset + 1;
708    $param{list_end}    = $offset + scalar @$data;
709    $param{list_total}  = $total;
710    $param{next_max}    = $param{list_total} - $limit;
711    $param{next_max}    = 0 if ( $param{next_max} || 0 ) < $offset + 1;
712    $param{nav_entries} = 1;
713    $param{feed_label}  = $app->translate( "[_1] Feed", $pkg->class_label );
714    $param{feed_url} =
715      $app->make_feed_link( $type, $blog_id ? { blog_id => $blog_id } : undef );
716    $app->add_breadcrumb( $pkg->class_label_plural );
717    $param{listing_screen} = 1;
718
719    unless ($blog_id) {
720        $param{system_overview_nav} = 1;
721    }
722    $param{container_label} = $pkg->container_label;
723    unless ( $param{screen_class} ) {
724        $param{screen_class} = "list-$type";
725        $param{screen_class} .= " list-entry"
726          if $param{object_type} eq "page";  # to piggyback on list-entry styles
727    }
728    $param{mode}            = $app->mode;
729    if ( my $blog = MT::Blog->load($blog_id) ) {
730        $param{sitepath_unconfigured} = $blog->site_path ? 0 : 1;
731    }
732
733    $param->{return_args} ||= $app->make_return_args;
734    my @return_args = grep { $_ !~ /offset=\d/ } split /&/, $param->{return_args};
735    $param{return_args} = join '&', @return_args;
736    $param{return_args} .= "&offset=$offset" if $offset;
737    $param{screen_id} = "list-entry";
738    $param{screen_id} = "list-page"
739      if $param{object_type} eq "page";
740    if ( $param{is_power_edit} ) {
741        $param{screen_id} = "batch-edit-entry";
742        $param{screen_id} = "batch-edit-page"
743          if $param{object_type} eq "page";
744        $param{screen_class} .= " batch-edit";
745    }
746    $app->load_tmpl( "list_entry.tmpl", \%param );
747}
748
749sub preview {
750    my $app         = shift;
751    my $q           = $app->param;
752    my $type        = $q->param('_type') || 'entry';
753    my $entry_class = $app->model($type);
754    my $blog_id     = $q->param('blog_id');
755    my $blog        = $app->blog;
756    my $id          = $q->param('id');
757    my $entry;
758    my $user_id = $app->user->id;
759
760    if ($id) {
761        $entry = $entry_class->load( { id => $id, blog_id => $blog_id } )
762            or return $app->errtrans( "Invalid request." );
763        $user_id = $entry->author_id;
764    }
765    else {
766        $entry = $entry_class->new;
767        $entry->author_id($user_id);
768        $entry->id(-1); # fake out things like MT::Taggable::__load_tags
769        $entry->blog_id($blog_id);
770    }
771    my $cat;
772    my $names = $entry->column_names;
773
774    my %values = map { $_ => scalar $app->param($_) } @$names;
775    delete $values{'id'} unless $q->param('id');
776    ## Strip linefeed characters.
777    for my $col (qw( text excerpt text_more keywords )) {
778        $values{$col} =~ tr/\r//d if $values{$col};
779    }
780    $values{allow_comments} = 0
781      if !defined( $values{allow_comments} )
782      || $q->param('allow_comments') eq '';
783    $values{allow_pings} = 0
784      if !defined( $values{allow_pings} )
785      || $q->param('allow_pings') eq '';
786    $entry->set_values( \%values );
787
788    my $cat_ids = $q->param('category_ids');
789    if ($cat_ids) {
790        my @cats = split /,/, $cat_ids;
791        if (@cats) {
792            my $primary_cat = $cats[0];
793            $cat =
794              MT::Category->load( { id => $primary_cat, blog_id => $blog_id } );
795            my @categories = MT::Category->load( { id => \@cats, blog_id => $blog_id });
796            $entry->cache_property('category', undef, $cat);
797            $entry->cache_property('categories', undef, \@categories);
798        }
799    } else {
800        $entry->cache_property('category', undef, undef);
801        $entry->cache_property('categories', undef, []);
802    }
803    my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
804    my @tag_names = MT::Tag->split( $tag_delim, $q->param('tags') );
805    if (@tag_names) {
806        my @tags;
807        foreach my $tag_name (@tag_names) {
808            my $tag = MT::Tag->new;
809            $tag->name($tag_name);
810            push @tags, $tag;
811        }
812        $entry->{__tags} = \@tag_names;
813        $entry->{__tag_objects} = \@tags;
814    }
815
816    my $date = $q->param('authored_on_date');
817    my $time = $q->param('authored_on_time');
818    my $ts   = $date . $time;
819    $ts =~ s/\D//g;
820    $entry->authored_on($ts);
821
822    my $preview_basename = $app->preview_object_basename;
823    $entry->basename($preview_basename);
824
825    require MT::TemplateMap;
826    require MT::Template;
827    my $tmpl_map = MT::TemplateMap->load(
828        {
829            archive_type => ( $type eq 'page' ? 'Page' : 'Individual' ),
830            is_preferred => 1,
831            blog_id => $blog_id,
832        }
833    );
834
835    my $tmpl;
836    my $fullscreen;
837    my $archive_file;
838    my $orig_file;
839    my $file_ext;
840    if ($tmpl_map) {
841        $tmpl         = MT::Template->load( $tmpl_map->template_id );
842        $file_ext = $blog->file_extension || '';
843        $archive_file = $entry->archive_file;
844
845        my $blog_path = $type eq 'page' ?
846            $blog->site_path :
847            ($blog->archive_path || $blog->site_path);
848        $archive_file = File::Spec->catfile( $blog_path, $archive_file );
849        require File::Basename;
850        my $path;
851        ( $orig_file, $path ) = File::Basename::fileparse( $archive_file );
852        $file_ext = '.' . $file_ext if $file_ext ne '';
853        $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext );
854    }
855    else {
856        $tmpl       = $app->load_tmpl('preview_entry_content.tmpl');
857        $fullscreen = 1;
858    }
859
860    # translates naughty words when PublishCharset is NOT UTF-8
861    $app->_translate_naughty_words($entry);
862
863    $entry->convert_breaks( scalar $q->param('convert_breaks') );
864       
865    my @data = ( { data_name => 'author_id', data_value => $user_id } );
866    $app->run_callbacks( 'cms_pre_preview', $app, $entry, \@data );
867
868    my $ctx = $tmpl->context;
869    $ctx->stash( 'entry', $entry );
870    $ctx->stash( 'blog',  $blog );
871    $ctx->stash( 'category', $cat ) if $cat;
872    $ctx->{current_timestamp} = $ts;
873    $ctx->var('entry_template',    1);
874    $ctx->var('main_template',     1);
875    $ctx->var('archive_template',  1);
876    $ctx->var('entry_template',    1);
877    $ctx->var('feedback_template', 1);
878    $ctx->var('archive_class',     'entry-archive');
879    $ctx->var('preview_template',  1);
880    my $html = $tmpl->output;
881    my %param;
882    unless ( defined($html) ) {
883        my $preview_error = $app->translate( "Publish error: [_1]",
884            MT::Util::encode_html( $tmpl->errstr ) );
885        $param{preview_error} = $preview_error;
886        my $tmpl_plain = $app->load_tmpl('preview_entry_content.tmpl');
887        $tmpl->text( $tmpl_plain->text );
888        $html = $tmpl->output;
889        defined($html)
890          or return $app->error(
891            $app->translate( "Publish error: [_1]", $tmpl->errstr ) );
892        $fullscreen = 1;
893    }
894
895    # If MT is configured to do 'local' previews, convert all
896    # the normal blog URLs into the domain used by MT itself (ie,
897    # blog is published to www.example.com, which is a different
898    # server from where MT runs, mt.example.com; previews therefore
899    # should occur locally, so replace all http://www.example.com/
900    # with http://mt.example.com/).
901    my ($old_url, $new_url);
902    if ($app->config('LocalPreviews')) {
903        $old_url = $blog->site_url;
904        $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!;
905        $new_url = $app->base . '/';
906        $html =~ s!\Q$old_url\E!$new_url!g;
907    }
908
909    if ( !$fullscreen ) {
910        my $fmgr = $blog->file_mgr;
911
912        ## Determine if we need to build directory structure,
913        ## and build it if we do. DirUmask determines
914        ## directory permissions.
915        require File::Basename;
916        my $path = File::Basename::dirname($archive_file);
917        $path =~ s!/$!!
918          unless $path eq '/';    ## OS X doesn't like / at the end in mkdir().
919        unless ( $fmgr->exists($path) ) {
920            $fmgr->mkpath($path);
921        }
922
923        if ( $fmgr->exists($path) && $fmgr->can_write($path) ) {
924            $fmgr->put_data( $html, $archive_file );
925            $param{preview_file} = $preview_basename;
926            my $preview_url = $entry->archive_url;
927            $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x;
928
929            # We also have to translate the URL used for the
930            # published file to be on the MT app domain.
931            if (defined $new_url) {
932                $preview_url =~ s!^\Q$old_url\E!$new_url!;
933            }
934
935            $param{preview_url}  = $preview_url;
936
937            # we have to make a record of this preview just in case it
938            # isn't cleaned up by re-editing, saving or cancelling on
939            # by the user.
940            require MT::Session;
941            my $sess_obj = MT::Session->get_by_key(
942                {
943                    id   => $preview_basename,
944                    kind => 'TF',                # TF = Temporary File
945                    name => $archive_file,
946                }
947            );
948            $sess_obj->start(time);
949            $sess_obj->save;
950        }
951        else {
952            $fullscreen = 1;
953            $param{preview_error} = $app->translate(
954                "Unable to create preview file in this location: [_1]", $path );
955            my $tmpl_plain = $app->load_tmpl('preview_entry_content.tmpl');
956            $tmpl->text( $tmpl_plain->text );
957            $tmpl->reset_tokens;
958            $html = $tmpl->output;
959            $param{preview_body} = $html;
960        }
961    }
962    else {
963        $param{preview_body} = $html;
964    }
965    $param{id} = $id if $id;
966    $param{new_object} = $param{id} ? 0 : 1;
967    $param{title} = $entry->title;
968    my $cols = $entry_class->column_names;
969
970    for my $col (@$cols) {
971        next
972          if $col eq 'created_on'
973          || $col eq 'created_by'
974          || $col eq 'modified_on'
975          || $col eq 'modified_by'
976          || $col eq 'authored_on'
977          || $col eq 'author_id'
978          || $col eq 'pinged_urls'
979          || $col eq 'tangent_cache'
980          || $col eq 'template_id'
981          || $col eq 'class'
982          || $col eq 'meta';
983        if ( $col eq 'basename' ) {
984            if (   ( !defined $q->param('basename') )
985                || ( $q->param('basename') eq '' ) )
986            {
987                $q->param( 'basename', $q->param('basename_old') );
988            }
989        }
990        push @data,
991          {
992            data_name  => $col,
993            data_value => scalar $q->param($col)
994          };
995    }
996    for my $data (
997        qw( authored_on_date authored_on_time basename_manual basename_old category_ids tags )
998      )
999    {
1000        push @data,
1001          {
1002            data_name  => $data,
1003            data_value => scalar $q->param($data)
1004          };
1005    }
1006
1007    $param{entry_loop} = \@data;
1008    my $list_mode;
1009    my $list_title;
1010    if ( $type eq 'page' ) {
1011        $list_title = 'Pages';
1012        $list_mode  = 'list_pages';
1013    }
1014    else {
1015        $list_title = 'Entries';
1016        $list_mode  = 'list_entries';
1017    }
1018    if ($id) {
1019        $app->add_breadcrumb(
1020            $app->translate($list_title),
1021            $app->uri(
1022                'mode' => $list_mode,
1023                args   => { blog_id => $blog_id }
1024            )
1025        );
1026        $app->add_breadcrumb( $entry->title || $app->translate('(untitled)') );
1027    }
1028    else {
1029        $app->add_breadcrumb( $app->translate($list_title),
1030            $app->uri( 'mode' => $list_mode, args => { blog_id => $blog_id } )
1031        );
1032        $app->add_breadcrumb(
1033            $app->translate( 'New [_1]', $entry_class->class_label ) );
1034        $param{nav_new_entry} = 1;
1035    }
1036    $param{object_type}  = $type;
1037    $param{object_label} = $entry_class->class_label;
1038    if ($fullscreen) {
1039        return $app->load_tmpl( 'preview_entry.tmpl', \%param );
1040    }
1041    else {
1042        return $app->load_tmpl( 'preview_strip.tmpl', \%param );
1043    }
1044}
1045
1046sub cfg_entry {
1047    my $app     = shift;
1048    my $q       = $app->param;
1049    my $blog_id = scalar $q->param('blog_id');
1050    return $app->return_to_dashboard( redirect => 1 )
1051      unless $blog_id;
1052    $q->param( '_type', 'blog' );
1053    $q->param( 'id',    scalar $q->param('blog_id') );
1054    $app->forward("view",
1055        {
1056            output       => 'cfg_entry.tmpl',
1057            screen_class => 'settings-screen entry-screen'
1058        }
1059    );
1060}
1061
1062sub save {
1063    my $app = shift;
1064    $app->validate_magic or return;
1065
1066    $app->remove_preview_file;
1067
1068    if ( $app->param('is_power_edit') ) {
1069        return $app->save_entries(@_);
1070    }
1071    my $author = $app->user;
1072    my $type = $app->param('_type') || 'entry';
1073
1074    my $class = $app->model($type)
1075      or retrun $app->errtrans("Invalid parameter");
1076
1077    my $cat_class = $app->model( $class->container_type );
1078
1079    my $perms = $app->permissions
1080      or return $app->errtrans("Permission denied.");
1081
1082    if ( $type eq 'page' ) {
1083        return $app->errtrans("Permission denied.")
1084          unless $perms->can_manage_pages;
1085    }
1086
1087    my $id = $app->param('id');
1088    if ( !$id ) {
1089        return $app->errtrans("Permission denied.")
1090          unless ( ( 'entry' eq $type ) && $perms->can_create_post )
1091          || ( ( 'page' eq $type ) && $perms->can_manage_pages );
1092    }
1093
1094    $app->validate_magic() or return;
1095
1096    # check for autosave
1097    if ( $app->param('_autosave') ) {
1098        return $app->autosave_object();
1099    }
1100
1101    require MT::Blog;
1102    my $blog_id = $app->param('blog_id');
1103    my $blog    = MT::Blog->load($blog_id);
1104
1105    my $archive_type;
1106
1107    my ( $obj, $orig_obj, $orig_file );
1108    if ($id) {
1109        $obj = $class->load($id)
1110          || return $app->error(
1111            $app->translate( "No such [_1].", $class->class_label ) );
1112        return $app->error( $app->translate("Invalid parameter") )
1113          unless $obj->blog_id == $blog_id;
1114        if ( $type eq 'entry' ) {
1115            return $app->error( $app->translate("Permission denied.") )
1116              unless $perms->can_edit_entry( $obj, $author );
1117            return $app->error( $app->translate("Permission denied.") )
1118              if ( $obj->status ne $app->param('status') )
1119              && !( $perms->can_edit_entry( $obj, $author, 1 ) );
1120            $archive_type = 'Individual';
1121        }
1122        elsif ( $type eq 'page' ) {
1123            $archive_type = 'Page';
1124        }
1125        $orig_obj = $obj->clone;
1126        $orig_file = archive_file_for( $orig_obj, $blog, $archive_type );
1127    }
1128    else {
1129        $obj = $class->new;
1130    }
1131    my $status_old = $id ? $obj->status : 0;
1132    my $names = $obj->column_names;
1133
1134    ## Get rid of category_id param, because we don't want to just set it
1135    ## in the Entry record; save it for later when we will set the Placement.
1136    my ( $cat_id, @add_cat ) = split /\s*,\s*/,
1137      ( $app->param('category_ids') || '' );
1138    $app->delete_param('category_id');
1139    if ($id) {
1140        ## Delete the author_id param (if present), because we don't want to
1141        ## change the existing author.
1142        $app->delete_param('author_id');
1143    }
1144
1145    my %values = map { $_ => scalar $app->param($_) } @$names;
1146    delete $values{'id'} unless $app->param('id');
1147    ## Strip linefeed characters.
1148    for my $col (qw( text excerpt text_more keywords )) {
1149        $values{$col} =~ tr/\r//d if $values{$col};
1150    }
1151    $values{allow_comments} = 0
1152      if !defined( $values{allow_comments} )
1153      || $app->param('allow_comments') eq '';
1154    delete $values{week_number}
1155      if ( $app->param('week_number') || '' ) eq '';
1156    delete $values{basename}
1157      unless $perms->can_publish_post || $perms->can_edit_all_posts;
1158    $obj->set_values( \%values );
1159    $obj->allow_pings(0)
1160      if !defined $app->param('allow_pings')
1161      || $app->param('allow_pings') eq '';
1162    my $ao_d = $app->param('authored_on_date');
1163    my $ao_t = $app->param('authored_on_time');
1164
1165    if ( !$id ) {
1166
1167        #  basename check for this new entry...
1168        if (   ( my $basename = $app->param('basename') )
1169            && !$app->param('basename_manual')
1170            && $type eq 'entry' )
1171        {
1172            my $cnt =
1173              $class->count( { blog_id => $blog_id, basename => $basename } );
1174            if ($cnt) {
1175                $obj->basename( MT::Util::make_unique_basename($obj) );
1176            }
1177        }
1178    }
1179
1180    if ( $type eq 'page' ) {
1181
1182        # -1 is a special id for identifying the 'root' folder
1183        $cat_id = 0 if $cat_id == -1;
1184        my $dup_it = $class->load_iter(
1185            {
1186                blog_id  => $blog_id,
1187                basename => $obj->basename,
1188                class    => 'page',
1189                ( $id ? ( id => $id ) : () )
1190            },
1191            { ( $id ? ( not => { id => 1 } ) : () ) }
1192        );
1193        while ( my $p = $dup_it->() ) {
1194            my $p_folder = $p->folder;
1195            my $dup_folder_path =
1196              defined $p_folder ? $p_folder->publish_path() : '';
1197            my $folder = MT::Folder->load($cat_id) if $cat_id;
1198            my $folder_path = defined $folder ? $folder->publish_path() : '';
1199            return $app->error(
1200                $app->translate(
1201"Same Basename has already been used. You should use an unique basename."
1202                )
1203            ) if ( $dup_folder_path eq $folder_path );
1204        }
1205
1206    }
1207
1208    if ( $type eq 'entry' ) {
1209        $obj->status( MT::Entry::HOLD() )
1210          if !$id
1211          && !$perms->can_publish_post
1212          && !$perms->can_edit_all_posts;
1213    }
1214
1215    my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app );
1216
1217    if ( !$filter_result ) {
1218        my %param = ();
1219        $param{error}       = $app->errstr;
1220        $param{return_args} = $app->param('return_args');
1221        return $app->forward( "view", \%param );
1222    }
1223
1224    # check to make sure blog has site url and path defined.
1225    # otherwise, we can't publish a released entry
1226    if ( ( $obj->status || 0 ) != MT::Entry::HOLD() ) {
1227        if ( !$blog->site_path || !$blog->site_url ) {
1228            return $app->error(
1229                $app->translate(
1230"Your blog has not been configured with a site path and URL. You cannot publish entries until these are defined."
1231                )
1232            );
1233        }
1234    }
1235
1236    my ( $previous_old, $next_old );
1237    if (   ( $perms->can_publish_post || $perms->can_edit_all_posts )
1238        && ($ao_d) )
1239    {
1240        my $ao = $ao_d . ' ' . $ao_t;
1241        unless (
1242            $ao =~ m!^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?$! )
1243        {
1244            return $app->error(
1245                $app->translate(
1246"Invalid date '[_1]'; authored on dates must be in the format YYYY-MM-DD HH:MM:SS.",
1247$ao
1248                )
1249            );
1250        }
1251        my $s = $6 || 0;
1252        return $app->error(
1253            $app->translate(
1254                "Invalid date '[_1]'; authored on dates should be real dates.",
1255                $ao
1256            )
1257          )
1258          if (
1259               $s > 59
1260            || $s < 0
1261            || $5 > 59
1262            || $5 < 0
1263            || $4 > 23
1264            || $4 < 0
1265            || $2 > 12
1266            || $2 < 1
1267            || $3 < 1
1268            || ( MT::Util::days_in( $2, $1 ) < $3
1269                && !MT::Util::leap_day( $0, $1, $2 ) )
1270          );
1271        if ($obj->authored_on) {
1272            $previous_old = $obj->previous(1);
1273            $next_old     = $obj->next(1);
1274        }
1275        my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $1, $2, $3, $4, $5, $s;
1276        $obj->authored_on($ts);
1277    }
1278    my $is_new = $obj->id ? 0 : 1;
1279
1280    $app->_translate_naughty_words($obj);
1281
1282    $obj->modified_by( $author->id ) unless $is_new;
1283
1284    $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $orig_obj )
1285      || return $app->error(
1286        $app->translate(
1287            "Saving [_1] failed: [_2]",
1288            $class->class_label, $app->errstr
1289        )
1290      );
1291
1292    $obj->save
1293      or return $app->error(
1294        $app->translate(
1295            "Saving [_1] failed: [_2]",
1296            $class->class_label, $obj->errstr
1297        )
1298      );
1299
1300    my $message;
1301    if ($is_new) {
1302        $message =
1303          $app->translate( "[_1] '[_2]' (ID:[_3]) added by user '[_4]'",
1304            $class->class_label, $obj->title, $obj->id, $author->name );
1305    }
1306    elsif ( $orig_obj->status ne $obj->status ) {
1307        $message = $app->translate(
1308"[_1] '[_2]' (ID:[_3]) edited and its status changed from [_4] to [_5] by user '[_6]'",
1309            $class->class_label,
1310            $obj->title,
1311            $obj->id,
1312            $app->translate( MT::Entry::status_text( $orig_obj->status ) ),
1313            $app->translate( MT::Entry::status_text( $obj->status ) ),
1314            $author->name
1315        );
1316
1317    }
1318    else {
1319        $message =
1320          $app->translate( "[_1] '[_2]' (ID:[_3]) edited by user '[_4]'",
1321            $class->class_label, $obj->title, $obj->id, $author->name );
1322    }
1323    require MT::Log;
1324    $app->log(
1325        {
1326            message => $message,
1327            level   => MT::Log::INFO(),
1328            class   => $type,
1329            $is_new ? ( category => 'new' ) : ( category => 'edit' ),
1330            metadata => $obj->id
1331        }
1332    );
1333
1334    my $error_string = MT::callback_errstr();
1335
1336    ## Now that the object is saved, we can be certain that it has an
1337    ## ID. So we can now add/update/remove the primary placement.
1338    require MT::Placement;
1339    my $place =
1340      MT::Placement->load( { entry_id => $obj->id, is_primary => 1 } );
1341    if ($cat_id) {
1342        unless ($place) {
1343            $place = MT::Placement->new;
1344            $place->entry_id( $obj->id );
1345            $place->blog_id( $obj->blog_id );
1346            $place->is_primary(1);
1347        }
1348        $place->category_id($cat_id);
1349        $place->save;
1350    }
1351    else {
1352        if ( $place ) {
1353            $place->remove;
1354        }
1355    }
1356
1357    my $placements_updated;
1358
1359    # save secondary placements...
1360    my @place = MT::Placement->load(
1361        {
1362            entry_id   => $obj->id,
1363            is_primary => 0
1364        }
1365    );
1366    for my $place (@place) {
1367        $place->remove;
1368        $placements_updated = 1;
1369    }
1370    for my $cat_id (@add_cat) {
1371        my $cat = $cat_class->load($cat_id);
1372
1373        # blog_id sanity check
1374        next if $cat->blog_id != $obj->blog_id;
1375
1376        my $place = MT::Placement->new;
1377        $place->entry_id( $obj->id );
1378        $place->blog_id( $obj->blog_id );
1379        $place->is_primary(0);
1380        $place->category_id($cat_id);
1381        $place->save
1382          or return $app->error(
1383            $app->translate( "Saving placement failed: [_1]", $place->errstr )
1384          );
1385        $placements_updated = 1;
1386    }
1387
1388    $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $orig_obj );
1389
1390    ## If the saved status is RELEASE, or if the *previous* status was
1391    ## RELEASE, then rebuild entry archives, indexes, and send the
1392    ## XML-RPC ping(s). Otherwise the status was and is HOLD, and we
1393    ## don't have to do anything.
1394    if ( ( $obj->status || 0 ) == MT::Entry::RELEASE()
1395        || $status_old eq MT::Entry::RELEASE() )
1396    {
1397        if ( $app->config('DeleteFilesAtRebuild') && $orig_obj ) {
1398            my $file = archive_file_for( $obj, $blog, $archive_type );
1399            if ( $file ne $orig_file || $obj->status != MT::Entry::RELEASE() ) {
1400                $app->publisher->remove_entry_archive_file(
1401                    Entry       => $orig_obj,
1402                    ArchiveType => $archive_type
1403                );
1404            }
1405        }
1406
1407        # If there are no static pages, just rebuild indexes.
1408        if ( $blog->count_static_templates($archive_type) == 0
1409            || MT::Util->launch_background_tasks() )
1410        {
1411            my $res = MT::Util::start_background_task(
1412                sub {
1413                    $app->rebuild_entry(
1414                        Entry             => $obj,
1415                        BuildDependencies => 1,
1416                        OldEntry          => $orig_obj,
1417                        OldPrevious       => ($previous_old)
1418                        ? $previous_old->id
1419                        : undef,
1420                        OldNext => ($next_old) ? $next_old->id : undef
1421                    ) or return $app->publish_error();
1422                    $app->run_callbacks( 'rebuild', $blog );
1423                    1;
1424                }
1425            );
1426            return unless $res;
1427            return ping_continuation($app,
1428                $obj, $blog,
1429                OldStatus => $status_old,
1430                IsNew     => $is_new,
1431            );
1432        }
1433        else {
1434            return $app->redirect(
1435                $app->uri(
1436                    'mode' => 'start_rebuild',
1437                    args   => {
1438                        blog_id    => $obj->blog_id,
1439                        'next'     => 0,
1440                        type       => 'entry-' . $obj->id,
1441                        entry_id   => $obj->id,
1442                        is_new     => $is_new,
1443                        old_status => $status_old,
1444                        (
1445                            $previous_old
1446                            ? ( old_previous => $previous_old->id )
1447                            : ()
1448                        ),
1449                        ( $next_old ? ( old_next => $next_old->id ) : () )
1450                    }
1451                )
1452            );
1453        }
1454    }
1455    _finish_rebuild_ping( $app, $obj, !$id );
1456}
1457
1458sub save_entries {
1459    my $app   = shift;
1460    my $perms = $app->permissions;
1461    my $type  = $app->param('_type');
1462    return $app->errtrans("Permission denied.")
1463      unless $perms
1464      && (
1465        $type eq 'page'
1466        ? ( $perms->can_manage_pages )
1467        : (      $perms->can_publish_post
1468              || $perms->can_create_post
1469              || $perms->can_edit_all_posts )
1470      );
1471
1472    $app->validate_magic() or return;
1473
1474    my $q = $app->param;
1475    my @p = $q->param;
1476    require MT::Entry;
1477    require MT::Placement;
1478    require MT::Log;
1479    my $blog_id        = $q->param('blog_id');
1480    my $this_author    = $app->user;
1481    my $this_author_id = $this_author->id;
1482    for my $p (@p) {
1483        next unless $p =~ /^category_id_(\d+)/;
1484        my $id    = $1;
1485        my $entry = MT::Entry->load($id);
1486        return $app->error( $app->translate("Permission denied.") )
1487          unless ( $perms->can_publish_post
1488            || $perms->can_create_post
1489            || $perms->can_edit_all_posts );
1490        my $orig_obj = $entry->clone;
1491        if ( $perms->can_edit_entry( $entry, $this_author ) ) {
1492            my $author_id = $q->param( 'author_id_' . $id );
1493            $entry->author_id( $author_id ? $author_id : 0 );
1494            $entry->title( scalar $q->param( 'title_' . $id ) );
1495        }
1496        if ( $perms->can_edit_entry( $entry, $this_author, 1 ) )
1497        {    ## can he/she change status?
1498            $entry->status( scalar $q->param( 'status_' . $id ) );
1499            my $co = $q->param( 'created_on_' . $id );
1500            unless ( $co =~
1501                m!(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?! )
1502            {
1503                return $app->error(
1504                    $app->translate(
1505"Invalid date '[_1]'; authored on dates must be in the format YYYY-MM-DD HH:MM:SS.",
1506                        $co
1507                    )
1508                );
1509            }
1510            my $s = $6 || 0;
1511
1512            # Emit an error message if the date is bogus.
1513            return $app->error(
1514                $app->translate(
1515"Invalid date '[_1]'; authored on dates should be real dates.",
1516                    $co
1517                )
1518              )
1519              if $s > 59
1520              || $s < 0
1521              || $5 > 59
1522              || $5 < 0
1523              || $4 > 23
1524              || $4 < 0
1525              || $2 > 12
1526              || $2 < 1
1527              || $3 < 1
1528              || ( MT::Util::days_in( $2, $1 ) < $3
1529                && !MT::Util::leap_day( $0, $1, $2 ) );
1530
1531            # FIXME: Should be assigning the publish_date field here
1532            my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $1, $2, $3, $4, $5, $s;
1533            if ($type eq 'page' ) {
1534                $entry->modified_on($ts);
1535            } else {
1536                $entry->authored_on($ts);
1537            }
1538        }
1539        $app->run_callbacks( 'cms_pre_save.' . $type, $app, $entry, $orig_obj )
1540          || return $app->error(
1541            $app->translate(
1542                "Saving [_1] failed: [_2]",
1543                $entry->class_label, $app->errstr
1544            )
1545          );
1546        $entry->save
1547          or return $app->error(
1548            $app->translate(
1549                "Saving entry '[_1]' failed: [_2]", $entry->title,
1550                $entry->errstr
1551            )
1552          );
1553        my $cat_id = $q->param("category_id_$id");
1554        my $place  = MT::Placement->load(
1555            {
1556                entry_id   => $id,
1557                is_primary => 1
1558            }
1559        );
1560        if ( $place && !$cat_id ) {
1561            $place->remove
1562              or return $app->error(
1563                $app->translate(
1564                    "Removing placement failed: [_1]",
1565                    $place->errstr
1566                )
1567              );
1568        }
1569        elsif ($cat_id) {
1570            unless ($place) {
1571                $place = MT::Placement->new;
1572                $place->entry_id($id);
1573                $place->blog_id($blog_id);
1574                $place->is_primary(1);
1575            }
1576            $place->category_id( scalar $q->param($p) );
1577            $place->save
1578              or return $app->error(
1579                $app->translate(
1580                    "Saving placement failed: [_1]",
1581                    $place->errstr
1582                )
1583              );
1584        }
1585        my $message;
1586        if ( $orig_obj->status ne $entry->status ) {
1587            $message = $app->translate(
1588"[_1] '[_2]' (ID:[_3]) edited and its status changed from [_4] to [_5] by user '[_6]'",
1589                $entry->class_label,
1590                $entry->title,
1591                $entry->id,
1592                $app->translate( MT::Entry::status_text( $orig_obj->status ) ),
1593                $app->translate( MT::Entry::status_text( $entry->status ) ),
1594                $this_author->name
1595            );
1596        }
1597        else {
1598            $message =
1599              $app->translate( "[_1] '[_2]' (ID:[_3]) edited by user '[_4]'",
1600                $entry->class_label, $entry->title, $entry->id,
1601                $this_author->name );
1602        }
1603        $app->log(
1604            {
1605                message  => $message,
1606                level    => MT::Log::INFO(),
1607                class    => $entry->class,
1608                category => 'edit',
1609                metadata => $entry->id
1610            }
1611        );
1612        $app->run_callbacks( 'cms_post_save.' . $type, $app, $entry, $orig_obj );
1613    }
1614    $app->add_return_arg( 'saved' => 1, is_power_edit => 1 );
1615    $app->call_return;
1616}
1617
1618sub send_pings {
1619    my $app = shift;
1620    my $q   = $app->param;
1621    $app->validate_magic() or return;
1622    require MT::Entry;
1623    require MT::Blog;
1624    my $blog  = MT::Blog->load( scalar $q->param('blog_id') );
1625    my $entry = MT::Entry->load( scalar $q->param('entry_id') );
1626    ## MT::ping_and_save pings each of the necessary URLs, then processes
1627    ## the return value from MT::ping to update the list of URLs pinged
1628    ## and not successfully pinged. It returns the return value from
1629    ## MT::ping for further processing. If a fatal error occurs, it returns
1630    ## undef.
1631    my $results = $app->ping_and_save(
1632        Blog      => $blog,
1633        Entry     => $entry,
1634        OldStatus => scalar $q->param('old_status')
1635    ) or return;
1636    my $has_errors = 0;
1637    require MT::Log;
1638    for my $res (@$results) {
1639        $has_errors++,
1640          $app->log(
1641            {
1642                message => $app->translate(
1643                    "Ping '[_1]' failed: [_2]",
1644                    $res->{url},
1645                    encode_text( $res->{error}, undef, undef )
1646                ),
1647                class => 'system',
1648                level => MT::Log::WARNING()
1649            }
1650          ) unless $res->{good};
1651    }
1652    $app->_finish_rebuild_ping( $entry, scalar $q->param('is_new'),
1653        $has_errors );
1654}
1655
1656sub entry_notify {
1657    my $app   = shift;
1658    my $user  = $app->user;
1659    my $perms = $app->permissions;
1660    return $app->error( $app->translate("No permissions.") )
1661      unless $perms->can_send_notifications;
1662
1663    my $q        = $app->param;
1664    my $entry_id = $q->param('entry_id')
1665      or return $app->error( $app->translate("No entry ID provided") );
1666    require MT::Entry;
1667    require MT::Blog;
1668    my $entry = MT::Entry->load($entry_id)
1669      or return $app->error(
1670        $app->translate( "No such entry '[_1]'", $entry_id ) );
1671    my $blog  = MT::Blog->load( $entry->blog_id );
1672    my $param = {};
1673    $param->{entry_id} = $entry_id;
1674    return $app->load_tmpl( "dialog/entry_notify.tmpl", $param );
1675}
1676
1677sub send_notify {
1678    my $app = shift;
1679    $app->validate_magic() or return;
1680    my $q        = $app->param;
1681    my $entry_id = $q->param('entry_id')
1682      or return $app->error( $app->translate("No entry ID provided") );
1683    require MT::Entry;
1684    require MT::Blog;
1685    my $entry = MT::Entry->load($entry_id)
1686      or return $app->error(
1687        $app->translate( "No such entry '[_1]'", $entry_id ) );
1688    my $blog = MT::Blog->load( $entry->blog_id );
1689
1690    my $user = $app->user;
1691    $app->blog($blog);
1692    my $perms = $user->permissions($blog);
1693    return $app->error( $app->translate("No permissions.") )
1694      unless $perms->can_send_notifications;
1695
1696    my $author = $entry->author;
1697    return $app->error(
1698        $app->translate( "No email address for user '[_1]'", $author->name ) )
1699      unless $author->email;
1700
1701    my $cols = 72;
1702    my %params;
1703    $params{blog} = $blog;
1704    $params{entry} = $entry;
1705    $params{author} = $author;
1706
1707    if ( $q->param('send_excerpt') ) {
1708        $params{send_excerpt} = 1;
1709    }
1710    $params{message} = wrap_text( $q->param('message'), $cols, '', '' );
1711    if ( $q->param('send_body') ) {
1712        $params{send_body} = 1;
1713    }
1714
1715    my $entry_editurl = $app->uri(
1716        'mode' => 'view',
1717        args   => {
1718            '_type' => 'entry',
1719            blog_id => $entry->blog_id,
1720            id      => $entry->id,
1721        }
1722    );
1723    if ( $entry_editurl =~ m|^/| ) {
1724        my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|;
1725        $entry_editurl = $blog_domain . $entry_editurl;
1726    }
1727    $params{entry_editurl} = $entry_editurl;
1728
1729    my $addrs;
1730    if ( $q->param('send_notify_list') ) {
1731        require MT::Notification;
1732        my $iter = MT::Notification->load_iter( { blog_id => $blog->id } );
1733        while ( my $note = $iter->() ) {
1734            next unless is_valid_email( $note->email );
1735            $addrs->{ $note->email } = 1;
1736        }
1737    }
1738
1739    if ( $q->param('send_notify_emails') ) {
1740        my @addr = split /[\n\r,]+/, $q->param('send_notify_emails');
1741        for my $a (@addr) {
1742            next unless is_valid_email($a);
1743            $addrs->{$a} = 1;
1744        }
1745    }
1746
1747    keys %$addrs
1748      or return $app->error(
1749        $app->translate(
1750            "No valid recipients found for the entry notification.")
1751      );
1752
1753    my $body = $app->build_email( 'notify-entry.tmpl', \%params );
1754
1755    my $subj =
1756      $app->translate( "[_1] Update: [_2]", $blog->name, $entry->title );
1757    if ( $app->current_language ne 'ja' ) {    # FIXME perhaps move to MT::I18N
1758        $subj =~ s![\x80-\xFF]!!g;
1759    }
1760    my $address =
1761      defined $author->nickname
1762      ? $author->nickname . ' <' . $author->email . '>'
1763      : $author->email;
1764    my %head = (
1765        id      => 'notify_entry',
1766        To      => $address,
1767        From    => $address,
1768        Subject => $subj,
1769    );
1770    my $charset = $app->config('MailEncoding')
1771      || $app->charset;
1772    $head{'Content-Type'} = qq(text/plain; charset="$charset");
1773    my $i = 1;
1774    require MT::Mail;
1775    MT::Mail->send( \%head, $body )
1776      or return $app->error(
1777        $app->translate(
1778            "Error sending mail ([_1]); try another MailTransfer setting?",
1779            MT::Mail->errstr
1780        )
1781      );
1782    delete $head{To};
1783
1784    foreach my $email ( keys %{$addrs} ) {
1785        next unless $email;
1786        if ( $app->config('EmailNotificationBcc') ) {
1787            push @{ $head{Bcc} }, $email;
1788            if ( $i++ % 20 == 0 ) {
1789                MT::Mail->send( \%head, $body )
1790                  or return $app->error(
1791                    $app->translate(
1792"Error sending mail ([_1]); try another MailTransfer setting?",
1793                        MT::Mail->errstr
1794                    )
1795                  );
1796                @{ $head{Bcc} } = ();
1797            }
1798        }
1799        else {
1800            $head{To} = $email;
1801            MT::Mail->send( \%head, $body )
1802              or return $app->error(
1803                $app->translate(
1804"Error sending mail ([_1]); try another MailTransfer setting?",
1805                    MT::Mail->errstr
1806                )
1807              );
1808            delete $head{To};
1809        }
1810    }
1811    if ( $head{Bcc} && @{ $head{Bcc} } ) {
1812        MT::Mail->send( \%head, $body )
1813          or return $app->error(
1814            $app->translate(
1815                "Error sending mail ([_1]); try another MailTransfer setting?",
1816                MT::Mail->errstr
1817            )
1818          );
1819    }
1820    $app->redirect(
1821        $app->uri(
1822            'mode' => 'view',
1823            args   => {
1824                '_type'      => $entry->class,
1825                blog_id      => $entry->blog_id,
1826                id           => $entry->id,
1827                saved_notify => 1
1828            }
1829        )
1830    );
1831}
1832
1833sub pinged_urls {
1834    my $app   = shift;
1835    my $perms = $app->permissions
1836      or return $app->error( $app->translate("No permissions") );
1837    my %param;
1838    my $entry_id = $app->param('entry_id');
1839    require MT::Entry;
1840    my $entry = MT::Entry->load($entry_id);
1841    $param{url_loop} = [ map { { url => $_ } } @{ $entry->pinged_url_list } ];
1842    $param{failed_url_loop} =
1843      [ map { { url => $_ } }
1844          @{ $entry->pinged_url_list( OnlyFailures => 1 ) } ];
1845    $app->load_tmpl( 'popup/pinged_urls.tmpl', \%param );
1846}
1847
1848sub save_entry_prefs {
1849    my $app   = shift;
1850    my $perms = $app->permissions
1851      or return $app->error( $app->translate("No permissions") );
1852    $app->validate_magic() or return;
1853    my $q     = $app->param;
1854    my $prefs = $app->_entry_prefs_from_params;
1855    $perms->entry_prefs($prefs);
1856    $perms->save
1857      or return $app->error(
1858        $app->translate( "Saving permissions failed: [_1]", $perms->errstr ) );
1859    $app->send_http_header("text/json");
1860    return "true";
1861}
1862
1863sub publish_entries {
1864    my $app = shift;
1865    require MT::Entry;
1866    update_entry_status( $app, MT::Entry::RELEASE(), $app->param('id') );
1867}
1868
1869sub draft_entries {
1870    my $app = shift;
1871    require MT::Entry;
1872    update_entry_status( $app, MT::Entry::HOLD(), $app->param('id') );
1873}
1874
1875sub open_batch_editor {
1876    my $app = shift;
1877    my @ids = $app->param('id');
1878
1879    $app->param( 'is_power_edit', 1 );
1880    $app->param( 'filter',        'power_edit' );
1881    $app->param( 'filter_val',    \@ids );
1882    $app->mode(
1883        'list_' . ( 'entry' eq $app->param('_type') ? 'entries' : 'pages' ) );
1884    $app->forward( "list_entry", { type => $app->param('_type') } );
1885}
1886
1887sub build_entry_table {
1888    my $app = shift;
1889    my (%args) = @_;
1890
1891    my $app_author = $app->user;
1892    my $perms      = $app->permissions;
1893    my $type       = $args{type};
1894    my $class      = $app->model($type);
1895
1896    my $list_pref = $app->list_pref($type);
1897    if ( $args{is_power_edit} ) {
1898        delete $list_pref->{view_expanded};
1899    }
1900    my $iter;
1901    if ( $args{load_args} ) {
1902        $iter = $class->load_iter( @{ $args{load_args} } );
1903    }
1904    elsif ( $args{iter} ) {
1905        $iter = $args{iter};
1906    }
1907    elsif ( $args{items} ) {
1908        $iter = sub { shift @{ $args{items} } };
1909    }
1910    return [] unless $iter;
1911    my $limit         = $args{limit};
1912    my $is_power_edit = $args{is_power_edit} || 0;
1913    my $param         = $args{param} || {};
1914
1915    ## Load list of categories for display in filter pulldown (and selection
1916    ## pulldown on power edit page).
1917    my ( $c_data, %cats );
1918    my $blog_id = $app->param('blog_id');
1919    if ($blog_id) {
1920        $c_data = $app->_build_category_list(
1921            blog_id => $blog_id,
1922            type    => $class->container_type,
1923        );
1924        my $i = 0;
1925        for my $row (@$c_data) {
1926            $row->{category_index} = $i++;
1927            my $spacer = $row->{category_label_spacer} || '';
1928            $spacer =~ s/\&nbsp;/\\u00A0/g;
1929            $row->{category_label_js} =
1930              $spacer . encode_js( $row->{category_label} );
1931            $cats{ $row->{category_id} } = $row;
1932        }
1933        $param->{category_loop} = $c_data;
1934    }
1935
1936    my ( $date_format, $datetime_format );
1937
1938    if ($is_power_edit) {
1939        $date_format          = "%Y.%m.%d";
1940        $datetime_format      = "%Y-%m-%d %H:%M:%S";
1941    }
1942    else {
1943        $date_format     = MT::App::CMS::LISTING_DATE_FORMAT();
1944        $datetime_format = MT::App::CMS::LISTING_DATETIME_FORMAT();
1945    }
1946
1947    my @cat_list;
1948    if ($is_power_edit) {
1949        @cat_list =
1950          sort { $cats{$a}->{category_index} <=> $cats{$b}->{category_index} }
1951          keys %cats;
1952    }
1953
1954    my @data;
1955    my %blogs;
1956    require MT::Blog;
1957    my $title_max_len   = const('DISPLAY_LENGTH_EDIT_ENTRY_TITLE');
1958    my $excerpt_max_len = const('DISPLAY_LENGTH_EDIT_ENTRY_TEXT_FROM_EXCERPT');
1959    my $text_max_len    = const('DISPLAY_LENGTH_EDIT_ENTRY_TEXT_BREAK_UP');
1960    my %blog_perms;
1961    $blog_perms{ $perms->blog_id } = $perms if $perms;
1962    while ( my $obj = $iter->() ) {
1963        my $blog_perms;
1964        if ( !$app_author->is_superuser() ) {
1965            $blog_perms = $blog_perms{ $obj->blog_id }
1966              || $app_author->blog_perm( $obj->blog_id );
1967        }
1968
1969        my $row = $obj->column_values;
1970        $row->{text} ||= '';
1971        if ( my $ts =
1972            ( $type eq 'page' ) ? $obj->modified_on : $obj->authored_on )
1973        {
1974            $row->{created_on_formatted} =
1975              format_ts( $date_format, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef );
1976            $row->{created_on_time_formatted} =
1977              format_ts( $datetime_format, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef );
1978            $row->{created_on_relative} =
1979              relative_date( $ts, time, $obj->blog );
1980        }
1981        my $author = $obj->author;
1982        $row->{author_name} =
1983          $author ? $author->name : $app->translate('(user deleted)');
1984        if ( my $cat = $obj->category ) {
1985            $row->{category_label}    = $cat->label;
1986            $row->{category_basename} = $cat->basename;
1987        }
1988        else {
1989            $row->{category_label}    = '';
1990            $row->{category_basename} = '';
1991        }
1992        $row->{file_extension} = $obj->blog ? $obj->blog->file_extension : '';
1993        $row->{title_short} = $obj->title;
1994        if ( !defined( $row->{title_short} ) || $row->{title_short} eq '' ) {
1995            my $title = remove_html( $obj->text );
1996            $row->{title_short} =
1997              substr_text( defined($title) ? $title : "", 0, $title_max_len )
1998              . '...';
1999        }
2000        else {
2001            $row->{title_short} = remove_html( $row->{title_short} );
2002            $row->{title_short} =
2003              substr_text( $row->{title_short}, 0, $title_max_len + 3 ) . '...'
2004              if length_text( $row->{title_short} ) > $title_max_len;
2005        }
2006        if ( $row->{excerpt} ) {
2007            $row->{excerpt} = remove_html( $row->{excerpt} );
2008        }
2009        if ( !$row->{excerpt} ) {
2010            my $text = remove_html( $row->{text} ) || '';
2011            $row->{excerpt} = first_n_text( $text, $excerpt_max_len );
2012            if ( length($text) > length( $row->{excerpt} ) ) {
2013                $row->{excerpt} .= ' ...';
2014            }
2015        }
2016        $row->{text} = break_up_text( $row->{text}, $text_max_len )
2017          if $row->{text};
2018        $row->{title_long} = remove_html( $obj->title );
2019        $row->{status_text} =
2020          $app->translate( MT::Entry::status_text( $obj->status ) );
2021        $row->{ "status_" . MT::Entry::status_text( $obj->status ) } = 1;
2022        $row->{has_edit_access} = $app_author->is_superuser
2023          || ( ( 'entry' eq $type )
2024            && $blog_perms
2025            && $blog_perms->can_edit_entry( $obj, $app_author ) )
2026          || ( ( 'page' eq $type )
2027            && $blog_perms
2028            && $blog_perms->can_manage_pages );
2029        if ($is_power_edit) {
2030            $row->{has_publish_access} = $app_author->is_superuser
2031              || ( ( 'entry' eq $type )
2032                && $blog_perms
2033                && $blog_perms->can_edit_entry( $obj, $app_author, 1 ) )
2034              || ( ( 'page' eq $type )
2035                && $blog_perms
2036                && $blog_perms->can_manage_pages );
2037            $row->{is_editable} = $row->{has_edit_access};
2038
2039            ## This is annoying. In order to generate and pre-select the
2040            ## category, user, and status pull down menus, we need to
2041            ## have a separate *copy* of the list of categories and
2042            ## users for every entry listed, so that each row in the list
2043            ## can "know" whether it is selected for this entry or not.
2044            my @this_c_data;
2045            my $this_category_id = $obj->category ? $obj->category->id : undef;
2046            for my $c_id (@cat_list) {
2047                push @this_c_data, { %{ $cats{$c_id} } };
2048                $this_c_data[-1]{category_is_selected} = $this_category_id
2049                  && $this_category_id == $c_id ? 1 : 0;
2050            }
2051            $row->{row_category_loop} = \@this_c_data;
2052
2053            if ( $obj->author ) {
2054                $row->{row_author_name} = $obj->author->name;
2055                $row->{row_author_id}   = $obj->author->id;
2056            } else {
2057                $row->{row_author_name} = $app->translate(
2058                    '(user deleted - ID:[_1])',
2059                    $obj->author_id
2060                );
2061                $row->{row_author_id} = $obj->author_id,
2062             }
2063        }
2064        if ( my $blog = $blogs{ $obj->blog_id } ||=
2065            MT::Blog->load( $obj->blog_id ) )
2066        {
2067            $row->{weblog_id}   = $blog->id;
2068            $row->{weblog_name} = $blog->name;
2069        }
2070        if ( $obj->status == MT::Entry::RELEASE() ) {
2071            $row->{entry_permalink} = $obj->permalink;
2072        }
2073        $row->{object} = $obj;
2074        push @data, $row;
2075    }
2076    return [] unless @data;
2077
2078    $param->{entry_table}[0] = {%$list_pref};
2079    $param->{object_loop} = $param->{entry_table}[0]{object_loop} = \@data;
2080    $app->load_list_actions( $type, \%$param )
2081      unless $is_power_edit;
2082    \@data;
2083}
2084
2085sub quickpost_js {
2086    my $app     = shift;
2087    my ($type)  = @_;
2088    my $blog_id = $app->blog->id;
2089    my $blog    = $app->model('blog')->load($blog_id);
2090    my %args    = ( '_type' => $type, blog_id => $blog_id, qp => 1 );
2091    my $uri = $app->base . $app->uri( 'mode' => 'view', args => \%args );
2092    my $script = qq!javascript:d=document;w=window;t='';if(d.selection)t=d.selection.createRange().text;else{if(d.getSelection)t=d.getSelection();else{if(w.getSelection)t=w.getSelection()}}void(w.open('$uri&title='+encodeURIComponent(d.title)+'&text='+encodeURIComponent(d.location.href)+encodeURIComponent('<br/><br/>')+encodeURIComponent(t),'_blank','scrollbars=yes,status=yes,resizable=yes,location=yes'))!;
2093    # Translate the phrase here to avoid ActivePerl DLL bug.
2094    $app->translate('<a href="[_1]">QuickPost to [_2]</a> - Drag this link to your browser\'s toolbar then click it when you are on a site you want to blog about.', encode_html($script), $blog->name);
2095}
2096
2097sub can_view {
2098    my ( $eh, $app, $id, $objp ) = @_;
2099    my $perms = $app->permissions;
2100    if (   !$id
2101        && !$perms->can_create_post )
2102    {
2103        return 0;
2104    }
2105    if ($id) {
2106        my $obj = $objp->force();
2107        if ( !$perms->can_edit_entry( $obj, $app->user ) ) {
2108            return 0;
2109        }
2110    }
2111    1;
2112}
2113
2114sub can_delete {
2115    my ( $eh, $app, $obj ) = @_;
2116    my $author = $app->user;
2117    return 1 if $author->is_superuser();
2118    my $perms = $app->permissions;
2119    if ( !$perms || $perms->blog_id != $obj->blog_id ) {
2120        $perms ||= $author->permissions( $obj->blog_id );
2121    }
2122    return $perms && $perms->can_edit_entry( $obj, $author );
2123}
2124
2125sub pre_save {
2126    my $eh = shift;
2127    my ( $app, $obj ) = @_;
2128
2129    # save tags
2130    my $tags = $app->param('tags');
2131    if ( defined $tags ) {
2132        my $blog = $app->blog;
2133        my $fields = $blog->smart_replace_fields;
2134        if ( $fields =~ m/tags/ig ) {
2135            $tags = MT::App::CMS::_convert_word_chars( $app, $tags );
2136        }
2137
2138        require MT::Tag;
2139        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
2140        my @tags = MT::Tag->split( $tag_delim, $tags );
2141        if (@tags) {
2142            $obj->set_tags(@tags);
2143        }
2144        else {
2145            $obj->remove_tags();
2146        }
2147    }
2148
2149    # update text heights if necessary
2150    if ( my $perms = $app->permissions ) {
2151        my $prefs = $perms->entry_prefs || $app->load_default_entry_prefs;
2152        my $text_height = $app->param('text_height');
2153        if ( defined $text_height ) {
2154            my ($pref_text_height) = $prefs =~ m/\bbody:(\d+)\b/;
2155            $pref_text_height ||= 0;
2156            if ( $text_height != $pref_text_height ) {
2157                if ( $prefs =~ m/\bbody\b/ ) {
2158                    $prefs =~ s/\bbody(:\d+)\b/body:$text_height/;
2159                }
2160                else {
2161                    $prefs = 'body:' . $text_height . ',' . $prefs;
2162                }
2163            }
2164        }
2165        if ( $prefs ne ( $perms->entry_prefs || '' ) ) {
2166            $perms->entry_prefs($prefs);
2167            $perms->save;
2168        }
2169    }
2170    $obj->discover_tb_from_entry();
2171    1;
2172}
2173
2174sub post_save {
2175    my $eh = shift;
2176    my ( $app, $obj ) = @_;
2177    my $sess_obj = $app->autosave_session_obj;
2178    $sess_obj->remove if $sess_obj;
2179    1;
2180}
2181
2182sub post_delete {
2183    my ( $eh, $app, $obj ) = @_;
2184
2185    my $sess_obj = $app->autosave_session_obj;
2186    $sess_obj->remove if $sess_obj;
2187
2188    $app->log(
2189        {
2190            message => $app->translate(
2191                "Entry '[_1]' (ID:[_2]) deleted by '[_3]'",
2192                $obj->title, $obj->id, $app->user->name
2193            ),
2194            level    => MT::Log::INFO(),
2195            class    => 'system',
2196            category => 'delete'
2197        }
2198    );
2199}
2200
2201sub update_entry_status {
2202    my $app = shift;
2203    my ( $new_status, @ids ) = @_;
2204    return $app->errtrans("Need a status to update entries")
2205      unless $new_status;
2206    return $app->errtrans("Need entries to update status")
2207      unless @ids;
2208    my @bad_ids;
2209    my %rebuild_these;
2210    require MT::Entry;
2211
2212    foreach my $id (@ids) {
2213        my $entry = MT::Entry->load($id)
2214          or return $app->errtrans(
2215            "One of the entries ([_1]) did not actually exist", $id );
2216        next if $entry->status == $new_status;
2217        if ( $app->config('DeleteFilesAtRebuild')
2218            && ( MT::Entry::RELEASE() eq $entry->status ) )
2219        {
2220            my $archive_type =
2221              $entry->class eq 'page'
2222              ? 'Page'
2223              : 'Individual';
2224            $app->publisher->remove_entry_archive_file(
2225                Entry       => $entry,
2226                ArchiveType => $archive_type
2227            );
2228        }
2229        my $old_status = $entry->status;
2230        $entry->status($new_status);
2231        $entry->save() and $rebuild_these{$id} = 1;
2232        my $message = $app->translate(
2233            "[_1] '[_2]' (ID:[_3]) status changed from [_4] to [_5]",
2234            $entry->class_label,
2235            $entry->title,
2236            $entry->id,
2237            $app->translate( MT::Entry::status_text($old_status) ),
2238            $app->translate( MT::Entry::status_text($new_status) )
2239        );
2240        $app->log(
2241            {
2242                message  => $message,
2243                level    => MT::Log::INFO(),
2244                class    => $entry->class,
2245                category => 'edit',
2246                metadata => $entry->id
2247            }
2248        );
2249    }
2250    $app->rebuild_these( \%rebuild_these, how => MT::App::CMS::NEW_PHASE() );
2251}
2252
2253sub _finish_rebuild_ping {
2254    my $app = shift;
2255    my ( $entry, $is_new, $ping_errors ) = @_;
2256    $app->redirect(
2257        $app->uri(
2258            'mode' => 'view',
2259            args   => {
2260                '_type' => $entry->class,
2261                blog_id => $entry->blog_id,
2262                id      => $entry->id,
2263                ( $is_new ? ( saved_added => 1 ) : ( saved_changes => 1 ) ),
2264                ( $ping_errors ? ( ping_errors => 1 ) : () )
2265            }
2266        )
2267    );
2268}
2269
2270sub ping_continuation {
2271    my $app = shift;
2272    my ( $entry, $blog, %options ) = @_;
2273    my $list = $app->needs_ping(
2274        Entry     => $entry,
2275        Blog      => $blog,
2276        OldStatus => $options{OldStatus}
2277    );
2278    require MT::Entry;
2279    if ( $entry->status == MT::Entry::RELEASE() && $list ) {
2280        my @urls = map { { url => $_ } } @$list;
2281        $app->load_tmpl(
2282            'pinging.tmpl',
2283            {
2284                blog_id    => $blog->id,
2285                entry_id   => $entry->id,
2286                old_status => $options{OldStatus},
2287                is_new     => $options{IsNew},
2288                url_list   => \@urls,
2289            }
2290        );
2291    }
2292    else {
2293        $app->_finish_rebuild_ping( $entry, $options{IsNew} );
2294    }
2295}
2296
22971;
Note: See TracBrowser for help on using the browser.