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

Revision 2638, 64.5 kB (checked in by takayama, 17 months ago)

Fixed typo..

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