root/branches/release-36/lib/MT/Comment.pm @ 2057

Revision 2057, 10.8 kB (checked in by bchoate, 19 months ago)

Adding meta support for comments/pings and creating a new index to aid selecting by parent object and visible status. Also, comment author method now returns MT::Author nickname when comment has an associated author. BugId:79475

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Comment;
8
9use strict;
10use base qw( MT::Object MT::Scorable );
11use MT::Util qw( weaken );
12
13sub JUNK ()     { -1 }
14sub NOT_JUNK () { 1 }
15
16__PACKAGE__->install_properties({
17    column_defs => {
18        'id' => 'integer not null auto_increment',
19        'blog_id' => 'integer not null',
20        'entry_id' => 'integer not null',
21        'author' => 'string(100)',
22        'commenter_id' => 'integer',
23        'visible' => 'boolean',
24        'junk_status' => 'smallint',
25        'email' => 'string(75)',
26        'url' => 'string(255)',
27        'text' => 'text',
28        'ip' => 'string(16)',
29        'last_moved_on' => 'datetime not null',
30        'junk_score' => 'float',
31        'junk_log' => 'text',
32        'parent_id' => 'integer',
33    },
34    indexes => {
35        ip => 1,
36        created_on => 1,
37        entry_visible => {
38            columns => [ 'entry_id', 'visible' ],
39        },
40        email => 1,
41        commenter_id => 1,
42        parent_id => 1,
43        last_moved_on => 1, # used for junk expiration
44        # For URL lookups to aid spam filtering
45        blog_url => {
46            columns => [ 'blog_id', 'visible', 'url' ],
47        },
48        blog_stat => {
49            columns => [ 'blog_id', 'junk_status', 'created_on' ],
50        },
51        blog_visible => {
52            columns => [ 'blog_id', 'visible', 'created_on', 'id' ],
53        },
54        visible_date => {
55            columns => [ 'visible', 'created_on' ],
56        },
57        junk_date => {
58            columns => [ 'junk_status', 'created_on' ],
59        },
60    },
61    meta => 1,
62    defaults => {
63        junk_status => NOT_JUNK,
64        last_moved_on => '20000101000000',
65    },
66    audit => 1,
67    datasource => 'comment',
68    primary_key => 'id',
69});
70
71my %blocklists = ();
72
73sub class_label {
74    return MT->translate("Comment");
75}
76
77sub class_label_plural {
78    return MT->translate("Comments");
79}
80
81sub is_junk {
82    $_[0]->junk_status == JUNK;
83}
84
85sub is_not_junk {
86    $_[0]->junk_status != JUNK;
87}
88
89sub is_not_blocked { 
90    my ($eh, $cmt) = @_;
91   
92    my($host, @hosts);
93    # other URI schemes?
94    require MT::Util;
95    @hosts = MT::Util::extract_urls($cmt->text);
96   
97    my $not_blocked = 1;
98    my $blog_id = $cmt->blog_id;
99    if (!$blocklists{$blog_id}) {
100        require MT::Blocklist;
101        my @blocks = MT::Blocklist->load( { blog_id => $blog_id } );
102        $blocklists{$blog_id} = [ @blocks ];
103    }
104    if (@{$blocklists{$blog_id}}) {
105        for my $h (@hosts) {
106            for my $b (@{$blocklists{$blog_id}}) {
107                $not_blocked = 0 if ($h eq $b->text);
108            }
109        }
110    }
111    $not_blocked;
112}
113
114sub next {
115    my $comment = shift;
116    my($publish_only) = @_;
117    $publish_only = $publish_only ? {'visible' => 1} : {};
118    $comment->_nextprev('next', $publish_only);
119}
120
121sub previous {
122    my $comment = shift;
123    my($publish_only) = @_;
124    $publish_only = $publish_only ? {'visible' => 1} : {};
125    $comment->_nextprev('previous', $publish_only);
126}
127
128sub _nextprev {
129    my $obj = shift;
130    my $class = ref($obj);
131    my ($direction, $terms) = @_;
132    return undef unless ($direction eq 'next' || $direction eq 'previous');
133    my $next = $direction eq 'next';
134
135    my $label = '__' . $direction;
136    return $obj->{$label} if $obj->{$label};
137
138    my $o = $obj->nextprev(
139        direction => $direction,
140        terms     => { blog_id => $obj->blog_id, %$terms },
141        by        => 'created_on',
142    );
143    weaken($o->{$label} = $o) if $o;
144    return $o;
145}
146
147sub entry {
148    my ($comment) = @_;
149    my $entry = $comment->{__entry};
150    unless ($entry) {
151        my $entry_id = $comment->entry_id;
152        return undef unless $entry_id;
153        require MT::Entry;
154        $entry = MT::Entry->load($entry_id) or
155            return $comment->error(MT->translate(
156            "Load of entry '[_1]' failed: [_2]", $entry_id, MT::Entry->errstr));
157        $comment->{__entry} = $entry;
158    }
159    return $entry;
160}
161
162sub blog {
163    my ($comment) = @_;
164    my $blog = $comment->{__blog};
165    unless ($blog) {
166        my $blog_id = $comment->blog_id;
167        require MT::Blog;
168        $blog = MT::Blog->load($blog_id) or
169            return $comment->error(MT->translate(
170            "Load of blog '[_1]' failed: [_2]", $blog_id, MT::Blog->errstr));
171        $comment->{__blog} = $blog;
172    }
173    return $blog;
174}
175
176sub junk {
177    my ($comment) = @_;
178    if (($comment->junk_status || 0) != JUNK) {
179        require MT::Util;
180        my @ts = MT::Util::offset_time_list(time, $comment->blog_id);
181        my $ts = sprintf("%04d%02d%02d%02d%02d%02d",
182                         $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0]);
183        $comment->last_moved_on($ts);
184    }
185    $comment->junk_status(JUNK);
186    $comment->visible(0);
187}
188
189sub moderate {
190    my ($comment) = @_;
191    $comment->visible(0);
192}
193
194sub approve {
195    my ($comment) = @_;
196    $comment->visible(1);
197    $comment->junk_status(NOT_JUNK);
198}
199
200*publish = \&approve;
201
202sub author {
203    my $comment = shift;
204    if (!@_ && $comment->commenter_id) {
205        require MT::Author;
206        if (my $auth = MT::Author->load($comment->commenter_id)) {
207            return $auth->nickname;
208        }
209    }
210    return $comment->column('author', @_);
211}
212
213sub all_text {
214    my $this = shift;
215    my $text = $this->column('author') || '';
216    $text .= "\n" . ($this->column('email') || '');
217    $text .= "\n" . ($this->column('url') || '');
218    $text .= "\n" . ($this->column('text') || '');
219    $text;
220}
221
222sub is_published {
223    return $_[0]->visible && !$_[0]->is_junk;
224}
225
226sub is_moderated {
227    return !$_[0]->visible() && !$_[0]->is_junk();
228}
229
230sub log {
231    # TBD: pre-load __junk_log when loading the comment
232    my $comment = shift;
233    push @{$comment->{__junk_log}}, @_;
234}
235
236sub save {
237    my $comment = shift;
238    $comment->junk_log(join "\n", @{$comment->{__junk_log}})
239        if ref $comment->{__junk_log} eq 'ARRAY';
240    my $ret = $comment->SUPER::save();
241    delete $comment->{__changed}{visibility} if $ret;
242    return $ret;
243}
244
245sub to_hash {
246    my $cmt = shift;
247    my $hash = $cmt->SUPER::to_hash();
248
249    $hash->{'comment.created_on_iso'} = sub { MT::Util::ts2iso($cmt->blog, $cmt->created_on) };
250    $hash->{'comment.modified_on_iso'} = sub { MT::Util::ts2iso($cmt->blog, $cmt->modified_on) };
251    if (my $blog = $cmt->blog) {
252        $hash->{'comment.text_html'} = sub {
253            my $txt = defined $cmt->text ? $cmt->text : '';
254            require MT::Util;
255            $txt = MT::Util::munge_comment($txt, $blog);
256            my $convert_breaks = $blog->convert_paras_comments;
257            $txt = $convert_breaks ?
258                MT->apply_text_filters($txt, $blog->comment_text_filters) :
259                $txt;
260            my $sanitize_spec = $blog->sanitize_spec ||
261                MT->config->GlobalSanitizeSpec;
262            require MT::Sanitize;
263            MT::Sanitize->sanitize($txt, $sanitize_spec);
264        }
265    }
266    if (my $entry = $cmt->entry) {
267        my $entry_hash = $entry->to_hash;
268        $hash->{"comment.$_"} = $entry_hash->{$_} foreach keys %$entry_hash;
269    }
270    if ($cmt->commenter_id) {
271        # commenter record exists... populate it
272        require MT::Author;
273        if (my $auth = MT::Author->load($cmt->commenter_id)) {
274            my $auth_hash = $auth->to_hash;
275            $hash->{"comment.$_"} = $auth_hash->{$_} foreach keys %$auth_hash;
276        }
277    }
278
279    $hash;
280}
281
282sub visible {
283    my $comment = shift;
284    return $comment->SUPER::visible unless @_;
285
286    ## Note transitions in visibility in the object, so that
287    ## other methods can act appropriately.
288    my $was_visible = $comment->SUPER::visible || 0;
289    my $is_visible = shift || 0;
290
291    my $vis_delta = 0;
292    if (!$was_visible && $is_visible) {
293        $vis_delta = 1;
294    } elsif ($was_visible && !$is_visible) {
295        $vis_delta = -1;
296    }
297    $comment->{__changed}{visibility} ||= 0;
298    $comment->{__changed}{visibility} += $vis_delta;
299
300    return $comment->SUPER::visible($is_visible);
301}
302
303sub parent {
304    my $comment = shift;
305    $comment->cache_property('parent', sub {
306        if ($comment->parent_id) {
307            return MT::Comment->load($comment->parent_id);
308        }
309    });
310}
311
3121;
313__END__
314
315=head1 NAME
316
317MT::Comment - Movable Type comment record
318
319=head1 SYNOPSIS
320
321    use MT::Comment;
322    my $comment = MT::Comment->new;
323    $comment->blog_id($entry->blog_id);
324    $comment->entry_id($entry->id);
325    $comment->author('Foo');
326    $comment->text('This is a comment.');
327    $comment->save
328        or die $comment->errstr;
329
330=head1 DESCRIPTION
331
332An I<MT::Comment> object represents a comment in the Movable Type system. It
333contains all of the metadata about the comment (author name, email address,
334homepage URL, IP address, etc.), as well as the actual body of the comment.
335
336=head1 USAGE
337
338As a subclass of I<MT::Object>, I<MT::Comment> inherits all of the
339data-management and -storage methods from that class; thus you should look
340at the I<MT::Object> documentation for details about creating a new object,
341loading an existing object, saving an object, etc.
342
343=head1 DATA ACCESS METHODS
344
345The I<MT::Comment> object holds the following pieces of data. These fields can
346be accessed and set using the standard data access methods described in the
347I<MT::Object> documentation.
348
349=over 4
350
351=item * id
352
353The numeric ID of the comment.
354
355=item * blog_id
356
357The numeric ID of the blog in which the comment is found.
358
359=item * entry_id
360
361The numeric ID of the entry on which the comment has been made.
362
363=item * author
364
365The name of the author of the comment.
366
367=item * commenter_id
368
369The author_id for the commenter; this will only be defined if the
370commenter is registered, which is only required if the blog config
371option allow_unreg_comments is false.
372
373=item * ip
374
375The IP address of the author of the comment.
376
377=item * email
378
379The email address of the author of the comment.
380
381=item * url
382
383The URL of the author of the comment.
384
385=item * text
386
387The body of the comment.
388
389=item * visible
390
391Returns a true value if the comment should be displayed. Comments can
392be hidden if comment registration is required and the commenter is
393pending approval.
394
395=item * created_on
396
397The timestamp denoting when the comment record was created, in the format
398C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted for the
399selected timezone.
400
401=item * modified_on
402
403The timestamp denoting when the comment record was last modified, in the
404format C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted
405for the selected timezone.
406
407=back
408
409=head1 DATA LOOKUP
410
411In addition to numeric ID lookup, you can look up or sort records by any
412combination of the following fields. See the I<load> documentation in
413I<MT::Object> for more information.
414
415=over 4
416
417=item * created_on
418
419=item * entry_id
420
421=item * blog_id
422
423=back
424
425=head1 AUTHOR & COPYRIGHTS
426
427Please see the I<MT> manpage for author, copyright, and license information.
428
429=cut
Note: See TracBrowser for help on using the browser.