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