root/branches/release-40/lib/MT/CMS/Comment.pm @ 2575

Revision 2575, 63.8 kB (checked in by bchoate, 18 months ago)

Fix for displaying publish error when saving a publishable comment. BugId:80094

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Comment;
2
3use strict;
4use MT::Util qw( remove_html format_ts relative_date encode_html );
5use MT::I18N qw( const break_up_text substr_text length_text );
6
7sub edit {
8    my $cb = shift;
9    my ($app, $id, $obj, $param) = @_;
10
11    my $q = $app->param;
12    my $blog_id = $q->param('blog_id');
13    my $perms = $app->permissions;
14    my $blog = $app->blog;
15    my $type = $q->param('_type');
16
17    if ($id) {
18        $param->{nav_comments} = 1;
19        $app->add_breadcrumb(
20            $app->translate('Comments'),
21            $app->uri(
22                'mode' => 'list_comment',
23                args   => { blog_id => $blog_id }
24            )
25        );
26        $app->add_breadcrumb( $app->translate('Edit Comment') );
27        $param->{has_publish_access} = 1 if $app->user->is_superuser;
28        $param->{has_publish_access} = (
29            ( $perms->can_manage_feedback || $perms->can_edit_all_posts )
30            ? 1
31            : 0
32        ) unless $app->user->is_superuser;
33        if ( my $entry = $obj->entry ) {
34            my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE');
35            $param->{entry_title} =
36              ( !defined( $entry->title ) || $entry->title eq '' )
37              ? $app->translate('(untitled)')
38              : $entry->title;
39            $param->{entry_title} =
40              substr_text( $param->{entry_title}, 0, $title_max_len ) . '...'
41              if $param->{entry_title}
42              && length_text( $param->{entry_title} ) > $title_max_len;
43            $param->{entry_permalink} = $entry->permalink;
44            unless ( $param->{has_publish_access} ) {
45                $param->{has_publish_access} =
46                  ( $perms->can_publish_post
47                      && ( $app->user->id == $entry->author_id ) ) ? 1 : 0;
48            }
49        }
50        else {
51            $param->{no_entry} = 1;
52        }
53        $param->{comment_approved} = $obj->is_published
54          or $param->{comment_pending} = $obj->is_moderated
55          or $param->{is_junk}         = $obj->is_junk;
56
57        $param->{created_on_time_formatted} =
58          format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef );
59        $param->{created_on_day_formatted} =
60          format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef );
61
62        $param->{approved}   = $app->param('approved');
63        $param->{unapproved} = $app->param('unapproved');
64        $param->{is_junk}    = $obj->is_junk;
65
66        $param->{entry_class_label} = $obj->entry->class_label;
67        $param->{entry_class} = $obj->entry->class;
68
69        ## Load next and previous entries for next/previous links
70        if ( my $next = $obj->next ) {
71            $param->{next_comment_id} = $next->id;
72        }
73        if ( my $prev = $obj->previous ) {
74            $param->{previous_comment_id} = $prev->id;
75        }
76        if ( $obj->junk_log ) {
77            require MT::CMS::Comment;
78            MT::CMS::Comment::build_junk_table( $app, param => $param, object => $obj );
79        }
80
81        if ( my $cmtr_id = $obj->commenter_id ) {
82            my $cmtr = $app->model('author')->load($cmtr_id);
83            if ($cmtr) {
84                $param->{is_mine} = 1 if $cmtr_id == $app->user->id;
85                $param->{commenter_approved} =
86                  $cmtr->commenter_status( $obj->blog_id ) ==
87                  MT::Author::APPROVED();
88                $param->{commenter_banned} =
89                  $cmtr->commenter_status( $obj->blog_id ) ==
90                  MT::Author::BANNED();
91                $param->{type_author} = 1
92                  if MT::Author::AUTHOR() == $cmtr->type;
93                $param->{commenter_url} = $app->uri(
94                    mode => 'view',
95                    args => { '_type' => 'author', 'id' => $cmtr->id, }
96                  )
97                  if ( MT::Author::AUTHOR() == $cmtr->type )
98                  && $app->user->is_superuser;
99            }
100            if ( $obj->email !~ m/@/ ) {    # no email for this commenter
101                $param->{email_withheld} = 1;
102            }
103        }
104        $param->{invisible_unregistered} = !$obj->visible
105          && !$obj->commenter_id;
106
107        $param->{search_label} = $app->translate('Comments');
108        $param->{object_type}  = 'comment';
109
110        my $children =
111          build_comment_table( $app, load_args =>
112              [ { parent_id => $obj->id }, { direction => 'descend' } ] );
113        $param->{object_loop} = $children if @$children;
114
115        $app->load_list_actions( $type, $param );
116    }
117    1;
118}
119
120sub edit_commenter {
121    my $cb = shift;
122    my ($app, $id, $obj, $param) = @_;
123
124    # We never create commenters through the UI, so this
125    # is really the only valid condition
126    if ($id) {
127        my $q = $app->param;
128        my $blog_id = $q->param('blog_id');
129        my $perms = $app->permissions;
130        my $type = $q->param('_type');
131
132        $param->{is_email_hidden} = $obj->is_email_hidden;
133        $param->{status}          = {
134            MT::Author::PENDING()  => "pending",
135            MT::Author::APPROVED() => "approved",
136            MT::Author::BANNED()   => "banned"
137        }->{ $obj->commenter_status($blog_id) };
138        $param->{"commenter_" . $param->{status}} = 1;
139        $param->{commenter_url} = $obj->url if $obj->url;
140
141        if ( $app->user->is_superuser()
142          || ($perms && $perms->can_administer_blog ) ) {
143            $param->{search_type}   = 'author';
144            $param->{search_label}  = $app->translate('Users');
145        }
146
147        $param->{is_me} = 1 if $obj->id == $app->user->id;
148        $param->{type_author} = 1
149          if MT::Author::AUTHOR() == $obj->type;
150    }
151
152    1;
153}
154
155sub list {
156    my $app = shift;
157
158    my $trim_length =
159      $app->config('ShowIPInformation')
160      ? const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT')
161      : const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_LONG');
162    my $author_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_AUTHOR');
163    my $comment_short_len =
164      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_SHORT');
165    my $comment_long_len =
166      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_LONG');
167    my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE');
168
169    my ( %entries, %blogs, %cmntrs );
170    my $perms = $app->permissions;
171    my $user  = $app->user;
172    my $admin = $user->is_superuser
173      || ( $perms && $perms->can_administer_blog );
174    my $can_empty_junk = $admin
175      || ( $perms && $perms->can_manage_feedback )
176      ? 1 : 0;
177    my $state_editable = $admin
178      || ( $perms
179        && ( $perms->can_publish_post
180          || $perms->can_edit_all_posts || $perms->can_manage_feedback ) )
181      ? 1 : 0;
182    my $entry_pkg = $app->model('entry');
183    my $code      = sub {
184        my ( $obj, $row ) = @_;
185
186        # Comment column
187        $row->{comment_short} =
188          ( substr_text( $obj->text(), 0, $trim_length )
189              . ( length_text( $obj->text ) > $trim_length ? "..." : "" ) );
190        $row->{comment_short} =
191          break_up_text( $row->{comment_short}, $comment_short_len )
192          ;    # break up really long strings
193        $row->{comment_long} = remove_html( $obj->text );
194        $row->{comment_long} =
195          break_up_text( $row->{comment_long}, $comment_long_len )
196          ;    # break up really long strings
197
198        # Commenter name column
199        $row->{author_display} = $row->{author};
200        $row->{author_display} =
201          substr_text( $row->{author_display}, 0, $author_max_len ) . '...'
202          if $row->{author_display}
203          && length_text( $row->{author_display} ) > $author_max_len;
204        if ( $row->{commenter_id} ) {
205            my $cmntr = $cmntrs{ $row->{commenter_id} } ||=
206              MT::Author->load( { id => $row->{commenter_id} } );
207            if ($cmntr) {
208                $row->{email_hidden} = $cmntr && $cmntr->is_email_hidden();
209                $row->{auth_icon_url} = $cmntr->auth_icon_url;
210
211                my $status = $cmntr->commenter_status( $obj->blog_id );
212                $row->{commenter_approved} =
213                  ( $cmntr->commenter_status( $obj->blog_id ) ==
214                      MT::Author::APPROVED() );
215                $row->{commenter_banned} =
216                  ( $cmntr->commenter_status( $obj->blog_id ) ==
217                      MT::Author::BANNED() );
218            }
219        }
220
221        # Entry column
222        my $entry = $entries{ $obj->entry_id } ||=
223          $entry_pkg->load( $obj->entry_id );
224        unless ($entry) {
225            $entry = $entry_pkg->new;
226            $entry->title( '* ' . $app->translate('Orphaned comment') . ' *' );
227        }
228        $row->{entry_class} = $entry->class;
229        $row->{entry_class_label} = $entry->class_label;
230        $row->{entry_title} = (
231              defined( $entry->title ) ? $entry->title
232            : defined( $entry->text )  ? $entry->text
233            : ''
234        );
235        $row->{entry_title} = $app->translate('(untitled)')
236          if $row->{entry_title} eq '';
237        $row->{entry_title} =
238          substr_text( $row->{entry_title}, 0, $title_max_len ) . '...'
239          if $row->{entry_title}
240          && length_text( $row->{entry_title} ) > $title_max_len;
241
242        # Date column
243        my $blog = $blogs{ $obj->blog_id } ||= $obj->blog;
244        if ( my $ts = $obj->created_on ) {
245            $row->{created_on_time_formatted} =
246              format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
247            $row->{created_on_formatted} =
248              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
249
250            $row->{created_on_relative} = relative_date( $ts, time, $blog );
251        }
252
253        $row->{has_edit_access} = $state_editable
254          || ( $entry && ( $user->id == $entry->author_id ) );
255
256        # Blog column
257        if ($blog) {
258            $row->{weblog_id}   = $blog->id;
259            $row->{weblog_name} = $blog->name;
260        }
261        else {
262            $row->{weblog_name} =
263              '* ' . $app->translate('Orphaned comment') . ' *';
264        }
265
266        $row->{reply_count} =
267          $app->model('comment')->count( { parent_id => $obj->id } );
268    };
269
270    my %terms;
271    my $filter_col = $app->param('filter');
272    if ( $filter_col && ( my $val = $app->param('filter_val') ) ) {
273        if ( $filter_col eq 'status' ) {
274            if ( $val eq 'approved' ) {
275                $terms{visible} = 1;
276            }
277            elsif ( $val eq 'pending' ) {
278                $terms{visible} = 0;
279            }
280            elsif ( $val eq 'junk' ) {
281                $terms{junk_status} = MT::Comment::JUNK();
282            }
283            else {
284                $terms{junk_status} = MT::Comment::NOT_JUNK();
285            }
286        }
287    }
288
289    my %param;
290    my $blog_id = $app->param('blog_id');
291    $param{feed_name} = $app->translate("Comments Activity Feed");
292    $param{feed_url} =
293      $app->make_feed_link( 'comment',
294        $blog_id ? { blog_id => $blog_id } : undef );
295    $param{filter_spam} =
296      ( $app->param('filter_key') && $app->param('filter_key') eq 'spam' );
297    $param{has_expanded_mode} = 1;
298    $param{object_type}       = 'comment';
299    $param{screen_id}         = 'list-comment';
300    $param{screen_class}      = 'list-comment';
301    $param{search_label}      = $app->translate('Comments');
302    $param{state_editable}    = $state_editable;
303    $param{can_empty_junk}    = $can_empty_junk;
304    return $app->listing(
305        {
306            type   => 'comment',
307            code   => $code,
308            args   => { sort => 'created_on', direction => 'descend' },
309            params => \%param,
310            terms    => \%terms,
311        }
312    );
313}
314
315# list of all users, regardless of commenter/author on a blog
316sub list_member {
317    my $app = shift;
318
319    my $blog_id = $app->param('blog_id');
320    $app->return_to_dashboard( redirect => 1 )
321      unless $blog_id;
322
323    my $blog  = $app->blog;
324    my $user  = $app->user;
325    my $perms = $app->permissions;
326    return $app->return_to_dashboard( permission => 1 )
327      unless $user->is_superuser() || ($perms && $perms->can_administer_blog());
328
329    my $super_user = 1 if $user->is_superuser();
330    my $args       = {};
331    my $terms      = {};
332    my $param = { list_noncron => 1 };
333    $args->{join} =
334      MT::Permission->join_on( 'author_id', { blog_id => $blog_id, } );
335
336    $args->{sort_order} = 'created_on';
337    $args->{direction}  = 'descend';
338
339    $param->{saved} = 1 if $app->param('saved');
340    $param->{search_label} = $app->translate('Users');
341    $param->{object_type} = 'author';
342
343    my $all_perms;
344    my @all_perms = @{ MT::Permission->perms() };
345    $all_perms = [@all_perms];
346    foreach (@$all_perms) {
347        $_->[1] = $app->translate( $_->[1] );
348    }
349
350    require MT::Association;
351    require MT::Role;
352    my @all_roles = MT::Role->load( undef, { sort => 'name' });
353
354    my $sel_role = 0;
355    my $filter = $app->param('filter') || '';
356    if ($filter eq 'role') {
357        my $val = scalar $app->param('filter_val');
358        if ($val) {
359            $sel_role = $val;
360            $args->{join} = MT::Association->join_on('author_id', { blog_id => $blog_id, role_id => $val });
361        }
362    }
363    elsif ($filter eq 'status') {
364        my $val = $app->param('filter_val');
365        if ($val eq 'disabled') {
366            $terms->{status} = 2;
367        }
368        elsif ($val eq 'pending') {
369            $terms->{status} = 3;
370        }
371        else {
372            $terms->{status} = 1;
373        }
374    }
375
376    my @role_loop;
377    foreach my $r (@all_roles) {
378        push @role_loop, { role_id => $r->id, role_name => $r->name, selected => $r->id == $sel_role };
379    }
380    $param->{role_loop} = \@role_loop;
381    my $hasher = sub {
382        my ( $obj, $row ) = @_;
383        if ( ( $row->{email} || '' ) !~ m/@/ ) {
384            $row->{email} = '';
385        }
386        if ( $row->{created_by} ) {
387            if ( my $created_by = MT::Author->load( $row->{created_by} ) ) {
388                $row->{created_by} = $created_by->name;
389            }
390            else {
391                $row->{created_by} = $app->translate('*User deleted*');
392            }
393        }
394        $row->{is_me}           = $row->{id} == $user->id;
395        $row->{has_edit_access} = 1 if $super_user;
396        $row->{usertype_author} = 1 if $obj->type == MT::Author::AUTHOR();
397        if ( $obj->type == MT::Author::COMMENTER() ) {
398            $row->{usertype_commenter} = 1;
399            $row->{status_trusted} = 1 if $obj->is_trusted($blog_id);
400            if ($row->{name} =~ m/^[a-f0-9]{32}$/) {
401                $row->{name} = $row->{nickname} || $row->{url};
402            }
403        }
404        $row->{status_enabled} = 1 if $obj->status == 1;
405        my @roles = MT::Role->load(undef, { join => MT::Association->join_on('role_id', { author_id => $row->{id}, blog_id => $blog_id }, { unique => 1 })});
406        my @role_loop;
407        foreach my $role (@roles) {
408            my @perms;
409            foreach (@$all_perms) {
410                next unless length( $_->[1] || '' );
411                push @perms, $app->translate( $_->[1] )
412                  if $role->has( $_->[0] );
413            }
414            my $role_perms = join(", ", @perms);
415            push @role_loop, { role_name => $role->name, role_id => $role->id, role_perms => $role_perms };
416        }
417        $row->{role_loop} = \@role_loop;
418        $row->{auth_icon_url} = $obj->auth_icon_url;
419    };
420    $param->{screen_id} = "list-member";
421
422    return $app->listing(
423        {
424            type     => 'user',
425            template => 'list_member.tmpl',
426            terms    => $terms,
427            params   => $param,
428            args     => $args,
429            code     => $hasher,
430        }
431    );
432}
433
434sub list_commenter {
435    my $app = shift;
436    unless ( $app->user_can_admin_commenters ) {
437        return $app->error( $app->translate("Permission denied.") );
438    }
439
440    my $q         = $app->param;
441    my $list_pref = $app->list_pref('commenter');
442    my $blog_id   = $q->param('blog_id');
443    my %param     = %$list_pref;
444    my %terms     = ( type => MT::Author::COMMENTER() );
445    my %terms2    = ();
446    my $limit     = $list_pref->{rows};
447    my $offset    = $q->param('offset') || 0;
448    my %arg;
449    require MT::Comment;
450    $arg{'join'} = MT::Comment->join_on(
451        'commenter_id',
452        { ( $blog_id ? ( blog_id => $blog_id ) : () ) },
453        {
454            'sort'    => 'created_on',
455            direction => 'descend',
456            unique    => 1
457        }
458    );
459    my ( $filter_col, $val );
460    $param{filter_args} = "";
461
462    if (   ( $filter_col = $q->param('filter') )
463        && ( $val = $q->param('filter_val') ) )
464    {
465        if ( !exists( $terms{$filter_col} ) ) {
466            if ( $filter_col eq 'status' ) {
467                my ($perm) = (
468                      $val eq 'neutral'  ? undef
469                    : $val eq 'approved' ? 'comment'
470                    : $val eq 'banned'   ? 'not_comment'
471                    : undef
472                );
473                $arg{join} = MT::Permission->join_on(
474                    'author_id',
475                    {
476                        blog_id => $blog_id,
477                        ( defined $perm ) ? ( permissions => $perm ) : ()
478                    }
479                );
480            }
481            else {
482                $terms{$filter_col} = $val;
483            }
484
485            $param{filter}     = $filter_col;
486            $param{filter_val} = $val;
487            $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($val);
488        }
489    }
490    $arg{'offset'} = $offset if $offset;
491    $arg{'limit'} = $limit + 1;
492    my $terms = \%terms;
493    my $arg   = \%arg;
494    my $iter  = MT::Author->load_iter( $terms, $arg );
495
496    my $data = build_commenter_table( $app, iter => $iter, param => \%param );
497    if ( @$data > $limit ) {
498        pop @$data;
499        $param{next_offset}     = 1;
500        $param{next_offset_val} = $offset + $limit;
501    }
502    if ( $offset > 0 ) {
503        $param{prev_offset}     = 1;
504        $param{prev_offset_val} = $offset - $limit;
505        $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0;
506    }
507
508    $param{object_type}  = 'commenter';
509    $param{search_label} = $app->translate('Commenters');
510    $param{list_start}   = $offset + 1;
511    $param{list_end}     = $offset + scalar @$data;
512    $param{offset}       = $offset;
513    $param{limit}        = $limit;
514    delete $arg->{limit};
515    delete $arg->{offset};
516    $param{list_total} = MT::Author->count( $terms, $arg );
517
518    if ( $param{list_total} ) {
519        $param{next_max} = $param{list_total} - $limit;
520        $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1;
521    }
522    $app->add_breadcrumb( $app->translate('Authenticated Commenters') );
523    $param{nav_commenters} = 1;
524    for my $msg (qw(trusted untrusted banned unbanned)) {
525        $param{$msg} = 1 if $app->param($msg);
526    }
527    return $app->load_tmpl( 'list_commenter.tmpl', \%param );
528}
529
530sub build_commenter_table {
531    my ($app, %args) = @_;
532
533    my $param = $args{param};
534    my $iter;
535    if ( $args{load_args} ) {
536        my $class = $app->model('commenter');
537        $iter = $class->load_iter( @{ $args{load_args} } );
538    }
539    elsif ( $args{iter} ) {
540        $iter = $args{iter};
541    }
542    elsif ( $args{items} ) {
543        $iter = sub { pop @{ $args{items} } };
544    }
545    return [] unless $iter;
546
547    my $app_user  = $app->user;
548    my $user_perm = $app->permissions;
549    my $blog_id   = $app->param('blog_id');
550
551    my @data;
552    require MT::Blog;
553    my $blog = MT::Blog->load($blog_id);
554    while ( my $cmtr = $iter->() ) {
555        require MT::Comment;
556        my $cmt_count = MT::Comment->count(
557            {
558                commenter_id => $cmtr->id,
559                blog_id      => $blog_id
560            }
561        );
562        my $most_recent = MT::Comment->load(
563            {
564                commenter_id => $cmtr->id,
565                blog_id      => $blog_id
566            },
567            {
568                'sort'    => 'created_on',
569                direction => 'descend'
570            }
571        ) if $cmt_count > 0;
572
573        my $blog_connection = MT::Permission->load(
574            {
575                author_id => $cmtr->id,
576                blog_id   => $blog_id
577            }
578        );
579
580        # Tells us whether the commenter is associated with this
581        # blog. the role flags are not important
582        next if ( !$cmt_count && !$blog_connection );
583
584        my $row = {};
585        $row->{author_id}      = $cmtr->id();
586        $row->{author}         = $cmtr->name();
587        $row->{author_display} = $cmtr->nickname();
588        $row->{email}          = $cmtr->email();
589        $row->{url}            = $cmtr->url();
590        $row->{email_hidden}   = $cmtr->is_email_hidden();
591        $row->{comment_count}  = $cmt_count;
592        if ($most_recent) {
593
594            if ( my $ts = $most_recent->created_on ) {
595                $row->{most_recent_time_formatted} =
596                  format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
597                $row->{most_recent_formatted} =
598                  format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
599                $row->{most_recent_relative} =
600                  relative_date( $ts, time, $blog );
601            }
602        }
603        $row->{status} = {
604            PENDING  => "neutral",
605            APPROVED => "approved",
606            BANNED   => "banned"
607        }->{ $cmtr->commenter_status($blog_id) };
608        $row->{commenter_approved} =
609          $cmtr->commenter_status($blog_id) == MT::Author::APPROVED();
610        $row->{commenter_banned} =
611          $cmtr->commenter_status($blog_id) == MT::Author::BANNED();
612        $row->{commenter_url}   = $cmtr->url if $cmtr->url;
613        $row->{has_edit_access} = $app->user_can_admin_commenters;
614        $row->{object}          = $cmtr;
615        push @data, $row;
616    }
617    return [] unless @data;
618
619    $param->{commenter_table}[0]{object_loop} = \@data if @data;
620    $app->load_list_actions( 'commenter', $param->{commenter_table}[0] );
621    $param->{commenter_table}[0]{page_actions} =
622      $app->page_actions('list_commenter');
623    $param->{object_loop} = $param->{commenter_table}[0]{object_loop};
624    \@data;
625}
626
627sub save_commenter_perm {
628    my $app      = shift;
629    my ($params) = @_;
630    my $q        = $app->param;
631    my $action   = $q->param('action');
632
633    $app->validate_magic() or return;
634
635    my $acted_on;
636    my %rebuild_set;
637    my @ids     = $params ? @$params : $app->param('commenter_id');
638    my $blog_id = $q->param('blog_id');
639    my $author  = $app->user;
640
641    foreach my $id (@ids) {
642        ( $id, $blog_id ) = @$id if ref $id eq 'ARRAY';
643
644        my $cmntr = MT::Author->load($id)
645          or return $app->errtrans( "No such commenter [_1].", $id );
646        my $old_status = $cmntr->commenter_status($blog_id);
647
648        if (   $action eq 'trust'
649            && $cmntr->commenter_status($blog_id) != MT::Author::APPROVED() )
650        {
651            $cmntr->approve($blog_id) or return $app->error( $cmntr->errstr );
652            $app->log(
653                $app->translate(
654                    "User '[_1]' trusted commenter '[_2]'.", $author->name,
655                    $cmntr->name
656                )
657            );
658            $acted_on++;
659        }
660        elsif ($action eq 'ban'
661            && $cmntr->commenter_status($blog_id) != MT::Author::BANNED() )
662        {
663            $cmntr->ban($blog_id) or return $app->error( $cmntr->errstr );
664            $app->log(
665                $app->translate(
666                    "User '[_1]' banned commenter '[_2]'.", $author->name,
667                    $cmntr->name
668                )
669            );
670            $acted_on++;
671        }
672        elsif ($action eq 'unban'
673            && $cmntr->commenter_status($blog_id) == MT::Author::BANNED() )
674        {
675            $cmntr->pending($blog_id) or return $app->error( $cmntr->errstr );
676            $app->log(
677                $app->translate(
678                    "User '[_1]' unbanned commenter '[_2]'.", $author->name,
679                    $cmntr->name
680                )
681            );
682            $acted_on++;
683        }
684        elsif ($action eq 'untrust'
685            && $cmntr->commenter_status($blog_id) == MT::Author::APPROVED() )
686        {
687            $cmntr->pending($blog_id) or return $app->error( $cmntr->errstr );
688            $app->log(
689                $app->translate(
690                    "User '[_1]' untrusted commenter '[_2]'.", $author->name,
691                    $cmntr->name
692                )
693            );
694            $acted_on++;
695        }
696
697        require MT::Entry;
698        require MT::Comment;
699        my $iter = MT::Entry->load_iter(
700            undef,
701            {
702                join => MT::Comment->join_on(
703                    'entry_id', { commenter_id => $cmntr->id }
704                )
705            }
706        );
707        my $e;
708        while ( $e = $iter->() ) {
709            $rebuild_set{$id} = $e;
710        }
711    }
712    if ($acted_on) {
713        my %msgs = (
714            trust   => 'trusted',
715            ban     => 'banned',
716            unban   => 'unbanned',
717            untrust => 'untrusted'
718        );
719        $app->add_return_arg( $msgs{$action} => 1 );
720    }
721    $app->call_return;
722}
723
724sub trust_commenter_by_comment {
725    my $app        = shift;
726    my @comments   = $app->param('id');
727    my @commenters = map_comment_to_commenter( $app, \@comments );
728    $app->param( 'action', 'trust' );
729    save_commenter_perm( $app, \@commenters );
730}
731
732sub untrust_commenter_by_comment {
733    my $app        = shift;
734    my @comments   = $app->param('id');
735    my @commenters = map_comment_to_commenter( $app, \@comments );
736    $app->param( 'action', 'untrust' );
737    save_commenter_perm( $app, \@commenters );
738}
739
740sub ban_commenter_by_comment {
741    my $app        = shift;
742    my @comments   = $app->param('id');
743    my @commenters = map_comment_to_commenter( $app, \@comments );
744    $app->param( 'action', 'ban' );
745    save_commenter_perm( $app, \@commenters );
746}
747
748sub unban_commenter_by_comment {
749    my $app        = shift;
750    my @comments   = $app->param('id');
751    my @commenters = map_comment_to_commenter( $app, \@comments );
752    $app->param( 'action', 'unban' );
753    save_commenter_perm( $app, \@commenters );
754}
755
756sub trust_commenter {
757    my $app        = shift;
758    my @commenters = $app->param('id');
759    $app->param( 'action', 'trust' );
760    save_commenter_perm( $app, \@commenters );
761}
762
763sub ban_commenter {
764    my $app        = shift;
765    my @commenters = $app->param('id');
766    $app->param( 'action', 'ban' );
767    save_commenter_perm( $app, \@commenters );
768}
769
770sub unban_commenter {
771    my $app        = shift;
772    my @commenters = $app->param('id');
773    $app->param( 'action', 'unban' );
774    save_commenter_perm( $app, \@commenters );
775}
776
777sub untrust_commenter {
778    my $app        = shift;
779    my @commenters = $app->param('id');
780    $app->param( 'action', 'untrust' );
781    save_commenter_perm( $app, \@commenters );
782}
783
784sub approve_item {
785    my $app   = shift;
786    my $perms = $app->permissions;
787    $app->param( 'approve', 1 );
788    set_item_visible($app);
789}
790
791sub unapprove_item {
792    my $app   = shift;
793    my $perms = $app->permissions;
794    $app->param( 'unapprove', 1 );
795    set_item_visible($app);
796}
797
798sub cfg_comments {
799    my $app     = shift;
800    my $q       = $app->param;
801    my $blog_id = scalar $q->param('blog_id');
802    return $app->return_to_dashboard( redirect => 1 )
803      unless $blog_id;
804    $q->param( '_type', 'blog' );
805    $q->param( 'id',    scalar $q->param('blog_id') );
806    $app->forward( "view",
807        {
808            output         => 'cfg_comments.tmpl',
809            screen_class   => 'settings-screen',
810            screen_id      => 'comment-settings',
811        }
812    );
813}
814
815sub cfg_registration {
816    my $app     = shift;
817    my $q       = $app->param;
818    my $blog_id = scalar $q->param('blog_id');
819    return $app->return_to_dashboard( redirect => 1 )
820      unless $blog_id;
821    $q->param( '_type', 'blog' );
822    $q->param( 'id',    scalar $q->param('blog_id') );
823    eval { require Digest::SHA1; };
824    my $openid_available = $@ ? 0 : 1;
825    $app->forward( "view",
826        {
827            output       => 'cfg_registration.tmpl',
828            screen_class => 'settings-screen registration-screen',
829            openid_enabled => $openid_available
830        }
831    );
832}
833
834sub cfg_spam {
835    my $app = shift;
836    my $q   = $app->param;
837    $q->param( '_type', 'blog' );
838    $q->param( 'id',    scalar $q->param('blog_id') );
839
840    my $plugin_config_html;
841    my $plugin_name;
842    my $plugin;
843    if ( my $p = $q->param('plugin') ) {
844        $plugin = $MT::Plugins{$p};
845        if ($plugin) {
846            $plugin = $plugin->{object};
847        }
848        else {
849            $plugin = MT->component($p);
850        }
851        return $app->errtrans("Invalid request.") unless $plugin;
852
853        my $scope;
854        if ( $q->param('blog_id') ) {
855            $scope = 'blog:' . $q->param('blog_id');
856        }
857        else {
858            $scope = 'system';
859        }
860        $plugin_name = $plugin->name;
861        my %plugin_param;
862        $plugin->load_config( \%plugin_param, $scope );
863        my $snip_tmpl = $plugin->config_template( \%plugin_param, $scope );
864        my $tmpl;
865        if ( ref $snip_tmpl ne 'MT::Template' ) {
866            require MT::Template;
867            $tmpl = MT::Template->new(
868                type   => 'scalarref',
869                source => ref $snip_tmpl
870                ? $snip_tmpl
871                : \$snip_tmpl
872            );
873        }
874        else {
875            $tmpl = $snip_tmpl;
876        }
877
878        # Process template independent of $app to avoid premature
879        # localization (give plugin a chance to do L10N first).
880        $tmpl->param( \%plugin_param );
881        $plugin_config_html = $tmpl->output();
882        $plugin_config_html =
883          $plugin->translate_templatized($plugin_config_html)
884          if $plugin_config_html =~ m/<(?:__trans|mt_trans) /i;
885    }
886    my $filters = MT::Component->registry('junk_filters') || [];
887    my %plugins;
888    foreach my $set (@$filters) {
889        foreach my $f ( values %$set ) {
890            $plugins{ $f->{plugin} } = $f->{plugin};
891        }
892    }
893    my @plugins = values %plugins;
894    my $loop    = [];
895    foreach my $p (@plugins) {
896        push @$loop,
897          {
898            name   => $p->name,
899            plugin => $p->id,
900            active => ( $plugin && ( $p->id eq $plugin->id ) ? 1 : 0 ),
901          },
902          ;
903    }
904    @$loop = sort { $a->{name} cmp $b->{name} } @$loop;
905
906    $app->forward( "view",
907        {
908            plugin_config_html => $plugin_config_html,
909            plugin_name        => $plugin_name,
910            junk_filter_loop   => $loop,
911            output             => 'cfg_spam.tmpl',
912            screen_class       => 'settings-screen spam-screen'
913        }
914    );
915}
916
917sub empty_junk {
918    my $app     = shift;
919    my $perms   = $app->permissions;
920    my $user    = $app->user;
921    my $blog_id = $app->param('blog_id');
922    return $app->errtrans("Permission denied.")
923      if ( !$blog_id && !$user->is_superuser() )
924      || (
925        $perms
926        && !(
927               $perms->can_administer_blog
928            || $perms->can_edit_all_posts
929            || $perms->can_manage_feedback
930        )
931      );
932
933    my $type  = $app->param('_type');
934    my $class = $app->model($type);
935    my $arg   = {};
936    require MT::Comment;
937    $arg->{junk_status} = MT::Comment::JUNK();
938    $arg->{blog_id} = $blog_id if $blog_id;
939    $class->remove($arg);
940    $app->add_return_arg( 'emptied' => 1 );
941    $app->call_return;
942}
943
944sub handle_junk {
945    my $app   = shift;
946    my @ids   = $app->param("id");
947    my $type  = $app->param("_type");
948    my $class = $app->model($type);
949    my @item_loop;
950    my $i       = 0;
951    my $blog_id = $app->param('blog_id');
952    my ( %rebuild_entries, %rebuild_categories );
953
954    my @obj_ids = $app->param('id');
955    if ( my $req_nonce = $app->param('nonce') ) {
956        if ( scalar @obj_ids == 1 ) {
957            my $cmt_id = $obj_ids[0];
958            if ( my $obj = $class->load($cmt_id) ) {
959                my $nonce =
960                  MT::Util::perl_sha1_digest_hex( $obj->id
961                      . $obj->created_on
962                      . $obj->blog_id
963                      . $app->config->SecretToken );
964                return $app->errtrans("Invalid request.")
965                  unless $nonce eq $req_nonce;
966                my $return_args = $app->uri_params(
967                    mode => 'view',
968                    args => {
969                        '_type' => $type,
970                        id      => $cmt_id,
971                        blog_id => $obj->blog_id
972                    }
973                );
974                $return_args =~ s!^\?!!;
975                $app->return_args($return_args);
976            }
977            else {
978                return $app->errtrans("Invalid request.");
979            }
980        }
981        else {
982            return $app->errtrans("Invalid request.");
983        }
984    }
985    else {
986        $app->validate_magic() or return;
987    }
988
989    my $perms = $app->permissions;
990    my $perm_checked = ( $app->user->is_superuser()
991      || (
992        $app->param('blog_id')
993        && (   $perms->can_manage_feedback
994            || $perms->can_edit_all_posts )
995      ) ) ? 1 : 0;
996
997    foreach my $id (@ids) {
998        next unless $id;
999
1000        my $obj = $class->load($id) or die "No $class $id";
1001        my $old_visible = $obj->visible || 0;
1002        unless ($perm_checked) {
1003            if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) {
1004                next if $obj->parent->author_id != $app->user->id;
1005            }
1006            elsif ( $obj->isa('MT::Comment') ) {
1007                next if $obj->entry->author_id != $app->user->id;
1008            }
1009            next unless $perms->can_publish_post;
1010        }
1011        $obj->junk;
1012        $app->run_callbacks( 'handle_spam', $app, $obj )
1013          ;            # mv this into blk below?
1014        $obj->save;    # (so that each cb doesn't have to save indiv'ly)
1015        next if $old_visible == $obj->visible;
1016        if ( $obj->isa('MT::TBPing') ) {
1017            my ( $parent_type, $parent_id ) = $obj->parent_id();
1018            if ( $parent_type eq 'MT::Entry' ) {
1019                $rebuild_entries{$parent_id} = 1;
1020            }
1021            else {
1022                $rebuild_categories{ $obj->category_id } = 1;
1023
1024                # TODO: do something with this list.
1025            }
1026        }
1027        else {
1028            $rebuild_entries{ $obj->entry_id } = 1;
1029        }
1030    }
1031    $app->add_return_arg( 'junked' => 1 );
1032    if (%rebuild_entries) {
1033        $app->rebuild_these( \%rebuild_entries, how => MT::App::CMS::NEW_PHASE() );
1034    }
1035    else {
1036        $app->call_return;
1037    }
1038}
1039
1040sub not_junk {
1041    my $app = shift;
1042
1043    my $perms = $app->permissions;
1044
1045    my @ids = $app->param("id");
1046    my @item_loop;
1047    my $i     = 0;
1048    my $type  = $app->param('_type');
1049    my $class = $app->model($type);
1050    my %rebuild_set;
1051
1052    my $perm_checked = ( $app->user->is_superuser()
1053      || (
1054        $app->param('blog_id')
1055        && (   $perms->can_manage_feedback
1056            || $perms->can_edit_all_posts )
1057      ) ) ? 1 : 0;
1058
1059    foreach my $id (@ids) {
1060        next unless $id;
1061        my $obj = $class->load($id)
1062            or next;
1063        unless ($perm_checked) {
1064            if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) {
1065                next if $obj->parent->author_id != $app->user->id;
1066            }
1067            elsif ( $obj->isa('MT::Comment') ) {
1068                next if $obj->entry->author_id != $app->user->id;
1069            }
1070            next unless $perms->can_publish_post;
1071        }
1072        $obj->approve;
1073        $app->run_callbacks( 'handle_ham', $app, $obj );
1074        if ( $obj->isa('MT::TBPing') ) {
1075            my ( $parent_type, $parent_id ) = $obj->parent_id();
1076            if ( $parent_type eq 'MT::Entry' ) {
1077                $rebuild_set{$parent_id} = 1;
1078            }
1079            else {
1080            }
1081        }
1082        else {
1083            $rebuild_set{ $obj->entry_id } = 1;
1084        }
1085        $obj->save();
1086    }
1087    $app->param( 'approve', 1 );
1088
1089    $app->add_return_arg( 'unjunked' => 1 );
1090
1091    $app->rebuild_these( \%rebuild_set, how => MT::App::CMS::NEW_PHASE() );
1092}
1093
1094sub cfg_system_feedback {
1095    my $app = shift;
1096    my %param;
1097    return $app->redirect(
1098        $app->uri(
1099            mode => 'cfg_comments',
1100            args => { blog_id => $app->param('blog_id') }
1101        )
1102    ) if $app->param('blog_id');
1103
1104    return $app->errtrans("Permission denied.")
1105      unless $app->user->is_superuser();
1106
1107    my $cfg = $app->config;
1108    $param{nav_config} = 1;
1109    $app->add_breadcrumb( $app->translate('Feedback Settings') );
1110    $param{nav_settings}         = 1;
1111    $param{comment_disable}      = $cfg->AllowComments ? 0 : 1;
1112    $param{ping_disable}         = $cfg->AllowPings ? 0 : 1;
1113    $param{disabled_notify_ping} = $cfg->DisableNotificationPings ? 1 : 0;
1114    $param{system_no_email}      = 1 unless $cfg->EmailAddressMain;
1115    my $send = $cfg->OutboundTrackbackLimit || 'any';
1116
1117    if ( $send =~ m/^(any|off|selected|local)$/ ) {
1118        $param{ "trackback_send_" . $cfg->OutboundTrackbackLimit } = 1;
1119        if ( $send eq 'selected' ) {
1120            my @domains = $cfg->OutboundTrackbackDomains;
1121            my $domains = join "\n", @domains;
1122            $param{trackback_send_domains} = $domains;
1123        }
1124    }
1125    else {
1126        $param{"trackback_send_any"} = 1;
1127    }
1128    $param{saved}        = $app->param('saved');
1129    $param{screen_class} = "settings-screen system-feedback-settings";
1130    $app->load_tmpl( 'cfg_system_feedback.tmpl', \%param );
1131}
1132
1133sub save_cfg_system_feedback {
1134    my $app = shift;
1135    return $app->errtrans("Permission denied.")
1136      unless $app->user->is_superuser();
1137
1138    $app->validate_magic or return;
1139    my $cfg = $app->config;
1140    $cfg->AllowComments( ( $app->param('comment_disable') ? 0 : 1 ), 1 );
1141    $cfg->AllowPings(    ( $app->param('ping_disable')    ? 0 : 1 ), 1 );
1142    $cfg->DisableNotificationPings(
1143        ( $app->param('disable_notify_ping') ? 1 : 0 ), 1 );
1144    my $send = $app->param('trackback_send') || 'any';
1145    if ( $send =~ m/^(any|off|selected|local)$/ ) {
1146        $cfg->OutboundTrackbackLimit( $send, 1 );
1147        if ( $send eq 'selected' ) {
1148            my $domains = $app->param('trackback_send_domains') || '';
1149            $domains =~ s/[\r\n]+/ /gs;
1150            $domains =~ s/\s{2,}/ /gs;
1151            my @domains = split /\s/, $domains;
1152            $cfg->OutboundTrackbackDomains( \@domains, 1 );
1153        }
1154    }
1155
1156    $cfg->save_config();
1157    $app->redirect(
1158        $app->uri(
1159            'mode' => 'cfg_system_feedback',
1160            args   => { saved => 1 }
1161        )
1162    );
1163}
1164
1165sub reply {
1166    my $app = shift;
1167    my $q   = $app->param;
1168
1169    my $reply_to    = encode_html( $q->param('reply_to') );
1170    my $magic_token = encode_html( $q->param('magic_token') );
1171    my $blog_id     = encode_html( $q->param('blog_id') );
1172    my $return_url  = encode_html( $q->param('return_url') );
1173    my $text        = encode_html( $q->param('text') );
1174    my $indicator   = $app->static_path . 'images/indicator.gif';
1175    my $url         = $app->uri;
1176    <<SPINNER;
1177<html><head><style type="text/css">
1178#dialog-indicator {position: relative;top: 200px;
1179background: url($indicator)
1180no-repeat;width: 66px;height: 66px;margin: 0 auto;
1181}</style><script type="text/javascript">
1182function init() { var f = document.getElementById('f'); f.submit(); }
1183window.setTimeout("init()", 1500);
1184</script></head><body>
1185<div align="center"><div id="dialog-indicator"></div>
1186<form id="f" method="post" action="$url">
1187<input type="hidden" name="__mode" value="do_reply" />
1188<input type="hidden" name="reply_to" value="$reply_to" />
1189<input type="hidden" name="magic_token" value="$magic_token" />
1190<input type="hidden" name="blog_id" value="$blog_id" />
1191<input type="hidden" name="return_url" value="$return_url" />
1192<input type="hidden" name="text" value="$text" />
1193</form></div></body></html>
1194SPINNER
1195}
1196
1197sub do_reply {
1198    my $app = shift;
1199    my $q   = $app->param;
1200
1201    my $param = {
1202        reply_to    => $q->param('reply_to'),
1203        magic_token => $q->param('magic_token'),
1204        blog_id     => $q->param('blog_id'),
1205    };
1206
1207    my ( $comment, $parent, $entry ) = _prepare_reply($app);
1208
1209    $param->{commenter_name} = $parent->author;
1210    $param->{entry_title}    = $entry->title;
1211    $param->{comment_created_on} =
1212      format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef );
1213    $param->{comment_text} = $parent->text;
1214
1215    return $app->build_page( 'dialog/comment_reply.tmpl',
1216        { %$param, error => $app->errstr } )
1217      unless $comment;
1218
1219    $comment->parent_id( $param->{reply_to} );
1220    $comment->approve;
1221    return $app->handle_error(
1222        $app->translate( "An error occurred: [_1]", $comment->errstr() ) )
1223      unless $comment->save;
1224
1225    MT::Util::start_background_task(
1226        sub {
1227            $app->rebuild_entry( Entry => $parent->entry_id, BuildDependencies => 1 )
1228              or return $app->publish_error( "Publish failed: [_1]", $app->errstr );
1229            $app->_send_comment_notification( $comment, q(), $entry,
1230                $app->model('blog')->load( $param->{blog_id} ), $app->user );
1231        }
1232    );
1233    return $app->build_page( 'dialog/comment_reply.tmpl',
1234        { closing => 1, return_url => $q->param('return_url') } );
1235}
1236
1237sub reply_preview {
1238    my $app = shift;
1239    my $q   = $app->param;
1240    my $cfg = $app->config;
1241
1242    my $param = {
1243        reply_to    => $q->param('reply_to'),
1244        magic_token => $q->param('magic_token'),
1245        blog_id     => $q->param('blog_id'),
1246    };
1247    my ( $comment, $parent, $entry ) = _prepare_reply($app);
1248
1249    my $blog = $parent->blog
1250            || $app->model('blog')->load($q->param('blog_id'));
1251    return $app->error($app->translate('Can\'t load blog #[_1].', $q->param('blog_id'))) unless $blog;
1252
1253    require MT::Sanitize;
1254    my $spec = $blog->sanitize_spec
1255            || $app->config->GlobalSanitizeSpec;
1256    $param->{commenter_name} = MT::Sanitize->sanitize($parent->author, $spec);
1257    $param->{entry_title}    = $entry->title;
1258    $param->{comment_created_on} =
1259      format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef );
1260    $param->{comment_text} = MT::Sanitize->sanitize($parent->text, $spec);
1261
1262    return $app->build_page( 'dialog/comment_reply.tmpl',
1263        { %$param, error => $app->errstr } )
1264      unless $comment;
1265
1266    ## Set timestamp as we would usually do in ObjectDriver.
1267    my @ts = MT::Util::offset_time_list( time, $entry->blog_id );
1268    my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1,
1269      @ts[ 3, 2, 1, 0 ];
1270    $comment->created_on($ts);
1271    $comment->commenter_id( $app->user->id );
1272    $param->{'comment'} = $comment;
1273
1274    require MT::Serialize;
1275    my $ser   = MT::Serialize->new( $cfg->Serializer );
1276    my $state = $comment->column_values;
1277    $state->{static} = $q->param('static');
1278    $param->{'comment_state'} = unpack 'H*', $ser->serialize( \$state );
1279    $param->{'comment_is_static'} = 1;
1280    $param->{'entry'} = $entry;
1281    $param->{'current_timestamp'} = $ts;
1282    $param->{'commenter'} = $app->user;
1283    $param->{'blog_id'} = $parent->blog_id;
1284    $param->{'blog'} = $parent->blog;
1285
1286    return $app->build_page( 'dialog/comment_reply.tmpl',
1287        { %$param, text => $q->param('text') } );
1288}
1289
1290sub dialog_post_comment {
1291    my $app = shift;
1292    $app->validate_magic or return;
1293
1294    my $user      = $app->user;
1295    my $parent_id = $app->param('reply_to');
1296
1297    return $app->errtrans('Parent comment id was not specified.')
1298      unless $parent_id;
1299
1300    my $comment_class = $app->model('comment');
1301    my $parent        = $comment_class->load($parent_id)
1302      or return $app->errtrans('Parent comment was not found.');
1303    return $app->errtrans("You can't reply to unapproved comment.")
1304      unless $parent->is_published;
1305
1306    my $perms = $app->{perms};
1307    return unless $perms;
1308
1309    my $entry_class = $app->_load_driver_for('entry');
1310    my $entry       = $entry_class->load( $parent->entry_id );
1311
1312    unless ( $user->is_superuser()
1313        || $perms->can_edit_all_posts
1314        || $perms->can_manage_feedback )
1315    {
1316        return $app->errtrans("Permission denied.")
1317          unless $perms->can_edit_entry( $entry, $user, 1 )
1318          ;    # check for publish_post
1319    }
1320
1321    my $blog = $parent->blog
1322            || $app->model('blog')->load($app->param('blog_id'));
1323    return $app->error($app->translate('Can\'t load blog #[_1].', $app->param('blog_id'))) unless $blog;
1324
1325    require MT::Sanitize;
1326    my $spec = $blog->sanitize_spec
1327            || $app->config->GlobalSanitizeSpec;
1328    my $param = {
1329        reply_to       => $parent_id,
1330        commenter_name => MT::Sanitize->sanitize($parent->author, $spec),
1331        entry_title    => $entry->title,
1332        comment_created_on =>
1333          format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $parent->created_on, $blog, $app->user ? $app->user->preferred_language : undef ),
1334        comment_text       => MT::Sanitize->sanitize($parent->text, $spec),
1335        comment_script_url => $app->config('CGIPath')
1336          . $app->config('CommentScript'),
1337        return_url => $app->base
1338          . $app->mt_uri . '?'
1339          . $app->param('return_args'),
1340    };
1341
1342    $app->load_tmpl( 'dialog/comment_reply.tmpl', $param );
1343}
1344
1345sub can_view {
1346    my $eh = shift;
1347    my ( $app, $id, $objp ) = @_;
1348    return 0 unless ($id);
1349    my $obj = $objp->force() or return 0;
1350    require MT::Entry;
1351    my $entry = MT::Entry->load( $obj->entry_id )
1352      or return 0;
1353    my $perms = $app->permissions;
1354    if (
1355        !(
1356               $entry->author_id == $app->user->id
1357            || $perms->can_edit_all_posts
1358            || $perms->can_manage_feedback
1359        )
1360      )
1361    {
1362        return 0;
1363    }
1364    1;
1365}
1366
1367sub can_save {
1368    my ( $eh, $app, $id ) = @_;
1369    return 0 unless $id;    # Can't create new comments here
1370    return 1 if $app->user->is_superuser();
1371
1372    my $perms = $app->permissions;
1373    return 1
1374      if $perms
1375      && ( $perms->can_edit_all_posts
1376        || $perms->can_manage_feedback );
1377
1378    my $c = MT::Comment->load($id)
1379        or return 0;
1380    if ( $perms && $perms->can_create_post && $perms->can_publish_post ) {
1381        return $c->entry->author_id == $app->user->id;
1382    }
1383    elsif ( $perms && $perms->can_create_post ) {
1384        return ( $c->entry->author_id == $app->user->id )
1385          && ( ( $c->is_junk && ( 'junk' eq $app->param('status') ) )
1386            || ( $c->is_moderated && ( 'moderate' eq $app->param('status') ) )
1387            || ( $c->is_published && ( 'publish' eq $app->param('status') ) ) );
1388    }
1389    elsif ( $perms && $perms->can_publish_post ) {
1390        return 0 unless $c->entry->author_id == $app->user->id;
1391        return 0
1392          unless ( $c->text eq $app->param('text') )
1393          && ( $c->author eq $app->param('author') )
1394          && ( $c->email  eq $app->param('email') )
1395          && ( $c->url    eq $app->param('url') );
1396    }
1397    else {
1398        return 0;
1399    }
1400}
1401
1402sub can_delete {
1403    my ( $eh, $app, $obj ) = @_;
1404    my $author = $app->user;
1405    return 1 if $author->is_superuser();
1406    my $perms = $app->permissions;
1407    require MT::Entry;
1408    my $entry = MT::Entry->load( $obj->entry_id )
1409        or return 0;
1410    if ( !$perms || $perms->blog_id != $entry->blog_id ) {
1411        $perms ||= $author->permissions( $entry->blog_id );
1412    }
1413
1414    # publish_post allows entry author to delete comment.
1415    return 1
1416      if $perms->can_edit_all_posts
1417      || $perms->can_manage_feedback
1418      || $perms->can_edit_entry( $entry, $author, 1 );
1419    return 0 if $obj->visible;    # otherwise, visible comment can't be deleted.
1420    return $perms && $perms->can_edit_entry( $entry, $author );
1421}
1422
1423sub pre_save {
1424    my $eh = shift;
1425    my ( $app, $obj, $original ) = @_;
1426    my $perms = $app->permissions;
1427    return 1
1428      unless $perms->can_publish_post
1429      || $perms->can_edit_all_posts
1430      || $perms->can_manage_feedback;
1431
1432    unless ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) {
1433        return 1 unless $perms->can_publish_post;
1434        require MT::Entry;
1435        my $entry = MT::Entry->load( $obj->entry_id )
1436          or return 1;
1437        return 1 unless $entry->author_id == $app->user->id;
1438    }
1439
1440    my $status = $app->param('status');
1441    if ( $status eq 'publish' ) {
1442        $obj->approve;
1443        if ( $original->junk_status != $obj->junk_status ) {
1444            $app->run_callbacks( 'handle_ham', $app, $obj );
1445        }
1446    }
1447    elsif ( $status eq 'moderate' ) {
1448        $obj->moderate;
1449    }
1450    elsif ( $status eq 'junk' ) {
1451        $obj->junk;
1452        if ( $original->junk_status != $obj->junk_status ) {
1453            $app->run_callbacks( 'handle_spam', $app, $obj );
1454        }
1455    }
1456    return 1;
1457}
1458
1459sub post_save {
1460    my $eh = shift;
1461    my ( $app, $obj, $original ) = @_;
1462
1463    if ( $obj->visible
1464        || ( ( $obj->visible || 0 ) != ( $original->visible || 0 ) ) )
1465    {
1466        return MT::Util::start_background_task(
1467            sub {
1468                my $app = MT->instance;
1469                if ( !$app->rebuild_entry( Entry => $obj->entry_id, BuildIndexes => 1 ) ) {
1470                    $app->publish_error(); # logs error as well.
1471                    return $eh->error( MT->translate( "Publish failed: [_1]", $app->errstr ) );
1472                }
1473            }
1474        );
1475    }
1476    1;
1477}
1478
1479sub post_delete {
1480    my ( $eh, $app, $obj ) = @_;
1481
1482    require MT::Entry;
1483    my $title = '';
1484    if ( my $entry = MT::Entry->load( $obj->entry_id ) ) {
1485        $title = $entry->title;
1486    }
1487
1488    $app->log(
1489        {
1490            message => $app->translate(
1491"Comment (ID:[_1]) by '[_2]' deleted by '[_3]' from entry '[_4]'",
1492                $obj->id, $obj->author, $app->user->name, $title
1493            ),
1494            level    => MT::Log::INFO(),
1495            class    => 'system',
1496            category => 'delete'
1497        }
1498    );
1499}
1500
1501sub can_view_commenter {
1502    my $eh = shift;
1503    my ( $app, $id ) = @_;
1504    my $auth = MT::Author->load(
1505        {
1506            id   => $id,
1507            type => MT::Author::COMMENTER()
1508        }
1509    );
1510    $auth ? 1 : 0;
1511}
1512
1513sub can_delete_commenter {
1514    my ( $eh, $app, $obj ) = @_;
1515    my $author = $app->user;
1516    return 1 if $author->is_superuser();
1517    my $perms = $author->permissions( $obj->blog_id );
1518    ( $perms && $perms->can_administer_blog );
1519}
1520
1521sub build_junk_table {
1522    my $app = shift;
1523    my (%args) = @_;
1524
1525    my $param = $args{param};
1526    my $obj   = $args{object};
1527
1528    if ( defined $obj->junk_score ) {
1529        $param->{junk_score} =
1530          ( $obj->junk_score > 0 ? '+' : '' ) . $obj->junk_score;
1531    }
1532    my $log = $obj->junk_log || '';
1533    my @log = split /\r?\n/, $log;
1534    my @junk;
1535    for ( my $i = 0 ; $i < scalar(@log) ; $i++ ) {
1536        my $line = $log[$i];
1537        $line =~ s/(^\s+|\s+$)//g;
1538        next unless $line;
1539        last if $line =~ m/^--->/;
1540        my ( $test, $score, $log );
1541        ($test) = $line =~ m/^([^:]+?):/;
1542        if ( defined $test ) {
1543            ($score) = $test =~ m/\(([+-]?\d+?(?:\.\d*?)?)\)/;
1544            $test =~ s/\(.+\)//;
1545        }
1546        if ( defined $score ) {
1547            $score =~ s/\+//;
1548            $score .= '.0' unless $score =~ m/\./;
1549            $score = ( $score > 0 ? '+' : '' ) . $score;
1550        }
1551        $log = $line;
1552        $log =~ s/^[^:]+:\s*//;
1553        $log = encode_html($log);
1554        for ( my $j = $i + 1 ; $j < scalar(@log) ; $j++ ) {
1555            my $line = encode_html( $log[$j] );
1556            if ( $line =~ m/^\t+(.*)$/s ) {
1557                $i = $j;
1558                $log .= "<br />" . $1;
1559            }
1560            else {
1561                last;
1562            }
1563        }
1564        push @junk, { test => $test, score => $score, log => $log };
1565    }
1566    $param->{junk_log_loop} = \@junk;
1567    \@junk;
1568}
1569
1570sub set_item_visible {
1571    my $app    = shift;
1572    my $perms  = $app->permissions;
1573    my $author = $app->user;
1574
1575    my $type    = $app->param('_type');
1576    my $class   = $app->model($type);
1577    my @obj_ids = $app->param('id');
1578
1579    if ( my $req_nonce = $app->param('nonce') ) {
1580        if ( scalar @obj_ids == 1 ) {
1581            my $cmt_id = $obj_ids[0];
1582            if ( my $obj = $class->load($cmt_id) ) {
1583                my $nonce =
1584                  MT::Util::perl_sha1_digest_hex( $obj->id
1585                      . $obj->created_on
1586                      . $obj->blog_id
1587                      . $app->config->SecretToken );
1588                return $app->errtrans("Invalid request.")
1589                  unless $nonce eq $req_nonce;
1590                my $return_args = $app->uri_params(
1591                    mode => 'view',
1592                    args => {
1593                        '_type' => $type,
1594                        id      => $cmt_id,
1595                        blog_id => $obj->blog_id
1596                    }
1597                );
1598                $return_args =~ s!^\?!!;
1599                $app->return_args($return_args);
1600            }
1601            else {
1602                return $app->errtrans("Invalid request.");
1603            }
1604        }
1605        else {
1606            return $app->errtrans("Invalid request.");
1607        }
1608    }
1609    else {
1610        $app->validate_magic() or return;
1611    }
1612
1613    my $new_visible;
1614    if ( $app->param('approve') ) {
1615        $new_visible = 1;
1616    }
1617    elsif ( $app->param('unapprove') ) {
1618        $new_visible = 0;
1619    }
1620
1621    my %rebuild_set = ();
1622    require MT::Entry;
1623    foreach my $id (@obj_ids) {
1624        my $obj = $class->load($id)
1625            or next;
1626        my $old_visible = $obj->visible || 0;
1627        if ( $old_visible != $new_visible ) {
1628            if ( $obj->isa('MT::TBPing') ) {
1629                my $obj_parent = $obj->parent();
1630                if ( $obj_parent->isa('MT::Category') ) {
1631                    my $blog = MT::Blog->load( $obj_parent->blog_id );
1632                    next unless $blog;
1633                    $app->publisher->_rebuild_entry_archive_type(
1634                        Entry       => undef,
1635                        Blog        => $blog,
1636                        Category    => $obj_parent,
1637                        ArchiveType => 'Category'
1638                    );
1639                }
1640                else {
1641                    if ( !$author->is_superuser ) {
1642                        if ( !$perms || $perms->blog_id != $obj->blog_id ) {
1643                            $perms = $author->permissions( $obj->blog_id );
1644                        }
1645                        unless ($perms) {
1646                            return $app->errtrans(
1647"You don't have permission to approve this comment."
1648                            );
1649                        }
1650                        unless (
1651                            $perms->can_manage_feedback
1652                            || ( $perms->can_publish_post
1653                                && ( $obj_parent->author_id == $author->id ) )
1654                          )
1655                        {
1656                            return $app->errtrans(
1657"You don't have permission to approve this comment."
1658                            );
1659                        }
1660                    }
1661                    $rebuild_set{ $obj_parent->id } = $obj_parent;
1662                }
1663            }
1664            elsif ( $obj->entry_id ) {
1665
1666                # TODO: Factor out permissions checking
1667                my $entry = MT::Entry->load( $obj->entry_id )
1668                  || return $app->error(
1669                    $app->translate("Comment on missing entry!") );
1670                if ( !$author->is_superuser ) {
1671                    if ( !$perms || $perms->blog_id != $obj->blog_id ) {
1672                        $perms = $author->permissions( $obj->blog_id );
1673                    }
1674                    unless ($perms) {
1675                        return $app->errtrans(
1676                            "You don't have permission to approve this comment."
1677                        );
1678                    }
1679                    unless (
1680                        $perms->can_manage_feedback
1681                        || ( $perms->can_publish_post
1682                            && ( $entry->author_id == $author->id ) )
1683                      )
1684                    {
1685                        return $app->errtrans(
1686                            "You don't have permission to approve this comment."
1687                        );
1688                    }
1689                }
1690                $rebuild_set{ $obj->entry_id } = $entry;
1691            }
1692            $obj->visible($new_visible);
1693            $obj->save();
1694        }
1695    }
1696    my $approved_flag = ( $new_visible ? '' : 'un' ) . 'approved';
1697    $app->add_return_arg( $approved_flag => 1 );
1698    return $app->rebuild_these( \%rebuild_set, how => MT::App::CMS::NEW_PHASE() );
1699}
1700
1701sub map_comment_to_commenter {
1702    my $app = shift;
1703    my ($comments) = @_;
1704    my %commenters;
1705    require MT::Comment;
1706    for my $id (@$comments) {
1707        my $cmt = MT::Comment->load($id);
1708        if ( $cmt && $cmt->commenter_id ) {
1709            $commenters{ $cmt->commenter_id . ':' . $cmt->blog_id } =
1710              [ $cmt->commenter_id, $cmt->blog_id ];
1711        }
1712        else {
1713            $app->add_return_arg( 'unauth', 1 );
1714        }
1715    }
1716    return values %commenters;
1717}
1718
1719sub _prepare_reply {
1720    my $app = shift;
1721    my $q   = $app->param;
1722
1723    my $comment_class = $app->model('comment');
1724    my $parent        = $comment_class->load( $q->param('reply_to') );
1725    my $entry         = $app->model('entry')->load( $parent->entry_id );
1726
1727    if ( !$parent || !$parent->is_published ) {
1728        $app->error(
1729            $app->translate("You can't reply to unpublished comment.") );
1730        return ( undef, $parent, $entry );
1731    }
1732
1733    unless ( $app->validate_magic ) {
1734        $app->error( $app->translate("Invalid request.") );
1735        return ( undef, $parent, $entry );
1736    }
1737
1738    my $nick = $app->user->nickname || $app->translate('Registered User');
1739
1740    my $comment = $comment_class->new;
1741
1742    ## Strip linefeed characters.
1743    my $text = $q->param('text');
1744    $text = '' unless defined $text;
1745    $text =~ tr/\r//d;
1746    $comment->ip( $app->remote_ip );
1747    $comment->commenter_id( $app->user->id );
1748    $comment->blog_id( $entry->blog_id );
1749    $comment->entry_id( $entry->id );
1750    $comment->author( remove_html($nick) );
1751    $comment->email( remove_html( $app->user->email ) );
1752    $comment->text($text);
1753    if (my $url = $app->user->url ) {
1754        $comment->url($url);
1755    }
1756
1757    $comment->visible(1);    # leave as undefined
1758    $comment->is_junk(0);
1759
1760    # strip of any null characters (done after junk checks so they can
1761    # monitor for that kind of activity)
1762    for my $field (qw(author email text)) {
1763        my $val = $comment->column($field);
1764        if ( $val =~ m/\x00/ ) {
1765            $val =~ tr/\x00//d;
1766            $comment->column( $field, $val );
1767        }
1768    }
1769
1770    ( $comment, $parent, $entry );
1771}
1772
1773sub build_comment_table {
1774    my $app = shift;
1775    my (%args) = @_;
1776
1777    my $author    = $app->user;
1778    my $class     = $app->model('comment');
1779    my $list_pref = $app->list_pref('comment');
1780    my $entry_pkg = $app->model('entry');
1781    my $iter;
1782    if ( $args{load_args} ) {
1783        $iter = $class->load_iter( @{ $args{load_args} } );
1784    }
1785    elsif ( $args{iter} ) {
1786        $iter = $args{iter};
1787    }
1788    elsif ( $args{items} ) {
1789        $iter = sub { pop @{ $args{items} } };
1790    }
1791    return [] unless $iter;
1792    my $limit = $args{limit};
1793    my $param = $args{param} || {};
1794
1795    my @data;
1796    my $i;
1797    $i = 1;
1798    my ( %blogs, %entries, %perms, %cmntrs );
1799    my $trim_length =
1800      $app->config('ShowIPInformation')
1801      ? const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT')
1802      : const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_LONG');
1803    my $author_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_AUTHOR');
1804    my $comment_short_len =
1805      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_SHORT');
1806    my $comment_long_len =
1807      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_LONG');
1808    my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE');
1809
1810    while ( my $obj = $iter->() ) {
1811        my $row = $obj->column_values;
1812        $row->{author_display} = $row->{author};
1813        $row->{author_display} =
1814          substr_text( $row->{author_display}, 0, $author_max_len ) . '...'
1815          if $row->{author_display}
1816          && length_text( $row->{author_display} ) > $author_max_len;
1817        $row->{comment_short} =
1818          ( substr_text( $obj->text(), 0, $trim_length )
1819              . ( length_text( $obj->text ) > $trim_length ? "..." : "" ) );
1820        $row->{comment_short} =
1821          break_up_text( $row->{comment_short}, $comment_short_len )
1822          ;    # break up really long strings
1823        $row->{comment_long} = remove_html( $obj->text );
1824        $row->{comment_long} =
1825          break_up_text( $row->{comment_long}, $comment_long_len )
1826          ;    # break up really long strings
1827
1828        $row->{visible}  = $obj->visible();
1829        $row->{entry_id} = $obj->entry_id();
1830        my $blog = $blogs{ $obj->blog_id } ||= $obj->blog;
1831        my $entry = $entries{ $obj->entry_id } ||=
1832          $entry_pkg->load( $obj->entry_id );
1833        unless ($entry) {
1834            $entry = $entry_pkg->new;
1835            $entry->title( '* ' . $app->translate('Orphaned comment') . ' *' );
1836        }
1837        $row->{entry_class} = $entry->class;
1838        $row->{entry_class_label} = $entry->class_label;
1839        $row->{entry_title} = (
1840              defined( $entry->title ) ? $entry->title
1841            : defined( $entry->text )  ? $entry->text
1842            : ''
1843        );
1844        $row->{entry_title} = $app->translate('(untitled)')
1845          if $row->{entry_title} eq '';
1846        $row->{entry_title} =
1847          substr_text( $row->{entry_title}, 0, $title_max_len ) . '...'
1848          if $row->{entry_title}
1849          && length_text( $row->{entry_title} ) > $title_max_len;
1850        $row->{commenter_id} = $obj->commenter_id() if $obj->commenter_id();
1851        my $cmntr;
1852        if ( $obj->commenter_id ) {
1853            $cmntr = $cmntrs{ $obj->commenter_id } ||= MT::Author->load(
1854                {
1855                    id   => $obj->commenter_id(),
1856                }
1857            );
1858        }
1859        if ($cmntr) {
1860            $row->{email_hidden} = $cmntr && $cmntr->is_email_hidden();
1861            $row->{auth_icon_url} = $cmntr->auth_icon_url;
1862
1863            my $status = $cmntr->commenter_status( $obj->blog_id );
1864            $row->{commenter_approved} =
1865              ( $cmntr->commenter_status( $obj->blog_id ) ==
1866                  MT::Author::APPROVED() );
1867            $row->{commenter_banned} =
1868              ( $cmntr->commenter_status( $obj->blog_id ) ==
1869                  MT::Author::BANNED() );
1870        }
1871        if ( my $ts = $obj->created_on ) {
1872            $row->{created_on_time_formatted} =
1873              format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
1874            $row->{created_on_formatted} =
1875              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
1876
1877            $row->{created_on_relative} = relative_date( $ts, time, $blog );
1878        }
1879        if ( $author->is_superuser() ) {
1880            $row->{has_edit_access} = 1;
1881            $row->{has_bulk_access} = 1;
1882        }
1883        else {
1884            my $perms = $perms{ $obj->blog_id } ||=
1885              $author->permissions( $obj->blog_id );
1886            $row->{has_bulk_access} = (
1887                $perms && ( $perms->can_edit_all_posts
1888                    || $perms->can_manage_feedback )
1889                  || ( ( $perms->can_publish_post )
1890                    && ( $author->id == $entry->author_id ) )
1891            );
1892            $row->{has_edit_access} = (
1893                $perms && ( $perms->can_edit_all_posts
1894                    || $perms->can_manage_feedback )
1895                  || ( ( $perms->can_create_post )
1896                    && ( $author->id == $entry->author_id ) )
1897            );
1898        }
1899        if ($blog) {
1900            $row->{weblog_id}   = $blog->id;
1901            $row->{weblog_name} = $blog->name;
1902        }
1903        else {
1904            $row->{weblog_name} =
1905              '* ' . $app->translate('Orphaned comment') . ' *';
1906        }
1907        $row->{reply_count} = $class->count( { parent_id => $obj->id } );
1908        $row->{object} = $obj;
1909        push @data, $row;
1910        last if $limit and @data > $limit;
1911    }
1912    return [] unless @data;
1913
1914    my $junk_tab = ( $app->param('tab') || '' ) eq 'junk';
1915    $param->{comment_table}[0]              = {%$list_pref};
1916    $param->{comment_table}[0]{object_loop} = \@data;
1917    $param->{comment_table}[0]{object_type} = 'comment';
1918    $param->{object_loop} = $param->{comment_table}[0]{object_loop}
1919      unless exists $param->{object_loop};
1920
1921    $app->load_list_actions( 'comment', \%$param );
1922    \@data;
1923}
1924
19251;
Note: See TracBrowser for help on using the browser.