| 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 | |
|---|
| 7 | package MT::Entry; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | |
|---|
| 11 | use MT::Tag; # Holds MT::Taggable |
|---|
| 12 | use base qw( MT::Object MT::Taggable MT::Scorable ); |
|---|
| 13 | |
|---|
| 14 | use MT::Blog; |
|---|
| 15 | use MT::Author; |
|---|
| 16 | use MT::Category; |
|---|
| 17 | use MT::Memcached; |
|---|
| 18 | use MT::Placement; |
|---|
| 19 | use MT::Comment; |
|---|
| 20 | use MT::TBPing; |
|---|
| 21 | use MT::Util qw( archive_file_for discover_tb start_end_period extract_domain |
|---|
| 22 | extract_domains ); |
|---|
| 23 | |
|---|
| 24 | use constant CATEGORY_CACHE_TIME => 7 * 24 * 60 * 60; ## 1 week |
|---|
| 25 | |
|---|
| 26 | __PACKAGE__->install_properties({ |
|---|
| 27 | column_defs => { |
|---|
| 28 | 'id' => 'integer not null auto_increment', |
|---|
| 29 | 'blog_id' => 'integer not null', |
|---|
| 30 | 'status' => 'smallint not null', |
|---|
| 31 | 'author_id' => 'integer not null', |
|---|
| 32 | 'allow_comments' => 'boolean', |
|---|
| 33 | 'title' => 'string(255)', |
|---|
| 34 | 'excerpt' => 'text', |
|---|
| 35 | 'text' => 'text', |
|---|
| 36 | 'text_more' => 'text', |
|---|
| 37 | 'convert_breaks' => 'string(30)', |
|---|
| 38 | 'to_ping_urls' => 'text', |
|---|
| 39 | 'pinged_urls' => 'text', |
|---|
| 40 | 'allow_pings' => 'boolean', |
|---|
| 41 | 'keywords' => 'text', |
|---|
| 42 | 'tangent_cache' => 'text', |
|---|
| 43 | 'basename' => 'string(255)', |
|---|
| 44 | 'atom_id' => 'string(255)', |
|---|
| 45 | 'authored_on' => 'datetime', |
|---|
| 46 | 'week_number' => 'integer', |
|---|
| 47 | 'template_id' => 'integer', |
|---|
| 48 | ## Have to keep this around for use in mt-upgrade.cgi. |
|---|
| 49 | 'category_id' => 'integer', |
|---|
| 50 | }, |
|---|
| 51 | indexes => { |
|---|
| 52 | blog_id => 1, |
|---|
| 53 | status => 1, |
|---|
| 54 | author_id => 1, |
|---|
| 55 | created_on => 1, |
|---|
| 56 | modified_on => 1, |
|---|
| 57 | authored_on => 1, |
|---|
| 58 | week_number => 1, |
|---|
| 59 | basename => 1, |
|---|
| 60 | blog_authored => { |
|---|
| 61 | columns => ['blog_id', 'authored_on'], |
|---|
| 62 | }, |
|---|
| 63 | }, |
|---|
| 64 | child_of => 'MT::Blog', |
|---|
| 65 | child_classes => ['MT::Comment','MT::Placement','MT::Trackback','MT::FileInfo'], |
|---|
| 66 | audit => 1, |
|---|
| 67 | meta => 1, |
|---|
| 68 | datasource => 'entry', |
|---|
| 69 | primary_key => 'id', |
|---|
| 70 | class_type => 'entry', |
|---|
| 71 | }); |
|---|
| 72 | |
|---|
| 73 | use constant HOLD => 1; |
|---|
| 74 | use constant RELEASE => 2; |
|---|
| 75 | use constant REVIEW => 3; |
|---|
| 76 | use constant FUTURE => 4; |
|---|
| 77 | |
|---|
| 78 | use Exporter; |
|---|
| 79 | *import = \&Exporter::import; |
|---|
| 80 | use vars qw( @EXPORT_OK %EXPORT_TAGS); |
|---|
| 81 | @EXPORT_OK = qw( HOLD RELEASE FUTURE ); |
|---|
| 82 | %EXPORT_TAGS = (constants => [ qw(HOLD RELEASE FUTURE) ]); |
|---|
| 83 | |
|---|
| 84 | sub class_label { |
|---|
| 85 | MT->translate("Entry"); |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | sub class_label_plural { |
|---|
| 89 | MT->translate("Entries"); |
|---|
| 90 | } |
|---|
| 91 | |
|---|
| 92 | sub container_type { |
|---|
| 93 | return "category"; |
|---|
| 94 | } |
|---|
| 95 | |
|---|
| 96 | sub container_label { |
|---|
| 97 | MT->translate("Category"); |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | sub cache_key { |
|---|
| 101 | my($entry_id, $key); |
|---|
| 102 | if (@_ == 3) { |
|---|
| 103 | ($entry_id, $key) = @_[1, 2]; |
|---|
| 104 | } else { |
|---|
| 105 | ($entry_id, $key) = ($_[0]->id, $_[1]); |
|---|
| 106 | } |
|---|
| 107 | return sprintf "entry%s-%d", $key, $entry_id; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | sub status_text { |
|---|
| 111 | my $s = $_[0]; |
|---|
| 112 | $s == HOLD ? "Draft" : |
|---|
| 113 | $s == RELEASE ? "Publish" : |
|---|
| 114 | $s == REVIEW ? "Review" : |
|---|
| 115 | $s == FUTURE ? "Future" : ''; |
|---|
| 116 | } |
|---|
| 117 | |
|---|
| 118 | sub status_int { |
|---|
| 119 | my $s = lc $_[0]; ## Lower-case it so that it's case-insensitive |
|---|
| 120 | $s eq 'draft' ? HOLD : |
|---|
| 121 | $s eq 'publish' ? RELEASE : |
|---|
| 122 | $s eq 'review' ? REVIEW : |
|---|
| 123 | $s eq 'future' ? FUTURE : undef; |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | sub authored_on_obj { |
|---|
| 127 | my $obj = shift; |
|---|
| 128 | return $obj->column_as_datetime('authored_on'); |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | sub next { |
|---|
| 132 | my $entry = shift; |
|---|
| 133 | my($opt) = @_; |
|---|
| 134 | my $terms; |
|---|
| 135 | if (ref $opt) { |
|---|
| 136 | $terms = $opt; |
|---|
| 137 | } |
|---|
| 138 | else { |
|---|
| 139 | $terms = $opt ? { status => RELEASE } : {}; |
|---|
| 140 | } |
|---|
| 141 | $entry->_nextprev('next', $terms); |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | sub previous { |
|---|
| 145 | my $entry = shift; |
|---|
| 146 | my($opt) = @_; |
|---|
| 147 | my $terms; |
|---|
| 148 | if (ref $opt) { |
|---|
| 149 | $terms = $opt; |
|---|
| 150 | } |
|---|
| 151 | else { |
|---|
| 152 | $terms = $opt ? { status => RELEASE } : {}; |
|---|
| 153 | } |
|---|
| 154 | $entry->_nextprev('previous', $terms); |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | sub _nextprev { |
|---|
| 158 | my $obj = shift; |
|---|
| 159 | my $class = ref($obj); |
|---|
| 160 | my ($direction, $terms) = @_; |
|---|
| 161 | return undef unless ($direction eq 'next' || $direction eq 'previous'); |
|---|
| 162 | my $next = $direction eq 'next'; |
|---|
| 163 | |
|---|
| 164 | $terms->{author_id} = $obj->author_id if delete $terms->{by_author}; |
|---|
| 165 | if (delete $terms->{by_category}) { |
|---|
| 166 | if (my $c = $obj->category) { |
|---|
| 167 | $terms->{category_id} = $c->id; |
|---|
| 168 | } |
|---|
| 169 | else { |
|---|
| 170 | return undef; |
|---|
| 171 | } |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | my $label = '__' . $direction; |
|---|
| 175 | $label .= ':author='. $terms->{author_id} if exists $terms->{author_id}; |
|---|
| 176 | $label .= ':category='. $terms->{category_id} if exists $terms->{category_id}; |
|---|
| 177 | return $obj->{$label} if $obj->{$label}; |
|---|
| 178 | |
|---|
| 179 | my $args = {}; |
|---|
| 180 | if (my $cat_id = delete $terms->{category_id}) { |
|---|
| 181 | my $join = MT::Placement->join_on('entry_id', |
|---|
| 182 | { category_id => $cat_id } |
|---|
| 183 | ); |
|---|
| 184 | $args->{join} = $join; |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | return $obj->{$label} = $obj->nextprev( |
|---|
| 188 | direction => $direction, |
|---|
| 189 | terms => { blog_id => $obj->blog_id, class => $obj->class, %$terms }, |
|---|
| 190 | args => $args, |
|---|
| 191 | by => 'authored_on', |
|---|
| 192 | ); |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | sub trackback { |
|---|
| 196 | my $entry = shift; |
|---|
| 197 | $entry->cache_property('trackback', sub { |
|---|
| 198 | require MT::Trackback; |
|---|
| 199 | if ($entry->id) { |
|---|
| 200 | return scalar MT::Trackback->load({ entry_id => $entry->id }); |
|---|
| 201 | } |
|---|
| 202 | }, @_); |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | sub author { |
|---|
| 206 | my $entry = shift; |
|---|
| 207 | $entry->cache_property('author', sub { |
|---|
| 208 | return undef unless $entry->author_id; |
|---|
| 209 | my $req = MT::Request->instance(); |
|---|
| 210 | my $author_cache = $req->stash('author_cache'); |
|---|
| 211 | my $author = $author_cache->{$entry->author_id}; |
|---|
| 212 | unless ($author) { |
|---|
| 213 | require MT::Author; |
|---|
| 214 | $author = MT::Author->load($entry->author_id); |
|---|
| 215 | $author_cache->{$entry->author_id} = $author; |
|---|
| 216 | $req->stash('author_cache', $author_cache); |
|---|
| 217 | } |
|---|
| 218 | $author; |
|---|
| 219 | }); |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | sub __load_category_data { |
|---|
| 223 | my $entry = shift; |
|---|
| 224 | my $t = MT->get_timer; |
|---|
| 225 | $t->pause_partial if $t; |
|---|
| 226 | my $cache = MT::Memcached->instance; |
|---|
| 227 | my $memkey = $entry->cache_key('categories'); |
|---|
| 228 | my $rows; |
|---|
| 229 | unless ($rows = $cache->get($memkey)) { |
|---|
| 230 | require MT::Placement; |
|---|
| 231 | my @maps = MT::Placement->search({ entry_id => $entry->id }); |
|---|
| 232 | $rows = [ map { [ $_->category_id, $_->is_primary ] } @maps ]; |
|---|
| 233 | $cache->set($memkey, $rows, CATEGORY_CACHE_TIME); |
|---|
| 234 | } |
|---|
| 235 | $t->mark('MT::Entry::__load_category_data') if $t; |
|---|
| 236 | return $rows; |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | sub flush_category_cache { |
|---|
| 240 | my($copy, $place) = @_; |
|---|
| 241 | MT::Memcached->instance->delete( |
|---|
| 242 | MT::Entry->cache_key($place->entry_id, 'categories') |
|---|
| 243 | ); |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | MT::Placement->add_trigger( |
|---|
| 247 | post_save => \&flush_category_cache, |
|---|
| 248 | post_remove => \&flush_category_cache |
|---|
| 249 | ); |
|---|
| 250 | |
|---|
| 251 | sub category { |
|---|
| 252 | my $entry = shift; |
|---|
| 253 | $entry->cache_property('category', sub { |
|---|
| 254 | my $rows = $entry->__load_category_data or return; |
|---|
| 255 | my @rows = grep { $_->[1] } @$rows or return; |
|---|
| 256 | require MT::Category; |
|---|
| 257 | return MT::Category->lookup( $rows[0] ); |
|---|
| 258 | }); |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | sub categories { |
|---|
| 262 | my $entry = shift; |
|---|
| 263 | $entry->cache_property('categories', sub { |
|---|
| 264 | my $rows = $entry->__load_category_data or return; |
|---|
| 265 | my $cats = MT::Category->lookup_multi([ map { $_->[0] } @$rows ]); |
|---|
| 266 | my @cats = sort { $a->label cmp $b->label } @$cats; |
|---|
| 267 | return \@cats; |
|---|
| 268 | }); |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | sub is_in_category { |
|---|
| 272 | my $entry = shift; |
|---|
| 273 | my($cat) = @_; |
|---|
| 274 | my $cats = $entry->categories; |
|---|
| 275 | for my $c (@$cats) { |
|---|
| 276 | return 1 if $c->id == $cat->id; |
|---|
| 277 | } |
|---|
| 278 | 0; |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | sub comments { |
|---|
| 282 | my $entry = shift; |
|---|
| 283 | my ($terms, $args) = @_; |
|---|
| 284 | require MT::Comment; |
|---|
| 285 | if ($terms || $args) { |
|---|
| 286 | $terms ||= {}; |
|---|
| 287 | $terms->{entry_id} = $entry->id; |
|---|
| 288 | return [ MT::Comment->load( $terms, $args ) ]; |
|---|
| 289 | } else { |
|---|
| 290 | $entry->cache_property('comments', sub { |
|---|
| 291 | [ MT::Comment->load({ entry_id => $entry->id }) ]; |
|---|
| 292 | }); |
|---|
| 293 | } |
|---|
| 294 | } |
|---|
| 295 | |
|---|
| 296 | sub comment_latest { |
|---|
| 297 | my $entry = shift; |
|---|
| 298 | $entry->cache_property('comment_latest', sub { |
|---|
| 299 | require MT::Comment; |
|---|
| 300 | MT::Comment->load({ |
|---|
| 301 | entry_id => $entry->id, |
|---|
| 302 | visible => 1 |
|---|
| 303 | }, { |
|---|
| 304 | 'sort' => 'created_on', |
|---|
| 305 | direction => 'descend', |
|---|
| 306 | limit => 1, |
|---|
| 307 | }); |
|---|
| 308 | }); |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | MT::Comment->add_trigger( post_save => sub { |
|---|
| 312 | my($clone, $comment) = @_; |
|---|
| 313 | |
|---|
| 314 | ## Note: This flag is set in MT::Comment::visible(). |
|---|
| 315 | if (my $delta = $comment->{__changed}{visibility}) { |
|---|
| 316 | my $memkey = MT::Entry->cache_key($comment->entry_id, 'commentcount'); |
|---|
| 317 | my $cache = MT::Memcached->instance; |
|---|
| 318 | if ($delta > 0) { |
|---|
| 319 | $cache->incr($memkey, $delta); |
|---|
| 320 | } elsif ($delta < 0) { |
|---|
| 321 | $cache->decr($memkey, abs $delta); |
|---|
| 322 | } |
|---|
| 323 | } |
|---|
| 324 | } ); |
|---|
| 325 | |
|---|
| 326 | MT::Comment->add_trigger( post_remove => sub { |
|---|
| 327 | my($comment) = @_; |
|---|
| 328 | |
|---|
| 329 | ## If this comment was published, decrement the cached count of |
|---|
| 330 | ## visible comments on the entry. |
|---|
| 331 | if ($comment->visible && !$comment->is_changed('visible')) { |
|---|
| 332 | my $memkey = MT::Entry->cache_key($comment->entry_id, 'commentcount'); |
|---|
| 333 | MT::Memcached->instance->decr($memkey, 1); |
|---|
| 334 | } |
|---|
| 335 | } ); |
|---|
| 336 | |
|---|
| 337 | sub comment_count { |
|---|
| 338 | my $entry = shift; |
|---|
| 339 | $entry->cache_property('comment_count', sub { |
|---|
| 340 | my $cache = MT::Memcached->instance; |
|---|
| 341 | my $memkey = $entry->cache_key('commentcount'); |
|---|
| 342 | if (defined( my $count = $cache->get($memkey) )) { |
|---|
| 343 | return $count; |
|---|
| 344 | } else { |
|---|
| 345 | require MT::Comment; |
|---|
| 346 | my $count = MT::Comment->count({ |
|---|
| 347 | entry_id => $entry->id, |
|---|
| 348 | visible => 1 |
|---|
| 349 | }); |
|---|
| 350 | $cache->add($memkey, $count, 7 * 24 * 60 * 60); ## 1 week. |
|---|
| 351 | return $count; |
|---|
| 352 | } |
|---|
| 353 | }); |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | sub pings { |
|---|
| 357 | my $entry = shift; |
|---|
| 358 | my ($terms, $args) = @_; |
|---|
| 359 | if ($terms || $args) { |
|---|
| 360 | $terms ||= {}; |
|---|
| 361 | $terms->{entry_id} = $entry->id; |
|---|
| 362 | return [ MT::TBPing->load( $terms, $args ) ]; |
|---|
| 363 | } else { |
|---|
| 364 | $entry->cache_property('pings', sub { |
|---|
| 365 | [ MT::TBPing->load({ entry_id => $entry->id }) ]; |
|---|
| 366 | }); |
|---|
| 367 | } |
|---|
| 368 | } |
|---|
| 369 | |
|---|
| 370 | MT::TBPing->add_trigger( post_save => sub { |
|---|
| 371 | my($clone, $ping) = @_; |
|---|
| 372 | |
|---|
| 373 | ## Note: This flag is set in MT::TBPing::visible(). |
|---|
| 374 | if (my $delta = $ping->{__changed}{visibility}) { |
|---|
| 375 | my($class, $entry_id) = $ping->parent_id; |
|---|
| 376 | return unless $entry_id; |
|---|
| 377 | my $memkey = MT::Entry->cache_key($entry_id, 'pingcount'); |
|---|
| 378 | my $cache = MT::Memcached->instance; |
|---|
| 379 | if ($delta > 0) { |
|---|
| 380 | $cache->incr($memkey, $delta); |
|---|
| 381 | } elsif ($delta < 0) { |
|---|
| 382 | $cache->decr($memkey, abs $delta); |
|---|
| 383 | } |
|---|
| 384 | } |
|---|
| 385 | } ); |
|---|
| 386 | |
|---|
| 387 | MT::TBPing->add_trigger( post_remove => sub { |
|---|
| 388 | my($ping) = @_; |
|---|
| 389 | |
|---|
| 390 | ## If this ping was published, decrement the cached count of |
|---|
| 391 | ## visible pings on the entry. |
|---|
| 392 | if ($ping->visible && !$ping->is_changed('visible')) { |
|---|
| 393 | my($class, $entry_id) = $ping->parent_id; |
|---|
| 394 | return unless $entry_id; |
|---|
| 395 | my $memkey = MT::Entry->cache_key($entry_id, 'pingcount'); |
|---|
| 396 | MT::Memcached->instance->decr($memkey, 1); |
|---|
| 397 | } |
|---|
| 398 | } ); |
|---|
| 399 | |
|---|
| 400 | sub ping_count { |
|---|
| 401 | my $entry = shift; |
|---|
| 402 | $entry->cache_property('ping_count', sub { |
|---|
| 403 | my $cache = MT::Memcached->instance; |
|---|
| 404 | my $memkey = 'entrypingcount-' . $entry->id; |
|---|
| 405 | if (defined( my $count = $cache->get($memkey) )) { |
|---|
| 406 | return $count; |
|---|
| 407 | } else { |
|---|
| 408 | require MT::TBPing; |
|---|
| 409 | my $tb = $entry->trackback; |
|---|
| 410 | my $count = |
|---|
| 411 | $tb ? MT::TBPing->count({ tb_id => $tb->id, visible => 1 }) : 0; |
|---|
| 412 | $cache->add($memkey, $count, 7 * 24 * 60 * 60); ## 1 week. |
|---|
| 413 | return $count; |
|---|
| 414 | } |
|---|
| 415 | }); |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | sub archive_file { |
|---|
| 419 | my $entry = shift; |
|---|
| 420 | my($at) = @_; |
|---|
| 421 | my $blog = $entry->blog() || return $entry->error(MT->translate( |
|---|
| 422 | "Load of blog failed: [_1]", |
|---|
| 423 | MT::Blog->errstr)); |
|---|
| 424 | unless ($at) { |
|---|
| 425 | $at = $blog->archive_type_preferred || $blog->archive_type; |
|---|
| 426 | return '' if !$at || $at eq 'None'; |
|---|
| 427 | return '' if $at eq 'Page'; |
|---|
| 428 | my %at = map { $_ => 1 } split /,/, $at; |
|---|
| 429 | # FIXME: should draw from list of registered archive types |
|---|
| 430 | for my $tat (qw( Individual Daily Weekly Author-Monthly Category-Monthly Monthly Category )) { |
|---|
| 431 | $at = $tat if $at{$tat}; |
|---|
| 432 | last; |
|---|
| 433 | } |
|---|
| 434 | } |
|---|
| 435 | archive_file_for($entry, $blog, $at); |
|---|
| 436 | } |
|---|
| 437 | |
|---|
| 438 | sub archive_url { |
|---|
| 439 | my $entry = shift; |
|---|
| 440 | my $blog = $entry->blog() || return $entry->error(MT->translate( |
|---|
| 441 | "Load of blog failed: [_1]", |
|---|
| 442 | MT::Blog->errstr)); |
|---|
| 443 | my $url = $blog->archive_url || ""; |
|---|
| 444 | $url .= '/' unless $url =~ m!/$!; |
|---|
| 445 | $url . $entry->archive_file(@_); |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | sub permalink { |
|---|
| 449 | my $entry = shift; |
|---|
| 450 | my $blog = $entry->blog() || return $entry->error(MT->translate( |
|---|
| 451 | "Load of blog failed: [_1]", |
|---|
| 452 | MT::Blog->errstr)); |
|---|
| 453 | my $url = $entry->archive_url($_[0]); |
|---|
| 454 | my $effective_archive_type = ($_[0] |
|---|
| 455 | || $blog->archive_type_preferred |
|---|
| 456 | || $blog->archive_type); |
|---|
| 457 | $url .= '#' . ($_[1]->{valid_html} ? 'a' : '') . |
|---|
| 458 | sprintf("%06d", $entry->id) |
|---|
| 459 | unless ($effective_archive_type eq 'Individual' |
|---|
| 460 | || $_[1]->{no_anchor}); |
|---|
| 461 | $url; |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | sub all_permalinks { |
|---|
| 465 | my $entry = shift; |
|---|
| 466 | my $blog = $entry->blog || return $entry->error(MT->translate( |
|---|
| 467 | "Load of blog failed: [_1]", |
|---|
| 468 | MT::Blog->errstr)); |
|---|
| 469 | my @at = split /,/, $blog->archive_type; |
|---|
| 470 | return unless @at; |
|---|
| 471 | my @urls; |
|---|
| 472 | for my $at (@at) { |
|---|
| 473 | push @urls, $entry->permalink($at); |
|---|
| 474 | } |
|---|
| 475 | @urls; |
|---|
| 476 | } |
|---|
| 477 | |
|---|
| 478 | sub text_filters { |
|---|
| 479 | my $entry = shift; |
|---|
| 480 | my $filters = $entry->convert_breaks; |
|---|
| 481 | if (!defined $filters) { |
|---|
| 482 | my $blog = $entry->blog() || return []; |
|---|
| 483 | $filters = $blog->convert_paras; |
|---|
| 484 | } |
|---|
| 485 | return [] unless $filters; |
|---|
| 486 | if ($filters eq '1') { |
|---|
| 487 | return [ '__default__' ]; |
|---|
| 488 | } else { |
|---|
| 489 | return [ split /\s*,\s*/, $filters ]; |
|---|
| 490 | } |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | sub get_excerpt { |
|---|
| 494 | my $entry = shift; |
|---|
| 495 | my($words) = @_; |
|---|
| 496 | return $entry->excerpt if $entry->excerpt; |
|---|
| 497 | my $excerpt = MT->apply_text_filters($entry->text, $entry->text_filters); |
|---|
| 498 | my $blog = $entry->blog() || return $entry->error(MT->translate( |
|---|
| 499 | "Load of blog failed: [_1]", |
|---|
| 500 | MT::Blog->errstr)); |
|---|
| 501 | MT::I18N::first_n_text($excerpt, $words || $blog->words_in_excerpt || MT::I18N::const('DEFAULT_LENGTH_ENTRY_EXCERPT')) . '...'; |
|---|
| 502 | } |
|---|
| 503 | |
|---|
| 504 | sub pinged_url_list { |
|---|
| 505 | my $entry = shift; |
|---|
| 506 | my (%param) = @_; |
|---|
| 507 | my $include_failures = $param{Failures} || $param{OnlyFailures}; |
|---|
| 508 | my $exclude_successes = $param{OnlyFailures}; |
|---|
| 509 | my $urls = $entry->pinged_urls; |
|---|
| 510 | return [] unless $urls && $urls =~ /\S/; |
|---|
| 511 | my %urls = map { $_ => 1 } split /\r?\n/, $urls; |
|---|
| 512 | my %to_ping = map { $_ => 1 } @{ $entry->to_ping_url_list }; |
|---|
| 513 | foreach (keys %to_ping) { |
|---|
| 514 | delete $urls{$_} if exists $urls{$_}; |
|---|
| 515 | } |
|---|
| 516 | my @urls = keys %urls; |
|---|
| 517 | foreach (@urls) { |
|---|
| 518 | if (m/^([^ ]+) /) { |
|---|
| 519 | delete $urls{$_}; # remove ones with error messages |
|---|
| 520 | $urls{$1} = 1 if $include_failures; |
|---|
| 521 | } else { |
|---|
| 522 | delete $urls{$_} if $exclude_successes; |
|---|
| 523 | } |
|---|
| 524 | } |
|---|
| 525 | [ keys %urls ]; |
|---|
| 526 | } |
|---|
| 527 | |
|---|
| 528 | sub to_ping_url_list { |
|---|
| 529 | my $entry = shift; |
|---|
| 530 | my $urls = $entry->to_ping_urls; |
|---|
| 531 | return [] unless $urls && $urls =~ /\S/; |
|---|
| 532 | [ split /\r?\n/, $urls ]; |
|---|
| 533 | } |
|---|
| 534 | |
|---|
| 535 | # TBD: Write a test for this routine |
|---|
| 536 | sub make_atom_id { |
|---|
| 537 | my $entry = shift; |
|---|
| 538 | |
|---|
| 539 | my $blog = $entry->blog; |
|---|
| 540 | my ($host, $year, $path, $blog_id, $entry_id); |
|---|
| 541 | $blog_id = $blog->id; |
|---|
| 542 | $entry_id = $entry->id; |
|---|
| 543 | my $url = $blog->site_url || ''; |
|---|
| 544 | return unless $url; |
|---|
| 545 | $url .= '/' unless $url =~ m!/$!; |
|---|
| 546 | if ($url && ($url =~ m!^https?://([^/:]+)(?::\d+)?(/.*)$!)) { |
|---|
| 547 | $host = $1; |
|---|
| 548 | $path = $2; |
|---|
| 549 | } |
|---|
| 550 | if ($entry->authored_on && ($entry->authored_on =~ m/^(\d{4})/)) { |
|---|
| 551 | $year = $1; |
|---|
| 552 | } |
|---|
| 553 | return unless $host && $year && $path && $blog_id && $entry_id; |
|---|
| 554 | qq{tag:$host,$year:$path/$blog_id.$entry_id}; |
|---|
| 555 | } |
|---|
| 556 | |
|---|
| 557 | sub discover_tb_from_entry { |
|---|
| 558 | my $entry = shift; |
|---|
| 559 | ## If we need to auto-discover TrackBack ping URLs, do that here. |
|---|
| 560 | my $cfg = MT->config; |
|---|
| 561 | my $blog = $entry->blog(); |
|---|
| 562 | my $send_tb = $cfg->OutboundTrackbackLimit; |
|---|
| 563 | if ($send_tb ne 'off' && |
|---|
| 564 | $blog && ($blog->autodiscover_links |
|---|
| 565 | || $blog->internal_autodiscovery)) { |
|---|
| 566 | my @tb_domains; |
|---|
| 567 | if ($send_tb eq 'selected') { |
|---|
| 568 | @tb_domains = $cfg->OutboundTrackbackDomains; |
|---|
| 569 | } elsif ($send_tb eq 'local') { |
|---|
| 570 | my $iter = MT::Blog->load_iter(); |
|---|
| 571 | while (my $b = $iter->()) { |
|---|
| 572 | next if $b->id == $blog->id; |
|---|
| 573 | push @tb_domains, extract_domain($b->site_url); |
|---|
| 574 | } |
|---|
| 575 | } |
|---|
| 576 | my $tb_domains; |
|---|
| 577 | if (@tb_domains) { |
|---|
| 578 | $tb_domains = ''; |
|---|
| 579 | my %seen; |
|---|
| 580 | foreach (@tb_domains) { |
|---|
| 581 | next unless $_; |
|---|
| 582 | $_ = lc($_); |
|---|
| 583 | next if $seen{$_}; |
|---|
| 584 | $tb_domains .= '|' if $tb_domains ne ''; |
|---|
| 585 | $tb_domains .= quotemeta($_); |
|---|
| 586 | $seen{$_} = 1; |
|---|
| 587 | } |
|---|
| 588 | $tb_domains = '(' . $tb_domains . ')' if $tb_domains; |
|---|
| 589 | } |
|---|
| 590 | my $archive_domain; |
|---|
| 591 | ($archive_domain) = extract_domains($blog->archive_url); |
|---|
| 592 | my %to_ping = map { $_ => 1 } @{ $entry->to_ping_url_list }; |
|---|
| 593 | my %pinged = map { $_ => 1 } @{ $entry->pinged_url_list(IncludeFailures => 1) }; |
|---|
| 594 | my $body = $entry->text . ($entry->text_more || ""); |
|---|
| 595 | $body = MT->apply_text_filters($body, $entry->text_filters); |
|---|
| 596 | while ($body =~ m!<a\s.*?\bhref\s*=\s*(["']?)([^'">]+)\1!gsi) { |
|---|
| 597 | my $url = $2; |
|---|
| 598 | my $url_domain; |
|---|
| 599 | ($url_domain) = extract_domains($url); |
|---|
| 600 | if ($url_domain =~ m/\Q$archive_domain\E$/i) { |
|---|
| 601 | next if !$blog->internal_autodiscovery; |
|---|
| 602 | } else { |
|---|
| 603 | next if !$blog->autodiscover_links; |
|---|
| 604 | } |
|---|
| 605 | next if $tb_domains && lc($url_domain) !~ m/$tb_domains$/; |
|---|
| 606 | if (my $item = discover_tb($url)) { |
|---|
| 607 | $to_ping{ $item->{ping_url} } = 1 |
|---|
| 608 | unless $pinged{$item->{ping_url}}; |
|---|
| 609 | } |
|---|
| 610 | } |
|---|
| 611 | $entry->to_ping_urls(join "\n", keys %to_ping); |
|---|
| 612 | } |
|---|
| 613 | } |
|---|
| 614 | |
|---|
| 615 | sub sync_assets { |
|---|
| 616 | my $entry = shift; |
|---|
| 617 | my $text = ($entry->text || '') . "\n" . ($entry->text_more || ''); |
|---|
| 618 | |
|---|
| 619 | require MT::ObjectAsset; |
|---|
| 620 | my @assets = MT::ObjectAsset->load({ |
|---|
| 621 | object_id => $entry->id, |
|---|
| 622 | blog_id => $entry->blog_id, |
|---|
| 623 | object_ds => $entry->datasource |
|---|
| 624 | }); |
|---|
| 625 | my %assets = map { $_->asset_id => $_->id } @assets; |
|---|
| 626 | while ($text =~ m!<form[^>]*?\smt:asset-id=["'](\d+)["'][^>]*?>(.+?)</form>!gis) { |
|---|
| 627 | my $id = $1; |
|---|
| 628 | my $innards = $2; |
|---|
| 629 | |
|---|
| 630 | # is asset exists? |
|---|
| 631 | my $asset = MT->model('asset')->load({ id => $id }) or next; |
|---|
| 632 | |
|---|
| 633 | # reference to an existing asset... |
|---|
| 634 | if (exists $assets{$id}) { |
|---|
| 635 | $assets{$id} = 0; |
|---|
| 636 | } else { |
|---|
| 637 | my $map = new MT::ObjectAsset; |
|---|
| 638 | $map->blog_id($entry->blog_id); |
|---|
| 639 | $map->asset_id($id); |
|---|
| 640 | $map->object_ds($entry->datasource); |
|---|
| 641 | $map->object_id($entry->id); |
|---|
| 642 | $map->save; |
|---|
| 643 | $assets{$id} = 0; |
|---|
| 644 | } |
|---|
| 645 | } |
|---|
| 646 | if (my @old_maps = grep { $assets{$_->asset_id} } @assets) { |
|---|
| 647 | my @old_ids = map { $_->id } @old_maps; |
|---|
| 648 | MT::ObjectAsset->remove( { id => \@old_ids }); |
|---|
| 649 | } |
|---|
| 650 | return 1; |
|---|
| 651 | } |
|---|
| 652 | |
|---|
| 653 | sub save { |
|---|
| 654 | my $entry = shift; |
|---|
| 655 | my $is_new = $entry->id ? 0 : 1; |
|---|
| 656 | |
|---|
| 657 | ## If there's no basename specified, create a unique basename. |
|---|
| 658 | if (!defined($entry->basename) || ($entry->basename eq '')) { |
|---|
| 659 | my $name = MT::Util::make_unique_basename($entry); |
|---|
| 660 | $entry->basename($name); |
|---|
| 661 | } |
|---|
| 662 | if (!$entry->id && !$entry->authored_on) { |
|---|
| 663 | my @ts = MT::Util::offset_time_list(time, $entry->blog_id); |
|---|
| 664 | my $ts = sprintf '%04d%02d%02d%02d%02d%02d', |
|---|
| 665 | $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0]; |
|---|
| 666 | $entry->authored_on($ts); |
|---|
| 667 | } |
|---|
| 668 | if (my $dt = $entry->authored_on_obj) { |
|---|
| 669 | my ($yr, $w) = $dt->week; |
|---|
| 670 | $entry->week_number($yr * 100 + $w); |
|---|
| 671 | } |
|---|
| 672 | |
|---|
| 673 | my $sync_assets = $entry->is_changed('text') |
|---|
| 674 | || $entry->is_changed('text_more'); |
|---|
| 675 | |
|---|
| 676 | unless ($entry->SUPER::save(@_)) { |
|---|
| 677 | print STDERR "error during save: " . $entry->errstr . "\n"; |
|---|
| 678 | die $entry->errstr; |
|---|
| 679 | } |
|---|
| 680 | |
|---|
| 681 | $entry->sync_assets() if $sync_assets; |
|---|
| 682 | |
|---|
| 683 | if (!$entry->atom_id && (($entry->status || 0) != HOLD)) { |
|---|
| 684 | $entry->atom_id($entry->make_atom_id()); |
|---|
| 685 | $entry->SUPER::save(@_) if $entry->atom_id; |
|---|
| 686 | } |
|---|
| 687 | |
|---|
| 688 | ## If pings are allowed on this entry, create or update |
|---|
| 689 | ## the corresponding TrackBack object for this entry. |
|---|
| 690 | require MT::Trackback; |
|---|
| 691 | if ($entry->allow_pings) { |
|---|
| 692 | my $tb; |
|---|
| 693 | unless ($tb = $entry->trackback) { |
|---|
| 694 | $tb = MT::Trackback->new; |
|---|
| 695 | $tb->blog_id($entry->blog_id); |
|---|
| 696 | $tb->entry_id($entry->id); |
|---|
| 697 | $tb->category_id(0); ## category_id can't be NULL |
|---|
| 698 | } |
|---|
| 699 | $tb->title($entry->title); |
|---|
| 700 | $tb->description($entry->get_excerpt); |
|---|
| 701 | $tb->url($entry->permalink); |
|---|
| 702 | $tb->is_disabled(0); |
|---|
| 703 | $tb->save |
|---|
| 704 | or return $entry->error($tb->errstr); |
|---|
| 705 | $entry->trackback($tb); |
|---|
| 706 | } else { |
|---|
| 707 | ## If there is a TrackBack item for this entry, but |
|---|
| 708 | ## pings are now disabled, make sure that we mark the |
|---|
| 709 | ## object as disabled. |
|---|
| 710 | if (my $tb = $entry->trackback) { |
|---|
| 711 | $tb->is_disabled(1); |
|---|
| 712 | $tb->save |
|---|
| 713 | or return $entry->error($tb->errstr); |
|---|
| 714 | } |
|---|
| 715 | } |
|---|
| 716 | |
|---|
| 717 | delete $entry->{__next} if exists $entry->{__next}; |
|---|
| 718 | delete $entry->{__previous} if exists $entry->{__previous}; |
|---|
| 719 | delete $entry->{__cache}{category} |
|---|
| 720 | if $is_new && exists $entry->{__cache}{category}; |
|---|
| 721 | 1; |
|---|
| 722 | } |
|---|
| 723 | |
|---|
| 724 | sub remove { |
|---|
| 725 | my $entry = shift; |
|---|
| 726 | if (ref $entry) { |
|---|
| 727 | $entry->remove_children({ key => 'entry_id' }) or return; |
|---|
| 728 | |
|---|
| 729 | # Remove MT::ObjectAsset records |
|---|
| 730 | my $class = MT->model('objectasset'); |
|---|
| 731 | my $iter = $class->load_iter({ object_id => $entry->id, object_ds => $entry->class_type }); |
|---|
| 732 | while (my $o = $iter->()) { |
|---|
| 733 | $o->remove; |
|---|
| 734 | } |
|---|
| 735 | } |
|---|
| 736 | |
|---|
| 737 | |
|---|
| 738 | $entry->SUPER::remove(@_); |
|---|
| 739 | } |
|---|
| 740 | |
|---|
| 741 | sub blog { |
|---|
| 742 | my ($entry) = @_; |
|---|
| 743 | $entry->cache_property('blog', sub { |
|---|
| 744 | my $blog_id = $entry->blog_id; |
|---|
| 745 | require MT::Blog; |
|---|
| 746 | MT::Blog->load($blog_id) or |
|---|
| 747 | $entry->error(MT->translate( |
|---|
| 748 | "Load of blog '[_1]' failed: [_2]", $blog_id, MT::Blog->errstr)); |
|---|
| 749 | }); |
|---|
| 750 | } |
|---|
| 751 | |
|---|
| 752 | sub to_hash { |
|---|
| 753 | my $entry = shift; |
|---|
| 754 | my $hash = $entry->SUPER::to_hash(@_); |
|---|
| 755 | |
|---|
| 756 | $hash->{'entry.text_html'} = sub { MT->apply_text_filters($entry->text, $entry->text_filters) }; |
|---|
| 757 | $hash->{'entry.text_more_html'} = sub { MT->apply_text_filters($entry->text_more, $entry->text_filters) }; |
|---|
| 758 | $hash->{'entry.permalink'} = $entry->permalink; |
|---|
| 759 | $hash->{'entry.status_text'} = $entry->status_text; |
|---|
| 760 | $hash->{'entry.status_is_' . $entry->status} = 1; |
|---|
| 761 | $hash->{'entry.created_on_iso'} = sub { MT::Util::ts2iso($entry->blog_id, $entry->created_on) }; |
|---|
| 762 | $hash->{'entry.modified_on_iso'} = sub { MT::Util::ts2iso($entry->blog_id, $entry->modified_on) }; |
|---|
| 763 | $hash->{'entry.authored_on_iso'} = sub { MT::Util::ts2iso($entry->blog_id, $entry->authored_on) }; |
|---|
| 764 | |
|---|
| 765 | # Populate author info |
|---|
| 766 | my $auth = $entry->author or return $hash; |
|---|
| 767 | my $auth_hash = $auth->to_hash; |
|---|
| 768 | $hash->{"entry.$_"} = $auth_hash->{$_} foreach keys %$auth_hash; |
|---|
| 769 | |
|---|
| 770 | $hash; |
|---|
| 771 | } |
|---|
| 772 | |
|---|
| 773 | #trans('Draft') |
|---|
| 774 | #trans('Review') |
|---|
| 775 | #trans('Future') |
|---|
| 776 | |
|---|
| 777 | 1; |
|---|
| 778 | __END__ |
|---|
| 779 | |
|---|
| 780 | =head1 NAME |
|---|
| 781 | |
|---|
| 782 | MT::Entry - Movable Type entry record |
|---|
| 783 | |
|---|
| 784 | =head1 SYNOPSIS |
|---|
| 785 | |
|---|
| 786 | use MT::Entry; |
|---|
| 787 | my $entry = MT::Entry->new; |
|---|
| 788 | $entry->blog_id($blog->id); |
|---|
| 789 | $entry->status(MT::Entry::RELEASE()); |
|---|
| 790 | $entry->author_id($author->id); |
|---|
| 791 | $entry->title('My title'); |
|---|
| 792 | $entry->text('Some text'); |
|---|
| 793 | $entry->save |
|---|
| 794 | or die $entry->errstr; |
|---|
| 795 | |
|---|
| 796 | =head1 DESCRIPTION |
|---|
| 797 | |
|---|
| 798 | An I<MT::Entry> object represents an entry in the Movable Type system. It |
|---|
| 799 | contains all of the metadata about the entry (author, status, category, etc.), |
|---|
| 800 | as well as the actual body (and extended body) of the entry. |
|---|
| 801 | |
|---|
| 802 | =head1 USAGE |
|---|
| 803 | |
|---|
| 804 | As a subclass of I<MT::Object>, I<MT::Entry> inherits all of the |
|---|
| 805 | data-management and -storage methods from that class; thus you should look |
|---|
| 806 | at the I<MT::Object> documentation for details about creating a new object, |
|---|
| 807 | loading an existing object, saving an object, etc. |
|---|
| 808 | |
|---|
| 809 | The following methods are unique to the I<MT::Entry> interface: |
|---|
| 810 | |
|---|
| 811 | =head2 $entry->next |
|---|
| 812 | |
|---|
| 813 | Loads and returns the next entry, where "next" is defined as the next record |
|---|
| 814 | in ascending chronological order (the entry posted after the current entry). |
|---|
| 815 | entry I<$entry>). |
|---|
| 816 | |
|---|
| 817 | Returns an I<MT::Entry> object representing this next entry; if there is not |
|---|
| 818 | a next entry, returns C<undef>. |
|---|
| 819 | |
|---|
| 820 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 821 | re-query the database. |
|---|
| 822 | |
|---|
| 823 | =head2 $entry->previous |
|---|
| 824 | |
|---|
| 825 | Loads and returns the previous entry, where "previous" is defined as the |
|---|
| 826 | previous record in ascending chronological order (the entry posted before the |
|---|
| 827 | current entry I<$entry>). |
|---|
| 828 | |
|---|
| 829 | Returns an I<MT::Entry> object representing this previous entry; if there is |
|---|
| 830 | not a next entry, returns C<undef>. |
|---|
| 831 | |
|---|
| 832 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 833 | re-query the database. |
|---|
| 834 | |
|---|
| 835 | =head2 $entry->author |
|---|
| 836 | |
|---|
| 837 | Returns an I<MT::Author> object representing the author of the entry |
|---|
| 838 | I<$entry>. If the author record has been removed, returns C<undef>. |
|---|
| 839 | |
|---|
| 840 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 841 | re-query the database. |
|---|
| 842 | |
|---|
| 843 | =head2 $entry->category |
|---|
| 844 | |
|---|
| 845 | Returns an I<MT::Category> object representing the primary category of the |
|---|
| 846 | entry I<$entry>. If a primary category has not been assigned, returns |
|---|
| 847 | C<undef>. |
|---|
| 848 | |
|---|
| 849 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 850 | re-query the database. |
|---|
| 851 | |
|---|
| 852 | =head2 $entry->categories |
|---|
| 853 | |
|---|
| 854 | Returns a reference to an array of I<MT::Category> objects representing the |
|---|
| 855 | categories to which the entry I<$entry> has been assigned (both primary and |
|---|
| 856 | secondary categories). If the entry has not been assigned to any categories, |
|---|
| 857 | returns a reference to an empty array. |
|---|
| 858 | |
|---|
| 859 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 860 | re-query the database. |
|---|
| 861 | |
|---|
| 862 | =head2 $entry->is_in_category($cat) |
|---|
| 863 | |
|---|
| 864 | Returns true if the entry I<$entry> has been assigned to entry I<$cat>, false |
|---|
| 865 | otherwise. |
|---|
| 866 | |
|---|
| 867 | =head2 $entry->comments |
|---|
| 868 | |
|---|
| 869 | Returns a reference to an array of I<MT::Comment> objects representing the |
|---|
| 870 | comments made on the entry I<$entry>. If no comments have been made on the |
|---|
| 871 | entry, returns a reference to an empty array. |
|---|
| 872 | |
|---|
| 873 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 874 | re-query the database. |
|---|
| 875 | |
|---|
| 876 | =head2 $entry->comment_count |
|---|
| 877 | |
|---|
| 878 | Returns the number of comments made on this entry. |
|---|
| 879 | |
|---|
| 880 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 881 | re-query the database. |
|---|
| 882 | |
|---|
| 883 | =head2 $entry->ping_count |
|---|
| 884 | |
|---|
| 885 | Returns the number of TrackBack pings made on this entry. |
|---|
| 886 | |
|---|
| 887 | Caches the return value internally so that subsequent calls will not have to |
|---|
| 888 | re-query the database. |
|---|
| 889 | |
|---|
| 890 | =head2 $entry->archive_file([ $archive_type ]) |
|---|
| 891 | |
|---|
| 892 | Returns the name of/path to the archive file for the entry I<$entry>. If |
|---|
| 893 | I<$archive_type> is not specified, and you are using multiple archive types |
|---|
| 894 | for your blog, the path is created from the preferred archive type that you |
|---|
| 895 | have selected. If I<$archive_type> is specified, it should be one of the |
|---|
| 896 | following values: C<Individual>, C<Daily>, C<Weekly>, C<Monthly>, and |
|---|
| 897 | C<Category>. |
|---|
| 898 | |
|---|
| 899 | =head2 $entry->archive_url([ $archive_type ]) |
|---|
| 900 | |
|---|
| 901 | Returns the absolute URL to the archive page for the entry I<$entry>. This |
|---|
| 902 | calls I<archive_file> internally, so if I<$archive_type> is specified, it |
|---|
| 903 | is merely passed through to that method. In other words, this is the |
|---|
| 904 | blog Archive URL plus the results of I<archive_file>. |
|---|
| 905 | |
|---|
| 906 | =head2 $entry->permalink([ $archive_type ]) |
|---|
| 907 | |
|---|
| 908 | Returns the (smart) permalink for the entry I<$entry>. Internally this calls |
|---|
| 909 | I<archive_url>, which calls I<archive_file>, so I<$archive_type> (if |
|---|
| 910 | specified) is merely passed through to that method. The result of this |
|---|
| 911 | method is the same as I<archive_url> plus the URI fragment |
|---|
| 912 | (C<#entry_id>), unless the preferred archive type is Individual, in which |
|---|
| 913 | case the two methods give exactly the same results. |
|---|
| 914 | |
|---|
| 915 | =head2 $entry->text_filters |
|---|
| 916 | |
|---|
| 917 | Returns a reference to an array of text filter keynames (the short names |
|---|
| 918 | that are the first argument to I<MT::add_text_filter>. This list can be |
|---|
| 919 | passed directly in as the second argument to I<MT::apply_text_filters>. |
|---|
| 920 | |
|---|
| 921 | =head1 DATA ACCESS METHODS |
|---|
| 922 | |
|---|
| 923 | The I<MT::Entry> object holds the following pieces of data. These fields can |
|---|
| 924 | be accessed and set using the standard data access methods described in the |
|---|
| 925 | I<MT::Object> documentation. |
|---|
| 926 | |
|---|
| 927 | =over 4 |
|---|
| 928 | |
|---|
| 929 | =item * id |
|---|
| 930 | |
|---|
| 931 | The numeric ID of the entry. |
|---|
| 932 | |
|---|
| 933 | =item * blog_id |
|---|
| 934 | |
|---|
| 935 | The numeric ID of the blog in which this entry has been posted. |
|---|
| 936 | |
|---|
| 937 | =item * author_id |
|---|
| 938 | |
|---|
| 939 | The numeric ID of the author who posted this entry. |
|---|
| 940 | |
|---|
| 941 | =item * status |
|---|
| 942 | |
|---|
| 943 | The status of the entry, either Publish (C<2>) or Draft (C<1>). |
|---|
| 944 | |
|---|
| 945 | =item * allow_comments |
|---|
| 946 | |
|---|
| 947 | An integer flag specifying whether comments are allowed on this entry. This |
|---|
| 948 | setting determines whether C<E<lt>MTEntryIfAllowCommentsE<gt>> containers are |
|---|
| 949 | displayed for this entry. Possible values are 0 for no comments, 1 for open |
|---|
| 950 | comments and 2 for closed comments (that is, display the comments on this |
|---|
| 951 | entry but do not allow new comments to be added). |
|---|
| 952 | |
|---|
| 953 | =item * convert_breaks |
|---|
| 954 | |
|---|
| 955 | A boolean flag specifying whether line and paragraph breaks should be converted |
|---|
| 956 | when rebuilding this entry. |
|---|
| 957 | |
|---|
| 958 | =item * title |
|---|
| 959 | |
|---|
| 960 | The title of the entry. |
|---|
| 961 | |
|---|
| 962 | =item * excerpt |
|---|
| 963 | |
|---|
| 964 | The excerpt of the entry. |
|---|
| 965 | |
|---|
| 966 | =item * text |
|---|
| 967 | |
|---|
| 968 | The main body text of the entry. |
|---|
| 969 | |
|---|
| 970 | =item * text_more |
|---|
| 971 | |
|---|
| 972 | The extended body text of the entry. |
|---|
| 973 | |
|---|
| 974 | =item * created_on |
|---|
| 975 | |
|---|
| 976 | The timestamp denoting when the entry record was created, in the format |
|---|
| 977 | C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted for the |
|---|
| 978 | selected timezone. |
|---|
| 979 | |
|---|
| 980 | =item * modified_on |
|---|
| 981 | |
|---|
| 982 | The timestamp denoting when the entry record was last modified, in the |
|---|
| 983 | format C<YYYYMMDDHHMMSS>. Note that the timestamp has already been adjusted |
|---|
| 984 | for the selected timezone. |
|---|
| 985 | |
|---|
| 986 | =back |
|---|
| 987 | |
|---|
| 988 | =head1 DATA LOOKUP |
|---|
| 989 | |
|---|
| 990 | In addition to numeric ID lookup, you can look up or sort records by any |
|---|
| 991 | combination of the following fields. See the I<load> documentation in |
|---|
| 992 | I<MT::Object> for more information. |
|---|
| 993 | |
|---|
| 994 | =over 4 |
|---|
| 995 | |
|---|
| 996 | =item * blog_id |
|---|
| 997 | |
|---|
| 998 | =item * status |
|---|
| 999 | |
|---|
| 1000 | =item * author_id |
|---|
| 1001 | |
|---|
| 1002 | =item * created_on |
|---|
| 1003 | |
|---|
| 1004 | =item * modified_on |
|---|
| 1005 | |
|---|
| 1006 | =back |
|---|
| 1007 | |
|---|
| 1008 | =head1 NOTES |
|---|
| 1009 | |
|---|
| 1010 | =over 4 |
|---|
| 1011 | |
|---|
| 1012 | =item * |
|---|
| 1013 | |
|---|
| 1014 | When you remove an entry using I<MT::Entry::remove>, in addition to removing |
|---|
| 1015 | the entry record, all of the comments and placements (I<MT::Comment> and |
|---|
| 1016 | I<MT::Placement> records, respectively) for this entry will also be removed. |
|---|
| 1017 | |
|---|
| 1018 | =back |
|---|
| 1019 | |
|---|
| 1020 | =head1 AUTHOR & COPYRIGHTS |
|---|
| 1021 | |
|---|
| 1022 | Please see the I<MT> manpage for author, copyright, and license information. |
|---|
| 1023 | |
|---|
| 1024 | =cut |
|---|