root/branches/release-26/lib/MT/Comment.pm @ 1174

Revision 1174, 12.2 kB (checked in by bchoate, 23 months ago)

Updated copyright year for source.

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