root/branches/release-32/lib/MT/TBPing.pm @ 1578

Revision 1578, 10.3 kB (checked in by bchoate, 20 months ago)

Index updates to improve next/previous comment, entry, ping calculation. BugID:70252

  • 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::TBPing;
8
9use strict;
10use base qw( MT::Object MT::Scorable );
11
12use constant JUNK => -1;
13use constant NOT_JUNK => 1;
14
15__PACKAGE__->install_properties({
16    column_defs => {
17        'id' => 'integer not null auto_increment',
18        'blog_id' => 'integer not null',
19        'tb_id' => 'integer not null',
20        'title' => 'string(255)',
21        'excerpt' => 'text',
22        'source_url' => 'string(255)',
23        'ip' => 'string(15) not null',
24        'blog_name' => 'string(255)',
25        'visible' => 'boolean',
26        'junk_status' => 'smallint not null',
27        'last_moved_on' => 'datetime not null',
28        'junk_score' => 'float',
29        'junk_log' => 'text',
30    },
31    indexes => {
32        created_on => 1,
33        tb_id => 1,
34        ip => 1,
35        last_moved_on => 1, # used for junk expiration
36        # For URL lookups to aid spam filtering
37        blog_url => {
38            columns => [ 'blog_id', 'visible', 'source_url' ],
39        },
40        blog_stat => {
41            columns => ['blog_id', 'junk_status', 'created_on'],
42        },
43        blog_visible => {
44            columns => ['blog_id', 'visible', 'created_on', 'id'],
45        },
46        visible_date => {
47            columns => [ 'visible', 'created_on' ],
48        },
49        junk_date => {
50            columns => [ 'junk_status', 'created_on' ],
51        },
52    },
53    defaults => {
54        junk_status => 0,
55        last_moved_on => '20000101000000',
56    },
57    audit => 1,
58    datasource => 'tbping',
59    primary_key => 'id',
60});
61
62sub class_label {
63    return MT->translate('TrackBack');
64}
65
66sub class_label_plural {
67    return MT->translate('TrackBacks');
68}
69
70sub is_junk {
71    $_[0]->junk_status == JUNK;
72}
73sub is_not_junk {
74    $_[0]->junk_status != JUNK;
75}
76
77sub is_published {
78    return $_[0]->visible && !$_[0]->is_junk;
79}
80
81sub is_moderated {
82    return !$_[0]->visible() && !$_[0]->is_junk();
83}
84
85sub blog {
86    my ($ping) = @_;
87    my $blog = $ping->{__blog};
88    unless ($blog) {
89        my $blog_id = $ping->blog_id;
90        my $blog_class = MT->model('blog');
91        $blog = $blog_class->load($blog_id) or
92            return $ping->error(MT->translate(
93                "Load of blog '[_1]' failed: [_2]", $blog_id, $blog_class->errstr));   
94        $ping->{__blog} = $blog;
95    }
96    return $blog;
97}
98
99sub parent {
100    my ($ping) = @_;
101    if (my $tb = MT->model('trackback')->load($ping->tb_id)) {
102        if ($tb->entry_id) {
103            return MT->model('entry')->load($tb->entry_id);
104        } else {
105            return MT->model('category')->load($tb->category_id);
106        }
107    }
108}
109
110sub parent_id {
111    my ($ping) = @_;
112    if (my $tb = MT->model('trackback')->load($ping->tb_id)) {
113        if ($tb->entry_id) {
114            return ('MT::Entry', $tb->entry_id);
115        } else {
116            return ('MT::Category', $tb->category_id);
117        }
118    }
119}
120
121sub next {
122    my $ping = shift;
123    my($publish_only) = @_;
124    $publish_only = $publish_only ? {'visible' => 1} : {};
125    $ping->_nextprev('next', $publish_only);
126}
127
128sub previous {
129    my $ping = shift;
130    my($publish_only) = @_;
131    $publish_only = $publish_only ? {'visible' => 1} : {};
132    $ping->_nextprev('previous', $publish_only);
133}
134
135sub _nextprev {
136    my $obj = shift;
137    my $class = ref($obj);
138    my ($direction, $publish_only) = @_;
139    return undef unless ($direction eq 'next' || $direction eq 'previous');
140    my $next = $direction eq 'next';
141
142    my $label = '__' . $direction;
143    return $obj->{$label} if $obj->{$label};
144
145    # Selecting the adjacent object can be tricky since timestamps
146    # are not necessarily unique for entries. If we find that the
147    # next/previous object has a matching timestamp, keep selecting entries
148    # to select all entries with the same timestamp, then compare them using
149    # id as a secondary sort column.
150
151    my ($id, $ts) = ($obj->id, $obj->created_on);
152    my $iter = $class->load_iter({
153        blog_id => $obj->blog_id,
154        created_on => ($next ? [ $ts, undef ] : [ undef, $ts ]),
155        %{$publish_only}
156    }, {
157        'sort' => 'created_on',
158        'direction' => $next ? 'ascend' : 'descend',
159        'range_incl' => { 'created_on' => 1 },
160    });
161
162    # This selection should always succeed, but handle situation if
163    # it fails by returning undef.
164    return unless $iter;
165
166    # The 'same' array will hold any entries that have matching
167    # timestamps; we will then sort those by id to find the correct
168    # adjacent object.
169    my @same;
170    while (my $e = $iter->()) {
171        # Don't consider the object that is 'current'
172        next if $e->id == $id;
173        my $e_ts = $e->created_on;
174        if ($e_ts eq $ts) {
175            # An object with the same timestamp should only be
176            # considered if the id is in the scope we're looking for
177            # (greater than for the 'next' object; less than for
178            # the 'previous' object).
179            push @same, $e
180                if $next && $e->id > $id or !$next && $e->id < $id;
181        } else {
182            # We found an object with a timestamp different than
183            # the 'current' object.
184            if (!@same) {
185                push @same, $e;
186                # We should check to see if this new timestamped object also
187                # has entries adjacent to _it_ that have the same timestamp.
188                while (my $e = $iter->()) {
189                    push(@same, $e), next if $e->created_on eq $e_ts;
190                    $iter->('finish'), last;
191                }
192            } else {
193                $iter->('finish');
194            }
195            return $obj->{$label} = $e unless @same;
196            last;
197        }
198    }
199    if (@same) {
200        # If we only have 1 element in @same, return that.
201        return $obj->{$label} = $same[0] if @same == 1;
202        # Sort remaining elements in @same by id.
203        @same = sort { $a->id <=> $b->id } @same;
204        # Return front of list (smallest id) if selecting 'next'
205        # object. Return tail of list (largest id) if selection 'previous'.
206        return $obj->{$label} = $same[$next ? 0 : $#same];
207    }
208    return;
209}
210
211sub junk {
212    my $ping = shift;
213    if (($ping->junk_status || 0) != JUNK) {
214        require MT::Util;
215        my @ts = MT::Util::offset_time_list(time, $ping->blog_id);
216        my $ts = sprintf("%04d%02d%02d%02d%02d%02d",
217                         $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0]);
218        $ping->last_moved_on($ts);
219    }
220    $ping->junk_status(JUNK);
221    $ping->visible(0);
222}
223
224sub moderate {
225    my $ping = shift;
226    $ping->visible(0);
227}
228
229sub approve {
230    my $ping = shift;
231    $ping->visible(1);
232    $ping->junk_status(NOT_JUNK);
233}
234
235sub all_text {
236    my $this = shift;
237    my $text = $this->column('blog_name') || '';
238    $text .= "\n" . ($this->column('title') || '');
239    $text .= "\n" . ($this->column('source_url') || '');
240    $text .= "\n" . ($this->column('excerpt') || '');
241    $text;
242}
243
244sub to_hash {
245    my $ping = shift;
246    my $hash = $ping->SUPER::to_hash(@_);
247    require MT::Sanitize;
248    $hash->{'tbping.excerpt_html'} = MT::Sanitize->sanitize($ping->excerpt || '');
249    $hash->{'tbping.created_on_iso'} = sub { MT::Util::ts2iso($ping->blog_id, $ping->created_on) };
250    $hash->{'tbping.modified_on_iso'} = sub { MT::Util::ts2iso($ping->blog_id, $ping->modified_on) };
251
252    if (my $parent = $ping->parent) {
253        my $parent_hash = $parent->to_hash;
254        $hash->{"tbping.$_"} = $parent_hash->{$_} foreach keys %$parent_hash;
255    }
256
257    $hash;
258}
259
260sub visible {
261    my $ping = shift;
262    return $ping->SUPER::visible unless @_;
263       
264    ## Note transitions in visibility in the object, so that
265    ## other methods can act appropriately.
266    my $was_visible = $ping->SUPER::visible || 0;
267    my $is_visible = shift || 0;
268       
269    my $vis_delta = 0;
270    if (!$was_visible && $is_visible) {
271        $vis_delta = 1;
272    } elsif ($was_visible && !$is_visible) {
273        $vis_delta = -1;
274    }
275    $ping->{__changed}{visibility} = $vis_delta;
276
277    return $ping->SUPER::visible($is_visible);
278}
279
2801;
281__END__
282
283=head1 NAME
284
285MT::TBPing - Movable Type TrackBack Ping record
286
287=head1 SYNOPSIS
288
289    use MT::TBPing;
290    my $ping = MT::TBPing->new;
291    $ping->blog_id($tb->blog_id);
292    $ping->tb_id($tb->id);
293    $ping->title('Foo');
294    $ping->excerpt('This is from a TrackBack ping.');
295    $ping->source_url('http://www.foo.com/bar');
296    $ping->save
297        or die $ping->errstr;
298
299=head1 DESCRIPTION
300
301An I<MT::TBPing> object represents a TrackBack ping in the Movable Type system.
302It contains all of the metadata about the ping (title, excerpt, URL, etc).
303
304=head1 USAGE
305
306As a subclass of I<MT::Object>, I<MT::TBPing> inherits all of the
307data-management and -storage methods from that class; thus you should look
308at the I<MT::Object> documentation for details about creating a new object,
309loading an existing object, saving an object, etc.
310
311=head1 DATA ACCESS METHODS
312
313The I<MT::TBPing> object holds the following pieces of data. These fields can
314be accessed and set using the standard data access methods described in the
315I<MT::Object> documentation.
316
317=over 4
318
319=item * id
320
321The numeric ID of the ping.
322
323=item * blog_id
324
325The numeric ID of the blog in which the ping is found.
326
327=item * tb_id
328
329The numeric ID of the TrackBack record (I<MT::Trackback> object) to which
330the ping was sent.
331
332=item * title
333
334The title of the ping item.
335
336=item * ip
337
338The IP address of the server that sent the ping.
339
340=item * excerpt
341
342The excerpt of the ping item.
343
344=item * source_url
345
346The URL of the item pointed to by the ping.
347
348=item * blog_name
349
350The name of the blog on which the original item was posted.
351
352=item * created_on
353
354The timestamp denoting when the ping record was created, in the format
355C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted for the
356selected timezone.
357
358=item * modified_on
359
360The timestamp denoting when the ping record was last modified, in the
361format C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted
362for the selected timezone.
363
364=back
365
366=head1 DATA LOOKUP
367
368In addition to numeric ID lookup, you can look up or sort records by any
369combination of the following fields. See the I<load> documentation in
370I<MT::Object> for more information.
371
372=over 4
373
374=item * created_on
375
376=item * tb_id
377
378=item * blog_id
379
380=item * ip
381
382=back
383
384=head1 AUTHOR & COPYRIGHTS
385
386Please see the I<MT> manpage for author, copyright, and license information.
387
388=cut
Note: See TracBrowser for help on using the browser.