root/branches/release-33/lib/MT/CMS/Comment.pm @ 1710

Revision 1710, 63.2 kB (checked in by fumiakiy, 20 months ago)

Comment reply should not have assumed the existence of template modules even if it was one of default templates. BugId:67976

  • 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{junk_status} = 1;
276            }
277            elsif ( $val eq 'pending' ) {
278                $terms{junk_status} = 0;
279            }
280            elsif ( $val eq 'junk' ) {
281                $terms{junk_status} = -1;
282            }
283            else {
284                $terms{junk_status} = [ 0, 1 ];
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( \@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( \@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( \@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( \@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    $arg->{junk_status} = -1;
937    $arg->{blog_id} = $blog_id if $blog_id;
938    $class->remove($arg);
939    $app->add_return_arg( 'emptied' => 1 );
940    $app->call_return;
941}
942
943sub handle_junk {
944    my $app   = shift;
945    my @ids   = $app->param("id");
946    my $type  = $app->param("_type");
947    my $class = $app->model($type);
948    my @item_loop;
949    my $i       = 0;
950    my $blog_id = $app->param('blog_id');
951    my ( %rebuild_entries, %rebuild_categories );
952
953    my @obj_ids = $app->param('id');
954    if ( my $req_nonce = $app->param('nonce') ) {
955        if ( scalar @obj_ids == 1 ) {
956            my $cmt_id = $obj_ids[0];
957            if ( my $obj = $class->load($cmt_id) ) {
958                my $nonce =
959                  MT::Util::perl_sha1_digest_hex( $obj->id
960                      . $obj->created_on
961                      . $obj->blog_id
962                      . $app->config->SecretToken );
963                return $app->errtrans("Invalid request.")
964                  unless $nonce eq $req_nonce;
965                my $return_args = $app->uri_params(
966                    mode => 'view',
967                    args => {
968                        '_type' => $type,
969                        id      => $cmt_id,
970                        blog_id => $obj->blog_id
971                    }
972                );
973                $return_args =~ s!^\?!!;
974                $app->return_args($return_args);
975            }
976            else {
977                return $app->errtrans("Invalid request.");
978            }
979        }
980        else {
981            return $app->errtrans("Invalid request.");
982        }
983    }
984    else {
985        $app->validate_magic() or return;
986    }
987
988    my $perms = $app->permissions;
989    my $perm_checked = ( $app->user->is_superuser()
990      || (
991        $app->param('blog_id')
992        && (   $perms->can_manage_feedback
993            || $perms->can_edit_all_posts )
994      ) ) ? 1 : 0;
995
996    foreach my $id (@ids) {
997        next unless $id;
998
999        my $obj = $class->load($id) or die "No $class $id";
1000        my $old_visible = $obj->visible || 0;
1001        unless ($perm_checked) {
1002            if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) {
1003                next if $obj->parent->author_id != $app->user->id;
1004            }
1005            elsif ( $obj->isa('MT::Comment') ) {
1006                next if $obj->entry->author_id != $app->user->id;
1007            }
1008            next unless $perms->can_publish_post;
1009        }
1010        $obj->junk;
1011        $app->run_callbacks( 'handle_spam', $app, $obj )
1012          ;            # mv this into blk below?
1013        $obj->save;    # (so that each cb doesn't have to save indiv'ly)
1014        next if $old_visible == $obj->visible;
1015        if ( $obj->isa('MT::TBPing') ) {
1016            my ( $parent_type, $parent_id ) = $obj->parent_id();
1017            if ( $parent_type eq 'MT::Entry' ) {
1018                $rebuild_entries{$parent_id} = 1;
1019            }
1020            else {
1021                $rebuild_categories{ $obj->category_id } = 1;
1022
1023                # TODO: do something with this list.
1024            }
1025        }
1026        else {
1027            $rebuild_entries{ $obj->entry_id } = 1;
1028        }
1029    }
1030    $app->add_return_arg( 'junked' => 1 );
1031    if (%rebuild_entries) {
1032        $app->rebuild_these( \%rebuild_entries, how => MT::App::CMS::NEW_PHASE() );
1033    }
1034    else {
1035        $app->call_return;
1036    }
1037}
1038
1039sub not_junk {
1040    my $app = shift;
1041
1042    my $perms = $app->permissions;
1043
1044    my @ids = $app->param("id");
1045    my @item_loop;
1046    my $i     = 0;
1047    my $type  = $app->param('_type');
1048    my $class = $app->model($type);
1049    my %rebuild_set;
1050
1051    my $perm_checked = ( $app->user->is_superuser()
1052      || (
1053        $app->param('blog_id')
1054        && (   $perms->can_manage_feedback
1055            || $perms->can_edit_all_posts )
1056      ) ) ? 1 : 0;
1057
1058    foreach my $id (@ids) {
1059        next unless $id;
1060        my $obj = $class->load($id);
1061        unless ($perm_checked) {
1062            if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) {
1063                next if $obj->parent->author_id != $app->user->id;
1064            }
1065            elsif ( $obj->isa('MT::Comment') ) {
1066                next if $obj->entry->author_id != $app->user->id;
1067            }
1068            next unless $perms->can_publish_post;
1069        }
1070        $obj->approve;
1071        $app->run_callbacks( 'handle_ham', $app, $obj );
1072        if ( $obj->isa('MT::TBPing') ) {
1073            my ( $parent_type, $parent_id ) = $obj->parent_id();
1074            if ( $parent_type eq 'MT::Entry' ) {
1075                $rebuild_set{$parent_id} = 1;
1076            }
1077            else {
1078            }
1079        }
1080        else {
1081            $rebuild_set{ $obj->entry_id } = 1;
1082        }
1083        $obj->save();
1084    }
1085    $app->param( 'approve', 1 );
1086
1087    $app->add_return_arg( 'unjunked' => 1 );
1088
1089    $app->rebuild_these( \%rebuild_set, how => MT::App::CMS::NEW_PHASE() );
1090}
1091
1092sub cfg_system_feedback {
1093    my $app = shift;
1094    my %param;
1095    return $app->redirect(
1096        $app->uri(
1097            mode => 'cfg_comments',
1098            args => { blog_id => $app->param('blog_id') }
1099        )
1100    ) if $app->param('blog_id');
1101
1102    return $app->errtrans("Permission denied.")
1103      unless $app->user->is_superuser();
1104
1105    my $cfg = $app->config;
1106    $param{nav_config} = 1;
1107    $app->add_breadcrumb( $app->translate('Feedback Settings') );
1108    $param{nav_settings}         = 1;
1109    $param{comment_disable}      = $cfg->AllowComments ? 0 : 1;
1110    $param{ping_disable}         = $cfg->AllowPings ? 0 : 1;
1111    $param{disabled_notify_ping} = $cfg->DisableNotificationPings ? 1 : 0;
1112    $param{system_no_email}      = 1 unless $cfg->EmailAddressMain;
1113    my $send = $cfg->OutboundTrackbackLimit || 'any';
1114
1115    if ( $send =~ m/^(any|off|selected|local)$/ ) {
1116        $param{ "trackback_send_" . $cfg->OutboundTrackbackLimit } = 1;
1117        if ( $send eq 'selected' ) {
1118            my @domains = $cfg->OutboundTrackbackDomains;
1119            my $domains = join "\n", @domains;
1120            $param{trackback_send_domains} = $domains;
1121        }
1122    }
1123    else {
1124        $param{"trackback_send_any"} = 1;
1125    }
1126    $param{saved}        = $app->param('saved');
1127    $param{screen_class} = "settings-screen system-feedback-settings";
1128    $app->load_tmpl( 'cfg_system_feedback.tmpl', \%param );
1129}
1130
1131sub save_cfg_system_feedback {
1132    my $app = shift;
1133    return $app->errtrans("Permission denied.")
1134      unless $app->user->is_superuser();
1135
1136    $app->validate_magic or return;
1137    my $cfg = $app->config;
1138    $cfg->AllowComments( ( $app->param('comment_disable') ? 0 : 1 ), 1 );
1139    $cfg->AllowPings(    ( $app->param('ping_disable')    ? 0 : 1 ), 1 );
1140    $cfg->DisableNotificationPings(
1141        ( $app->param('disable_notify_ping') ? 1 : 0 ), 1 );
1142    my $send = $app->param('trackback_send') || 'any';
1143    if ( $send =~ m/^(any|off|selected|local)$/ ) {
1144        $cfg->OutboundTrackbackLimit( $send, 1 );
1145        if ( $send eq 'selected' ) {
1146            my $domains = $app->param('trackback_send_domains') || '';
1147            $domains =~ s/[\r\n]+/ /gs;
1148            $domains =~ s/\s{2,}/ /gs;
1149            my @domains = split /\s/, $domains;
1150            $cfg->OutboundTrackbackDomains( \@domains, 1 );
1151        }
1152    }
1153
1154    $cfg->save_config();
1155    $app->redirect(
1156        $app->uri(
1157            'mode' => 'cfg_system_feedback',
1158            args   => { saved => 1 }
1159        )
1160    );
1161}
1162
1163sub reply {
1164    my $app = shift;
1165    my $q   = $app->param;
1166
1167    my $reply_to    = encode_html( $q->param('reply_to') );
1168    my $magic_token = encode_html( $q->param('magic_token') );
1169    my $blog_id     = encode_html( $q->param('blog_id') );
1170    my $return_url  = encode_html( $q->param('return_url') );
1171    my $text        = encode_html( $q->param('text') );
1172    my $indicator   = $app->static_path . 'images/indicator.gif';
1173    my $url         = $app->uri;
1174    <<SPINNER;
1175<html><head><style type="text/css">
1176#dialog-indicator {position: relative;top: 200px;
1177background: url($indicator)
1178no-repeat;width: 66px;height: 66px;margin: 0 auto;
1179}</style><script type="text/javascript">
1180function init() { var f = document.getElementById('f'); f.submit(); }
1181window.setTimeout("init()", 1500);
1182</script></head><body>
1183<div align="center"><div id="dialog-indicator"></div>
1184<form id="f" method="post" action="$url">
1185<input type="hidden" name="__mode" value="do_reply" />
1186<input type="hidden" name="reply_to" value="$reply_to" />
1187<input type="hidden" name="magic_token" value="$magic_token" />
1188<input type="hidden" name="blog_id" value="$blog_id" />
1189<input type="hidden" name="return_url" value="$return_url" />
1190<input type="hidden" name="text" value="$text" />
1191</form></div></body></html>
1192SPINNER
1193}
1194
1195sub do_reply {
1196    my $app = shift;
1197    my $q   = $app->param;
1198
1199    my $param = {
1200        reply_to    => $q->param('reply_to'),
1201        magic_token => $q->param('magic_token'),
1202        blog_id     => $q->param('blog_id'),
1203    };
1204
1205    my ( $comment, $parent, $entry ) = _prepare_reply($app);
1206
1207    $param->{commenter_name} = $parent->author;
1208    $param->{entry_title}    = $entry->title;
1209    $param->{comment_created_on} =
1210      format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef );
1211    $param->{comment_text} = $parent->text;
1212
1213    return $app->build_page( 'dialog/comment_reply.tmpl',
1214        { %$param, error => $app->errstr } )
1215      unless $comment;
1216
1217    $comment->parent_id( $param->{reply_to} );
1218    $comment->approve;
1219    return $app->handle_error(
1220        $app->translate( "An error occurred: [_1]", $comment->errstr() ) )
1221      unless $comment->save;
1222
1223    MT::Util::start_background_task(
1224        sub {
1225            $app->rebuild_entry( Entry => $parent->entry_id, BuildDependencies => 1 )
1226              or return $app->publish_error( "Publish failed: [_1]", $app->errstr );
1227            $app->_send_comment_notification( $comment, q(), $entry,
1228                $app->model('blog')->load( $param->{blog_id} ), $app->user );
1229        }
1230    );
1231    return $app->build_page( 'dialog/comment_reply.tmpl',
1232        { closing => 1, return_url => $q->param('return_url') } );
1233}
1234
1235sub reply_preview {
1236    my $app = shift;
1237    my $q   = $app->param;
1238    my $cfg = $app->config;
1239
1240    my $param = {
1241        reply_to    => $q->param('reply_to'),
1242        magic_token => $q->param('magic_token'),
1243        blog_id     => $q->param('blog_id'),
1244    };
1245    my ( $comment, $parent, $entry ) = _prepare_reply($app);
1246
1247    my $blog = $parent->blog
1248            || $app->model('blog')->load($q->param('blog_id'));
1249
1250    require MT::Sanitize;
1251    my $spec = $blog->sanitize_spec
1252            || $app->config->GlobalSanitizeSpec;
1253    $param->{commenter_name} = MT::Sanitize->sanitize($parent->author, $spec);
1254    $param->{entry_title}    = $entry->title;
1255    $param->{comment_created_on} =
1256      format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef );
1257    $param->{comment_text} = MT::Sanitize->sanitize($parent->text, $spec);
1258
1259    return $app->build_page( 'dialog/comment_reply.tmpl',
1260        { %$param, error => $app->errstr } )
1261      unless $comment;
1262
1263    ## Set timestamp as we would usually do in ObjectDriver.
1264    my @ts = MT::Util::offset_time_list( time, $entry->blog_id );
1265    my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1,
1266      @ts[ 3, 2, 1, 0 ];
1267    $comment->created_on($ts);
1268    $comment->commenter_id( $app->user->id );
1269    $param->{'comment'} = $comment;
1270
1271    require MT::Serialize;
1272    my $ser   = MT::Serialize->new( $cfg->Serializer );
1273    my $state = $comment->column_values;
1274    $state->{static} = $q->param('static');
1275    $param->{'comment_state'} = unpack 'H*', $ser->serialize( \$state );
1276    $param->{'comment_is_static'} = 1;
1277    $param->{'entry'} = $entry;
1278    $param->{'current_timestamp'} = $ts;
1279    $param->{'commenter'} = $app->user;
1280    $param->{'blog_id'} = $parent->blog_id;
1281    $param->{'blog'} = $parent->blog;
1282
1283    return $app->build_page( 'dialog/comment_reply.tmpl',
1284        { %$param, text => $q->param('text') } );
1285}
1286
1287sub dialog_post_comment {
1288    my $app = shift;
1289    $app->validate_magic or return;
1290
1291    my $user      = $app->user;
1292    my $parent_id = $app->param('reply_to');
1293
1294    return $app->errtrans('Parent comment id was not specified.')
1295      unless $parent_id;
1296
1297    my $comment_class = $app->model('comment');
1298    my $parent        = $comment_class->load($parent_id)
1299      or return $app->errtrans('Parent comment was not found.');
1300    return $app->errtrans("You can't reply to unapproved comment.")
1301      unless $parent->is_published;
1302
1303    my $perms = $app->{perms};
1304    return unless $perms;
1305
1306    my $entry_class = $app->_load_driver_for('entry');
1307    my $entry       = $entry_class->load( $parent->entry_id );
1308
1309    unless ( $user->is_superuser()
1310        || $perms->can_edit_all_posts
1311        || $perms->can_manage_feedback )
1312    {
1313        return $app->errtrans("Permission denied.")
1314          unless $perms->can_edit_entry( $entry, $user, 1 )
1315          ;    # check for publish_post
1316    }
1317
1318    my $blog = $parent->blog
1319            || $app->model('blog')->load($app->param('blog_id'));
1320
1321    require MT::Sanitize;
1322    my $spec = $blog->sanitize_spec
1323            || $app->config->GlobalSanitizeSpec;
1324    my $param = {
1325        reply_to       => $parent_id,
1326        commenter_name => MT::Sanitize->sanitize($parent->author, $spec),
1327        entry_title    => $entry->title,
1328        comment_created_on =>
1329          format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $parent->created_on, $blog, $app->user ? $app->user->preferred_language : undef ),
1330        comment_text       => MT::Sanitize->sanitize($parent->text, $spec),
1331        comment_script_url => $app->config('CGIPath')
1332          . $app->config('CommentScript'),
1333        return_url => $app->base
1334          . $app->mt_uri . '?'
1335          . $app->param('return_args'),
1336    };
1337
1338    $app->load_tmpl( 'dialog/comment_reply.tmpl', $param );
1339}
1340
1341sub can_view {
1342    my $eh = shift;
1343    my ( $app, $id, $objp ) = @_;
1344    return 0 unless ($id);
1345    my $obj = $objp->force() or return 0;
1346    require MT::Entry;
1347    my $entry = MT::Entry->load( $obj->entry_id )
1348      or return 0;
1349    my $perms = $app->permissions;
1350    if (
1351        !(
1352               $entry->author_id == $app->user->id
1353            || $perms->can_edit_all_posts
1354            || $perms->can_manage_feedback
1355        )
1356      )
1357    {
1358        return 0;
1359    }
1360    1;
1361}
1362
1363sub can_save {
1364    my ( $eh, $app, $id ) = @_;
1365    return 0 unless $id;    # Can't create new comments here
1366    return 1 if $app->user->is_superuser();
1367
1368    my $perms = $app->permissions;
1369    return 1
1370      if $perms
1371      && ( $perms->can_edit_all_posts
1372        || $perms->can_manage_feedback );
1373
1374    my $c = MT::Comment->load($id);
1375    if ( $perms && $perms->can_create_post && $perms->can_publish_post ) {
1376        return $c->entry->author_id == $app->user->id;
1377    }
1378    elsif ( $perms && $perms->can_create_post ) {
1379        return ( $c->entry->author_id == $app->user->id )
1380          && ( ( $c->is_junk && ( 'junk' eq $app->param('status') ) )
1381            || ( $c->is_moderated && ( 'moderate' eq $app->param('status') ) )
1382            || ( $c->is_published && ( 'publish' eq $app->param('status') ) ) );
1383    }
1384    elsif ( $perms && $perms->can_publish_post ) {
1385        return 0 unless $c->entry->author_id == $app->user->id;
1386        return 0
1387          unless ( $c->text eq $app->param('text') )
1388          && ( $c->author eq $app->param('author') )
1389          && ( $c->email  eq $app->param('email') )
1390          && ( $c->url    eq $app->param('url') );
1391    }
1392    else {
1393        return 0;
1394    }
1395}
1396
1397sub can_delete {
1398    my ( $eh, $app, $obj ) = @_;
1399    my $author = $app->user;
1400    return 1 if $author->is_superuser();
1401    my $perms = $app->permissions;
1402    require MT::Entry;
1403    my $entry = MT::Entry->load( $obj->entry_id );
1404    if ( !$perms || $perms->blog_id != $entry->blog_id ) {
1405        $perms ||= $author->permissions( $entry->blog_id );
1406    }
1407
1408    # publish_post allows entry author to delete comment.
1409    return 1
1410      if $perms->can_edit_all_posts
1411      || $perms->can_manage_feedback
1412      || $perms->can_edit_entry( $entry, $author, 1 );
1413    return 0 if $obj->visible;    # otherwise, visible comment can't be deleted.
1414    return $perms && $perms->can_edit_entry( $entry, $author );
1415}
1416
1417sub pre_save {
1418    my $eh = shift;
1419    my ( $app, $obj, $original ) = @_;
1420    my $perms = $app->permissions;
1421    return 1
1422      unless $perms->can_publish_post
1423      || $perms->can_edit_all_posts
1424      || $perms->can_manage_feedback;
1425
1426    unless ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) {
1427        return 1 unless $perms->can_publish_post;
1428        require MT::Entry;
1429        my $entry = MT::Entry->load( $obj->entry_id )
1430          or return 1;
1431        return 1 unless $entry->author_id == $app->user->id;
1432    }
1433
1434    my $status = $app->param('status');
1435    if ( $status eq 'publish' ) {
1436        $obj->approve;
1437        if ( $original->junk_status != $obj->junk_status ) {
1438            $app->run_callbacks( 'handle_ham', $app, $obj );
1439        }
1440    }
1441    elsif ( $status eq 'moderate' ) {
1442        $obj->moderate;
1443        $obj->junk_status(0);
1444    }
1445    elsif ( $status eq 'junk' ) {
1446        $obj->junk;
1447        if ( $original->junk_status != $obj->junk_status ) {
1448            $app->run_callbacks( 'handle_spam', $app, $obj );
1449        }
1450    }
1451    return 1;
1452}
1453
1454sub post_save {
1455    my $eh = shift;
1456    my ( $app, $obj, $original ) = @_;
1457    if ( $obj->visible
1458        || ( ( $obj->visible || 0 ) != ( $original->visible || 0 ) ) )
1459    {
1460        $app->rebuild_entry( Entry => $obj->entry_id, BuildIndexes => 1 )
1461            or return $app->publish_error();
1462    }
1463    1;
1464}
1465
1466sub post_delete {
1467    my ( $eh, $app, $obj ) = @_;
1468
1469    require MT::Entry;
1470    my $title = '';
1471    if ( my $entry = MT::Entry->load( $obj->entry_id ) ) {
1472        $title = $entry->title;
1473    }
1474
1475    $app->log(
1476        {
1477            message => $app->translate(
1478"Comment (ID:[_1]) by '[_2]' deleted by '[_3]' from entry '[_4]'",
1479                $obj->id, $obj->author, $app->user->name, $title
1480            ),
1481            level    => MT::Log::INFO(),
1482            class    => 'system',
1483            category => 'delete'
1484        }
1485    );
1486}
1487
1488sub can_view_commenter {
1489    my $eh = shift;
1490    my ( $app, $id ) = @_;
1491    my $auth = MT::Author->load(
1492        {
1493            id   => $id,
1494            type => MT::Author::COMMENTER()
1495        }
1496    );
1497    $auth ? 1 : 0;
1498}
1499
1500sub can_delete_commenter {
1501    my ( $eh, $app, $obj ) = @_;
1502    my $author = $app->user;
1503    return 1 if $author->is_superuser();
1504    my $perms = $author->permissions( $obj->blog_id );
1505    ( $perms && $perms->can_administer_blog );
1506}
1507
1508sub build_junk_table {
1509    my $app = shift;
1510    my (%args) = @_;
1511
1512    my $param = $args{param};
1513    my $obj   = $args{object};
1514
1515    if ( defined $obj->junk_score ) {
1516        $param->{junk_score} =
1517          ( $obj->junk_score > 0 ? '+' : '' ) . $obj->junk_score;
1518    }
1519    my $log = $obj->junk_log || '';
1520    my @log = split /\r?\n/, $log;
1521    my @junk;
1522    for ( my $i = 0 ; $i < scalar(@log) ; $i++ ) {
1523        my $line = $log[$i];
1524        $line =~ s/(^\s+|\s+$)//g;
1525        next unless $line;
1526        last if $line =~ m/^--->/;
1527        my ( $test, $score, $log );
1528        ($test) = $line =~ m/^([^:]+?):/;
1529        if ( defined $test ) {
1530            ($score) = $test =~ m/\(([+-]?\d+?(?:\.\d*?)?)\)/;
1531            $test =~ s/\(.+\)//;
1532        }
1533        if ( defined $score ) {
1534            $score =~ s/\+//;
1535            $score .= '.0' unless $score =~ m/\./;
1536            $score = ( $score > 0 ? '+' : '' ) . $score;
1537        }
1538        $log = $line;
1539        $log =~ s/^[^:]+:\s*//;
1540        $log = encode_html($log);
1541        for ( my $j = $i + 1 ; $j < scalar(@log) ; $j++ ) {
1542            my $line = encode_html( $log[$j] );
1543            if ( $line =~ m/^\t+(.*)$/s ) {
1544                $i = $j;
1545                $log .= "<br />" . $1;
1546            }
1547            else {
1548                last;
1549            }
1550        }
1551        push @junk, { test => $test, score => $score, log => $log };
1552    }
1553    $param->{junk_log_loop} = \@junk;
1554    \@junk;
1555}
1556
1557sub set_item_visible {
1558    my $app    = shift;
1559    my $perms  = $app->permissions;
1560    my $author = $app->user;
1561
1562    my $type    = $app->param('_type');
1563    my $class   = $app->model($type);
1564    my @obj_ids = $app->param('id');
1565
1566    if ( my $req_nonce = $app->param('nonce') ) {
1567        if ( scalar @obj_ids == 1 ) {
1568            my $cmt_id = $obj_ids[0];
1569            if ( my $obj = $class->load($cmt_id) ) {
1570                my $nonce =
1571                  MT::Util::perl_sha1_digest_hex( $obj->id
1572                      . $obj->created_on
1573                      . $obj->blog_id
1574                      . $app->config->SecretToken );
1575                return $app->errtrans("Invalid request.")
1576                  unless $nonce eq $req_nonce;
1577                my $return_args = $app->uri_params(
1578                    mode => 'view',
1579                    args => {
1580                        '_type' => $type,
1581                        id      => $cmt_id,
1582                        blog_id => $obj->blog_id
1583                    }
1584                );
1585                $return_args =~ s!^\?!!;
1586                $app->return_args($return_args);
1587            }
1588            else {
1589                return $app->errtrans("Invalid request.");
1590            }
1591        }
1592        else {
1593            return $app->errtrans("Invalid request.");
1594        }
1595    }
1596    else {
1597        $app->validate_magic() or return;
1598    }
1599
1600    my $new_visible;
1601    if ( $app->param('approve') ) {
1602        $new_visible = 1;
1603    }
1604    elsif ( $app->param('unapprove') ) {
1605        $new_visible = 0;
1606    }
1607
1608    my %rebuild_set = ();
1609    require MT::Entry;
1610    foreach my $id (@obj_ids) {
1611        my $obj = $class->load($id);
1612        my $old_visible = $obj->visible || 0;
1613        if ( $old_visible != $new_visible ) {
1614            if ( $obj->isa('MT::TBPing') ) {
1615                my $obj_parent = $obj->parent();
1616                if ( $obj_parent->isa('MT::Category') ) {
1617                    my $blog = MT::Blog->load( $obj_parent->blog_id );
1618                    next unless $blog;
1619                    $app->publisher->_rebuild_entry_archive_type(
1620                        Entry       => undef,
1621                        Blog        => $blog,
1622                        Category    => $obj_parent,
1623                        ArchiveType => 'Category'
1624                    );
1625                }
1626                else {
1627                    if ( !$author->is_superuser ) {
1628                        if ( !$perms || $perms->blog_id != $obj->blog_id ) {
1629                            $perms = $author->permissions( $obj->blog_id );
1630                        }
1631                        unless ($perms) {
1632                            return $app->errtrans(
1633"You don't have permission to approve this comment."
1634                            );
1635                        }
1636                        unless (
1637                            $perms->can_manage_feedback
1638                            || ( $perms->can_publish_post
1639                                && ( $obj_parent->author_id == $author->id ) )
1640                          )
1641                        {
1642                            return $app->errtrans(
1643"You don't have permission to approve this comment."
1644                            );
1645                        }
1646                    }
1647                    $rebuild_set{ $obj_parent->id } = $obj_parent;
1648                }
1649            }
1650            elsif ( $obj->entry_id ) {
1651
1652                # TODO: Factor out permissions checking
1653                my $entry = MT::Entry->load( $obj->entry_id )
1654                  || return $app->error(
1655                    $app->translate("Comment on missing entry!") );
1656                if ( !$author->is_superuser ) {
1657                    if ( !$perms || $perms->blog_id != $obj->blog_id ) {
1658                        $perms = $author->permissions( $obj->blog_id );
1659                    }
1660                    unless ($perms) {
1661                        return $app->errtrans(
1662                            "You don't have permission to approve this comment."
1663                        );
1664                    }
1665                    unless (
1666                        $perms->can_manage_feedback
1667                        || ( $perms->can_publish_post
1668                            && ( $entry->author_id == $author->id ) )
1669                      )
1670                    {
1671                        return $app->errtrans(
1672                            "You don't have permission to approve this comment."
1673                        );
1674                    }
1675                }
1676                $rebuild_set{ $obj->entry_id } = $entry;
1677            }
1678            $obj->visible($new_visible);
1679            $obj->save();
1680        }
1681    }
1682    my $approved_flag = ( $new_visible ? '' : 'un' ) . 'approved';
1683    $app->add_return_arg( $approved_flag => 1 );
1684    return $app->rebuild_these( \%rebuild_set, how => MT::App::CMS::NEW_PHASE() );
1685}
1686
1687sub map_comment_to_commenter {
1688    my $app = shift;
1689    my ($comments) = @_;
1690    my %commenters;
1691    require MT::Comment;
1692    for my $id (@$comments) {
1693        my $cmt = MT::Comment->load($id);
1694        if ( $cmt->commenter_id ) {
1695            $commenters{ $cmt->commenter_id . ':' . $cmt->blog_id } =
1696              [ $cmt->commenter_id, $cmt->blog_id ];
1697        }
1698        else {
1699            $app->add_return_arg( 'unauth', 1 );
1700        }
1701    }
1702    return values %commenters;
1703}
1704
1705sub _prepare_reply {
1706    my $app = shift;
1707    my $q   = $app->param;
1708
1709    my $comment_class = $app->model('comment');
1710    my $parent        = $comment_class->load( $q->param('reply_to') );
1711    my $entry         = $app->model('entry')->load( $parent->entry_id );
1712
1713    unless ( $parent->is_published ) {
1714        $app->error(
1715            $app->translate("You can't reply to unpublished comment.") );
1716        return ( undef, $parent, $entry );
1717    }
1718
1719    unless ( $app->validate_magic ) {
1720        $app->error( $app->translate("Invalid request.") );
1721        return ( undef, $parent, $entry );
1722    }
1723
1724    my $blog = $app->model('blog')->load( $entry->blog_id );
1725
1726    my $nick = $app->user->nickname || $app->translate('Registered User');
1727
1728    my $comment = $comment_class->new;
1729
1730    ## Strip linefeed characters.
1731    my $text = $q->param('text');
1732    $text = '' unless defined $text;
1733    $text =~ tr/\r//d;
1734    $comment->ip( $app->remote_ip );
1735    $comment->commenter_id( $app->user->id );
1736    $comment->blog_id( $entry->blog_id );
1737    $comment->entry_id( $entry->id );
1738    $comment->author( remove_html($nick) );
1739    $comment->email( remove_html( $app->user->email ) );
1740    $comment->text($text);
1741    if (my $url = $app->user->url ) {
1742        $comment->url($url);
1743    }
1744
1745    $comment->visible(1);    # leave as undefined
1746    $comment->is_junk(0);
1747
1748    # strip of any null characters (done after junk checks so they can
1749    # monitor for that kind of activity)
1750    for my $field (qw(author email text)) {
1751        my $val = $comment->column($field);
1752        if ( $val =~ m/\x00/ ) {
1753            $val =~ tr/\x00//d;
1754            $comment->column( $field, $val );
1755        }
1756    }
1757
1758    ( $comment, $parent, $entry );
1759}
1760
1761sub build_comment_table {
1762    my $app = shift;
1763    my (%args) = @_;
1764
1765    my $author    = $app->user;
1766    my $class     = $app->model('comment');
1767    my $list_pref = $app->list_pref('comment');
1768    my $entry_pkg = $app->model('entry');
1769    my $iter;
1770    if ( $args{load_args} ) {
1771        $iter = $class->load_iter( @{ $args{load_args} } );
1772    }
1773    elsif ( $args{iter} ) {
1774        $iter = $args{iter};
1775    }
1776    elsif ( $args{items} ) {
1777        $iter = sub { pop @{ $args{items} } };
1778    }
1779    return [] unless $iter;
1780    my $limit = $args{limit};
1781    my $param = $args{param} || {};
1782
1783    my @data;
1784    my $i;
1785    $i = 1;
1786    my ( %blogs, %entries, %perms, %cmntrs );
1787    my $trim_length =
1788      $app->config('ShowIPInformation')
1789      ? const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT')
1790      : const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_LONG');
1791    my $author_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_AUTHOR');
1792    my $comment_short_len =
1793      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_SHORT');
1794    my $comment_long_len =
1795      const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_LONG');
1796    my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE');
1797
1798    while ( my $obj = $iter->() ) {
1799        my $row = $obj->column_values;
1800        $row->{author_display} = $row->{author};
1801        $row->{author_display} =
1802          substr_text( $row->{author_display}, 0, $author_max_len ) . '...'
1803          if $row->{author_display}
1804          && length_text( $row->{author_display} ) > $author_max_len;
1805        $row->{comment_short} =
1806          ( substr_text( $obj->text(), 0, $trim_length )
1807              . ( length_text( $obj->text ) > $trim_length ? "..." : "" ) );
1808        $row->{comment_short} =
1809          break_up_text( $row->{comment_short}, $comment_short_len )
1810          ;    # break up really long strings
1811        $row->{comment_long} = remove_html( $obj->text );
1812        $row->{comment_long} =
1813          break_up_text( $row->{comment_long}, $comment_long_len )
1814          ;    # break up really long strings
1815
1816        $row->{visible}  = $obj->visible();
1817        $row->{entry_id} = $obj->entry_id();
1818        my $blog = $blogs{ $obj->blog_id } ||= $obj->blog;
1819        my $entry = $entries{ $obj->entry_id } ||=
1820          $entry_pkg->load( $obj->entry_id );
1821        unless ($entry) {
1822            $entry = $entry_pkg->new;
1823            $entry->title( '* ' . $app->translate('Orphaned comment') . ' *' );
1824        }
1825        $row->{entry_class} = $entry->class;
1826        $row->{entry_class_label} = $entry->class_label;
1827        $row->{entry_title} = (
1828              defined( $entry->title ) ? $entry->title
1829            : defined( $entry->text )  ? $entry->text
1830            : ''
1831        );
1832        $row->{entry_title} = $app->translate('(untitled)')
1833          if $row->{entry_title} eq '';
1834        $row->{entry_title} =
1835          substr_text( $row->{entry_title}, 0, $title_max_len ) . '...'
1836          if $row->{entry_title}
1837          && length_text( $row->{entry_title} ) > $title_max_len;
1838        $row->{commenter_id} = $obj->commenter_id() if $obj->commenter_id();
1839        my $cmntr;
1840        if ( $obj->commenter_id ) {
1841            $cmntr = $cmntrs{ $obj->commenter_id } ||= MT::Author->load(
1842                {
1843                    id   => $obj->commenter_id(),
1844                }
1845            );
1846        }
1847        if ($cmntr) {
1848            $row->{email_hidden} = $cmntr && $cmntr->is_email_hidden();
1849            $row->{auth_icon_url} = $cmntr->auth_icon_url;
1850
1851            my $status = $cmntr->commenter_status( $obj->blog_id );
1852            $row->{commenter_approved} =
1853              ( $cmntr->commenter_status( $obj->blog_id ) ==
1854                  MT::Author::APPROVED() );
1855            $row->{commenter_banned} =
1856              ( $cmntr->commenter_status( $obj->blog_id ) ==
1857                  MT::Author::BANNED() );
1858        }
1859        if ( my $ts = $obj->created_on ) {
1860            $row->{created_on_time_formatted} =
1861              format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
1862            $row->{created_on_formatted} =
1863              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
1864
1865            $row->{created_on_relative} = relative_date( $ts, time, $blog );
1866        }
1867        if ( $author->is_superuser() ) {
1868            $row->{has_edit_access} = 1;
1869            $row->{has_bulk_access} = 1;
1870        }
1871        else {
1872            my $perms = $perms{ $obj->blog_id } ||=
1873              $author->permissions( $obj->blog_id );
1874            $row->{has_bulk_access} = (
1875                $perms && ( $perms->can_edit_all_posts
1876                    || $perms->can_manage_feedback )
1877                  || ( ( $perms->can_publish_post )
1878                    && ( $author->id == $entry->author_id ) )
1879            );
1880            $row->{has_edit_access} = (
1881                $perms && ( $perms->can_edit_all_posts
1882                    || $perms->can_manage_feedback )
1883                  || ( ( $perms->can_create_post )
1884                    && ( $author->id == $entry->author_id ) )
1885            );
1886        }
1887        if ($blog) {
1888            $row->{weblog_id}   = $blog->id;
1889            $row->{weblog_name} = $blog->name;
1890        }
1891        else {
1892            $row->{weblog_name} =
1893              '* ' . $app->translate('Orphaned comment') . ' *';
1894        }
1895        $row->{reply_count} = $class->count( { parent_id => $obj->id } );
1896        $row->{object} = $obj;
1897        push @data, $row;
1898        last if $limit and @data > $limit;
1899    }
1900    return [] unless @data;
1901
1902    my $junk_tab = ( $app->param('tab') || '' ) eq 'junk';
1903    $param->{comment_table}[0]              = {%$list_pref};
1904    $param->{comment_table}[0]{object_loop} = \@data;
1905    $param->{comment_table}[0]{object_type} = 'comment';
1906    $param->{object_loop} = $param->{comment_table}[0]{object_loop}
1907      unless exists $param->{object_loop};
1908
1909    $app->load_list_actions( 'comment', \%$param );
1910    \@data;
1911}
1912
19131;
Note: See TracBrowser for help on using the browser.