| 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::Template; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use base qw( MT::Object ); |
|---|
| 11 | use MT::Util qw( weaken ); |
|---|
| 12 | |
|---|
| 13 | sub NODE () { 'MT::Template::Node' } |
|---|
| 14 | |
|---|
| 15 | sub NODE_TEXT () { 1 } |
|---|
| 16 | sub NODE_BLOCK () { 2 } |
|---|
| 17 | sub NODE_FUNCTION () { 3 } |
|---|
| 18 | |
|---|
| 19 | my $resync_to_db; |
|---|
| 20 | |
|---|
| 21 | __PACKAGE__->install_properties({ |
|---|
| 22 | column_defs => { |
|---|
| 23 | 'id' => 'integer not null auto_increment', |
|---|
| 24 | 'blog_id' => 'integer not null', |
|---|
| 25 | 'name' => 'string(255) not null', |
|---|
| 26 | 'type' => 'string(25) not null', |
|---|
| 27 | 'outfile' => 'string(255)', |
|---|
| 28 | 'text' => 'text', |
|---|
| 29 | 'linked_file' => 'string(255)', |
|---|
| 30 | 'linked_file_mtime' => 'string(10)', |
|---|
| 31 | 'linked_file_size' => 'integer', |
|---|
| 32 | 'rebuild_me' => 'boolean', |
|---|
| 33 | 'build_dynamic' => 'boolean', |
|---|
| 34 | 'identifier' => 'string(50)', |
|---|
| 35 | 'build_type' => 'smallint', |
|---|
| 36 | 'build_interval' => 'integer', |
|---|
| 37 | |
|---|
| 38 | # meta properties |
|---|
| 39 | 'last_rebuild_time' => 'integer meta', |
|---|
| 40 | 'page_layout' => 'string meta', |
|---|
| 41 | 'include_with_ssi' => 'integer meta', |
|---|
| 42 | 'cache_expire_type' => 'integer meta', |
|---|
| 43 | 'cache_expire_interval' => 'integer meta', |
|---|
| 44 | 'cache_expire_event' => 'string meta', |
|---|
| 45 | 'cache_path' => 'string meta', |
|---|
| 46 | }, |
|---|
| 47 | indexes => { |
|---|
| 48 | blog_id => 1, |
|---|
| 49 | name => 1, |
|---|
| 50 | type => 1, |
|---|
| 51 | outfile => 1, |
|---|
| 52 | identifier => 1, |
|---|
| 53 | }, |
|---|
| 54 | defaults => { |
|---|
| 55 | 'rebuild_me' => 1, |
|---|
| 56 | 'build_dynamic' => 0, |
|---|
| 57 | 'build_type' => 1, |
|---|
| 58 | }, |
|---|
| 59 | meta => 1, |
|---|
| 60 | child_of => 'MT::Blog', |
|---|
| 61 | child_classes => [ 'MT::TemplateMap', 'MT::FileInfo' ], |
|---|
| 62 | audit => 1, |
|---|
| 63 | datasource => 'template', |
|---|
| 64 | primary_key => 'id', |
|---|
| 65 | }); |
|---|
| 66 | __PACKAGE__->add_trigger('pre_remove' => \&pre_remove_children); |
|---|
| 67 | |
|---|
| 68 | use MT::Builder; |
|---|
| 69 | use MT::Blog; |
|---|
| 70 | use File::Spec; |
|---|
| 71 | |
|---|
| 72 | sub class_label { |
|---|
| 73 | MT->translate("Template"); |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | sub class_label_plural { |
|---|
| 77 | MT->translate("Templates"); |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | sub new { |
|---|
| 81 | my $pkg = shift; |
|---|
| 82 | my (%param) = @_; |
|---|
| 83 | if (my $type = delete $param{type}) { |
|---|
| 84 | if ($type eq 'filename') { |
|---|
| 85 | return $pkg->new_file($param{source}, %param); |
|---|
| 86 | } elsif ($type eq 'scalarref') { |
|---|
| 87 | return $pkg->new_string($param{source}, %param); |
|---|
| 88 | } |
|---|
| 89 | } |
|---|
| 90 | my $tmpl = $pkg->SUPER::new(@_); |
|---|
| 91 | $tmpl->{include_path} = $param{path}; |
|---|
| 92 | $tmpl->{include_filter} = $param{filter}; |
|---|
| 93 | return $tmpl; |
|---|
| 94 | } |
|---|
| 95 | |
|---|
| 96 | sub new_file { |
|---|
| 97 | my $pkg = shift; |
|---|
| 98 | my ($file, %param) = @_; |
|---|
| 99 | my $tmpl = $pkg->new; |
|---|
| 100 | $tmpl->{include_path} = $param{path}; |
|---|
| 101 | $tmpl->{include_filter} = $param{filter}; |
|---|
| 102 | $tmpl->{__file} = $file; |
|---|
| 103 | my $contents = $tmpl->load_file($file); |
|---|
| 104 | if (defined $contents) { |
|---|
| 105 | if ($tmpl->{include_filter}) { |
|---|
| 106 | $tmpl->{include_filter}->(\$contents, $file); |
|---|
| 107 | } |
|---|
| 108 | $tmpl->text($contents); |
|---|
| 109 | return $tmpl; |
|---|
| 110 | } |
|---|
| 111 | return; # load_file errror; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | sub new_string { |
|---|
| 115 | my $pkg = shift; |
|---|
| 116 | my ($str, %param) = @_; |
|---|
| 117 | my $tmpl = $pkg->new; |
|---|
| 118 | $tmpl->{include_path} = $param{path}; |
|---|
| 119 | $tmpl->{include_filter} = $param{filter}; |
|---|
| 120 | if (ref($str) && defined($$str)) { |
|---|
| 121 | if ($tmpl->{include_filter}) { |
|---|
| 122 | $tmpl->{include_filter}->($str); |
|---|
| 123 | } |
|---|
| 124 | $tmpl->text($$str); |
|---|
| 125 | } |
|---|
| 126 | return $tmpl; |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | sub load_file { |
|---|
| 130 | my $tmpl = shift; |
|---|
| 131 | my ($file) = @_; |
|---|
| 132 | unless (File::Spec->file_name_is_absolute($file)) { |
|---|
| 133 | my @paths = @{ $tmpl->{include_path} || [] }; |
|---|
| 134 | foreach my $path (@paths) { |
|---|
| 135 | my $test_file = File::Spec->catfile($path, $file); |
|---|
| 136 | $file = $test_file, last if -f $test_file; |
|---|
| 137 | } |
|---|
| 138 | } |
|---|
| 139 | return $tmpl->trans_error("File not found: [_1]", $file) unless -e $file; |
|---|
| 140 | local *FH; |
|---|
| 141 | open FH, $file |
|---|
| 142 | or return $tmpl->trans_error("Error reading file '[_1]': [_2]", $file, $!); |
|---|
| 143 | my $c; |
|---|
| 144 | do { local $/; $c = <FH> }; |
|---|
| 145 | close FH; |
|---|
| 146 | return $c; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | sub context { |
|---|
| 150 | my $tmpl = shift; |
|---|
| 151 | return $tmpl->{context} = shift if @_; |
|---|
| 152 | require MT::Template::Context; |
|---|
| 153 | my $ctx = $tmpl->{context} ||= MT::Template::Context->new; |
|---|
| 154 | weaken($ctx->{__stash}{'template'} = $tmpl); |
|---|
| 155 | return $ctx; |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | sub param { |
|---|
| 159 | my $tmpl = shift; |
|---|
| 160 | my $ctx = $tmpl->context; |
|---|
| 161 | if (@_ == 1) { |
|---|
| 162 | if (ref($_[0]) eq 'HASH') { |
|---|
| 163 | $ctx->var($_, $_[0]->{$_}) for keys %{ $_[0] }; |
|---|
| 164 | } else { |
|---|
| 165 | return $ctx->var($_[0]); |
|---|
| 166 | } |
|---|
| 167 | } elsif (@_ == 2) { |
|---|
| 168 | $ctx->var($_[0], $_[1]); |
|---|
| 169 | } else { |
|---|
| 170 | return $ctx->{__stash}{vars}; |
|---|
| 171 | } |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | sub clear_params { |
|---|
| 175 | my $tmpl = shift; |
|---|
| 176 | my $ctx = $tmpl->context; |
|---|
| 177 | %{$ctx->{__stash}{vars}} = (); |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | sub reflow { |
|---|
| 181 | my $tmpl = shift; |
|---|
| 182 | my ($tokens) = @_; |
|---|
| 183 | $tokens ||= $tmpl->tokens; |
|---|
| 184 | |
|---|
| 185 | # reconstitute text of template based on tokens |
|---|
| 186 | my $str = ''; |
|---|
| 187 | foreach my $token (@$tokens) { |
|---|
| 188 | if ($token->[0] eq 'TEXT') { |
|---|
| 189 | $str .= $token->[1]; |
|---|
| 190 | } else { |
|---|
| 191 | my $tag = $token->[0]; |
|---|
| 192 | $str .= '<mt' . $tag; |
|---|
| 193 | if (my $attrs = $token->[4]) { |
|---|
| 194 | my $attrh = $token->[1]; |
|---|
| 195 | foreach my $a (@$attrs) { |
|---|
| 196 | delete $attrh->{$a->[0]}; |
|---|
| 197 | my $v = $a->[1]; |
|---|
| 198 | $v = $v =~ m/"/ ? qq{'$v'} : qq{"$v"}; |
|---|
| 199 | $str .= ' ' . $a->[0] . '=' . $v; |
|---|
| 200 | } |
|---|
| 201 | foreach my $a (keys %$attrh) { |
|---|
| 202 | my $v = $attrh->{$a}; |
|---|
| 203 | $v = $v =~ m/"/ ? qq{'$v'} : qq{"$v"}; |
|---|
| 204 | $str .= ' ' . $a . '=' . $v; |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | $str .= '>'; |
|---|
| 208 | if ($token->[2]) { |
|---|
| 209 | # container tag |
|---|
| 210 | $str .= $tmpl->reflow( $token->[2] ); |
|---|
| 211 | $str .= '</mt' . $tag . '>'; |
|---|
| 212 | } |
|---|
| 213 | } |
|---|
| 214 | } |
|---|
| 215 | return $str; |
|---|
| 216 | } |
|---|
| 217 | |
|---|
| 218 | sub build { |
|---|
| 219 | my $tmpl = shift; |
|---|
| 220 | my($ctx, $cond) = @_; |
|---|
| 221 | $ctx ||= $tmpl->context; |
|---|
| 222 | |
|---|
| 223 | my $timer = MT->get_timer(); |
|---|
| 224 | local $timer->{elapsed} = 0 if $timer; |
|---|
| 225 | |
|---|
| 226 | local $ctx->{__stash}{template} = $tmpl; |
|---|
| 227 | my $tokens = $tmpl->tokens |
|---|
| 228 | or return; |
|---|
| 229 | my $build = $ctx->{__stash}{builder} || MT::Builder->new; |
|---|
| 230 | my $page_layout; |
|---|
| 231 | if (my $blog_id = $tmpl->blog_id) { |
|---|
| 232 | $ctx->stash('blog_id', $blog_id); |
|---|
| 233 | my $blog = $ctx->stash('blog'); |
|---|
| 234 | unless ($blog) { |
|---|
| 235 | $blog = MT::Blog->load($blog_id) or |
|---|
| 236 | return $tmpl->error(MT->translate( |
|---|
| 237 | "Load of blog '[_1]' failed: [_2]", $blog_id, MT::Blog->errstr )); |
|---|
| 238 | $ctx->stash('blog', $blog); |
|---|
| 239 | } else { |
|---|
| 240 | $ctx->stash('blog_id', $blog->id); |
|---|
| 241 | } |
|---|
| 242 | MT->config->TimeOffset($blog->server_offset); |
|---|
| 243 | $page_layout = $blog->page_layout; |
|---|
| 244 | } |
|---|
| 245 | $page_layout = $tmpl->page_layout if $tmpl->page_layout; |
|---|
| 246 | $ctx->var( 'page_layout', $page_layout ) |
|---|
| 247 | unless $ctx->var('page_layout'); |
|---|
| 248 | if (my $layout = $ctx->var('page_layout')) { |
|---|
| 249 | my $columns = { |
|---|
| 250 | 'layout-wt' => 2, |
|---|
| 251 | 'layout-tw' => 2, |
|---|
| 252 | 'layout-wm' => 2, |
|---|
| 253 | 'layout-mw' => 2, |
|---|
| 254 | 'layout-wtt' => 3, |
|---|
| 255 | 'layout-twt' => 3, |
|---|
| 256 | }->{$layout}; |
|---|
| 257 | $ctx->var( 'page_columns', $columns ) if $columns; |
|---|
| 258 | } |
|---|
| 259 | $ctx->var( $tmpl->identifier, 1 ) if defined $tmpl->identifier; |
|---|
| 260 | |
|---|
| 261 | $timer->pause_partial if $timer; |
|---|
| 262 | |
|---|
| 263 | my $res = $build->build($ctx, $tokens, $cond); |
|---|
| 264 | |
|---|
| 265 | if ($timer) { |
|---|
| 266 | $timer->mark("MT::Template::build[" . ($tmpl->name || $tmpl->{__file}).']'); |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | unless (defined($res)) { |
|---|
| 270 | return $tmpl->error(MT->translate( |
|---|
| 271 | "Publish error in template '[_1]': [_2]", |
|---|
| 272 | $tmpl->name || $tmpl->{__file}, $build->errstr)); |
|---|
| 273 | } |
|---|
| 274 | $res =~ s/^\s*//; |
|---|
| 275 | return $res; |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | sub output { |
|---|
| 279 | my $tmpl = shift; |
|---|
| 280 | my ($param) = @_; |
|---|
| 281 | $tmpl->param($param) if $param; |
|---|
| 282 | return $tmpl->build(); |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | sub save { |
|---|
| 286 | my $tmpl = shift; |
|---|
| 287 | my $existing = MT::Template->load({ name => $tmpl->name, blog_id => $tmpl->blog_id }); |
|---|
| 288 | if ($existing && (!$tmpl->id || ($tmpl->id && ($existing->id ne $tmpl->id))) |
|---|
| 289 | && ($existing->type eq $tmpl->type)) { |
|---|
| 290 | return $tmpl->error(MT->translate('Template with the same name already exists in this blog.')); |
|---|
| 291 | } |
|---|
| 292 | |
|---|
| 293 | if ($tmpl->id && ($tmpl->is_changed('build_type'))) { |
|---|
| 294 | # check for templatemaps, and update them appropriately |
|---|
| 295 | require MT::TemplateMap; |
|---|
| 296 | require MT::PublishOption; |
|---|
| 297 | my @maps = MT::TemplateMap->load({ template_id => $tmpl->id }); |
|---|
| 298 | foreach my $map (@maps) { |
|---|
| 299 | $map->build_type($tmpl->build_type); |
|---|
| 300 | $map->save or die $map->errstr; |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | if ($tmpl->linked_file) { |
|---|
| 305 | $tmpl->_sync_to_disk($tmpl->SUPER::text) or return; |
|---|
| 306 | } |
|---|
| 307 | $tmpl->{needs_db_sync} = 0; |
|---|
| 308 | |
|---|
| 309 | # if ((!$tmpl->id) && (my $blog = $tmpl->blog)) { |
|---|
| 310 | # my $dcty = $blog->custom_dynamic_templates; |
|---|
| 311 | # if ($dcty eq 'all') { |
|---|
| 312 | # if (('index' eq $tmpl->type) || ('archive' eq $tmpl->type) || |
|---|
| 313 | # ('individual' eq $tmpl->type) || ('page' eq $tmpl->type) || |
|---|
| 314 | # ('category' eq $tmpl->type)) { |
|---|
| 315 | # $tmpl->build_dynamic(1); |
|---|
| 316 | # } |
|---|
| 317 | # } elsif ($dcty eq 'archives') { |
|---|
| 318 | # if (('archive' eq $tmpl->type) || ('page' eq $tmpl->type) || |
|---|
| 319 | # ('individual' eq $tmpl->type) || ('category' eq $tmpl->type)) { |
|---|
| 320 | # $tmpl->build_dynamic(1); |
|---|
| 321 | # } |
|---|
| 322 | # } |
|---|
| 323 | # } |
|---|
| 324 | |
|---|
| 325 | $tmpl->SUPER::save; |
|---|
| 326 | } |
|---|
| 327 | |
|---|
| 328 | sub build_dynamic { |
|---|
| 329 | my $tmpl = shift; |
|---|
| 330 | return $tmpl->SUPER::build_dynamic($_[0]) if @_; |
|---|
| 331 | require MT::PublishOption; |
|---|
| 332 | return 1 if $tmpl->build_type == MT::PublishOption::DYNAMIC(); |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | sub blog { |
|---|
| 336 | my $this = shift; |
|---|
| 337 | return $this->{__blog} if $this->{__blog}; |
|---|
| 338 | return $this->{__blog} = MT::Blog->load($this->blog_id); |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | sub set_values_internal { |
|---|
| 342 | my $tmpl = shift; |
|---|
| 343 | my ($cols) = @_; |
|---|
| 344 | if (exists $cols->{text}) { |
|---|
| 345 | # The text column of the MT::Template object can be associated |
|---|
| 346 | # with a physical file. When loading the record data from the |
|---|
| 347 | # database, we should observe whether or not it is in sync with |
|---|
| 348 | # the physical file. This logic handles the case where the |
|---|
| 349 | # record is loaded through the MT::ObjectDriver and should |
|---|
| 350 | # not apply for any other use of $tmpl->set_values, since those |
|---|
| 351 | # should all be explicitly setting the template text. |
|---|
| 352 | my @info = caller(); |
|---|
| 353 | if ($info[0] =~ m/^MT::ObjectDriver::/) { |
|---|
| 354 | if ($cols->{linked_file}) { |
|---|
| 355 | my %local_cols = %$cols; |
|---|
| 356 | delete $local_cols{text}; |
|---|
| 357 | $tmpl->SUPER::set_values_internal(\%local_cols); |
|---|
| 358 | my $sync_text = $tmpl->text(); |
|---|
| 359 | if (!defined $sync_text) { |
|---|
| 360 | $tmpl->text($cols->{text}); |
|---|
| 361 | } |
|---|
| 362 | return; |
|---|
| 363 | } |
|---|
| 364 | } |
|---|
| 365 | } |
|---|
| 366 | $tmpl->SUPER::set_values_internal(@_); |
|---|
| 367 | } |
|---|
| 368 | |
|---|
| 369 | sub text { |
|---|
| 370 | my $tmpl = shift; |
|---|
| 371 | my $text; |
|---|
| 372 | if ($tmpl->{reflow_flag}) { |
|---|
| 373 | $tmpl->{reflow_flag} = 0; |
|---|
| 374 | $text = $tmpl->reflow(); |
|---|
| 375 | } |
|---|
| 376 | $text = $tmpl->SUPER::text(@_); |
|---|
| 377 | |
|---|
| 378 | $tmpl->{needs_db_sync} = 0; |
|---|
| 379 | unless (@_) { |
|---|
| 380 | if ($tmpl->linked_file) { |
|---|
| 381 | if (my $res = $tmpl->_sync_from_disk) { |
|---|
| 382 | $text = $res; |
|---|
| 383 | $tmpl->SUPER::text($text); |
|---|
| 384 | ## We used to save the template here; now we don't, because |
|---|
| 385 | ## it causes deadlock (the DB is locked from loading the |
|---|
| 386 | ## template, so saving would try to write-lock it). |
|---|
| 387 | if (!defined $resync_to_db) { |
|---|
| 388 | $resync_to_db = {}; |
|---|
| 389 | MT->add_callback('takedown', 9, undef, \&_resync_to_db); |
|---|
| 390 | } |
|---|
| 391 | $resync_to_db->{$tmpl->id} = $tmpl; |
|---|
| 392 | $tmpl->{needs_db_sync} = 1; |
|---|
| 393 | } |
|---|
| 394 | } |
|---|
| 395 | $tmpl->reset_tokens; |
|---|
| 396 | } |
|---|
| 397 | $text; |
|---|
| 398 | } |
|---|
| 399 | |
|---|
| 400 | sub _resync_to_db { |
|---|
| 401 | return unless defined $resync_to_db; |
|---|
| 402 | return unless %$resync_to_db; |
|---|
| 403 | foreach my $tmpl_id (keys %$resync_to_db) { |
|---|
| 404 | my $tmpl = $resync_to_db->{$tmpl_id}; |
|---|
| 405 | next unless $tmpl->{needs_db_sync}; |
|---|
| 406 | $tmpl->save; |
|---|
| 407 | } |
|---|
| 408 | $resync_to_db = {}; |
|---|
| 409 | } |
|---|
| 410 | |
|---|
| 411 | sub _sync_from_disk { |
|---|
| 412 | my $tmpl = shift; |
|---|
| 413 | my $lfile = $tmpl->linked_file; |
|---|
| 414 | unless (File::Spec->file_name_is_absolute($lfile)) { |
|---|
| 415 | if ($tmpl->blog_id) { |
|---|
| 416 | my $blog = MT::Blog->load($tmpl->blog_id) |
|---|
| 417 | or return; |
|---|
| 418 | $lfile = File::Spec->catfile($blog->site_path, $lfile); |
|---|
| 419 | } |
|---|
| 420 | else { |
|---|
| 421 | # use MT path to base relative paths |
|---|
| 422 | $lfile = File::Spec->catfile(MT->instance->server_path, $lfile); |
|---|
| 423 | } |
|---|
| 424 | } |
|---|
| 425 | return unless -e $lfile; |
|---|
| 426 | my($size, $mtime) = (stat _)[7,9]; |
|---|
| 427 | return if $size == $tmpl->linked_file_size && |
|---|
| 428 | $mtime == $tmpl->linked_file_mtime; |
|---|
| 429 | local *FH; |
|---|
| 430 | open FH, $lfile or return; |
|---|
| 431 | my $c; do { local $/; $c = <FH> }; |
|---|
| 432 | close FH; |
|---|
| 433 | $tmpl->linked_file_size($size); |
|---|
| 434 | $tmpl->linked_file_mtime($mtime); |
|---|
| 435 | $c; |
|---|
| 436 | } |
|---|
| 437 | |
|---|
| 438 | sub _sync_to_disk { |
|---|
| 439 | my $tmpl = shift; |
|---|
| 440 | my($text) = @_; |
|---|
| 441 | my $lfile = $tmpl->linked_file; |
|---|
| 442 | my $cfg = MT->config; |
|---|
| 443 | if ($cfg->SafeMode) { |
|---|
| 444 | ## Check for a set of extensions that aren't allowed. |
|---|
| 445 | for my $ext (qw( pl pm cgi cfg )) { |
|---|
| 446 | if ($lfile =~ /\.$ext$/i) { |
|---|
| 447 | return $tmpl->error(MT->translate( |
|---|
| 448 | "You cannot use a [_1] extension for a linked file.", |
|---|
| 449 | ".$ext")); |
|---|
| 450 | } |
|---|
| 451 | } |
|---|
| 452 | } |
|---|
| 453 | unless (File::Spec->file_name_is_absolute($lfile)) { |
|---|
| 454 | if ($tmpl->blog_id) { |
|---|
| 455 | my $blog = MT::Blog->load($tmpl->blog_id) |
|---|
| 456 | or return; |
|---|
| 457 | $lfile = File::Spec->catfile($blog->site_path, $lfile); |
|---|
| 458 | } else { |
|---|
| 459 | $lfile = File::Spec->catfile(MT->instance->server_path, $lfile); |
|---|
| 460 | } |
|---|
| 461 | } |
|---|
| 462 | local *FH; |
|---|
| 463 | ## If the linked file already exists, and there is no template text |
|---|
| 464 | ## (empty textarea, etc.), then we read the template text from the |
|---|
| 465 | ## linked file, assuming that it should not be overwritten. If the |
|---|
| 466 | ## file does not already exist, or if there is template text, assume |
|---|
| 467 | ## that we should update the linked file. |
|---|
| 468 | if (-e $lfile && !$tmpl->SUPER::text) { |
|---|
| 469 | open FH, $lfile or return; |
|---|
| 470 | do { local $/; $tmpl->SUPER::text(<FH>) }; |
|---|
| 471 | close FH; |
|---|
| 472 | } else { |
|---|
| 473 | my $umask = oct $cfg->HTMLUmask; |
|---|
| 474 | my $old = umask($umask); |
|---|
| 475 | ## Untaint. We assume that the user knows what he/she is doing, |
|---|
| 476 | ## and allow anything. |
|---|
| 477 | ($lfile) = $lfile =~ /(.+)/s; |
|---|
| 478 | open FH, ">$lfile" or |
|---|
| 479 | return $tmpl->error(MT->translate( |
|---|
| 480 | "Opening linked file '[_1]' failed: [_2]", $lfile, "$!" )); |
|---|
| 481 | print FH $text; |
|---|
| 482 | close FH; |
|---|
| 483 | umask($old); |
|---|
| 484 | } |
|---|
| 485 | my($size, $mtime) = (stat $lfile)[7,9]; |
|---|
| 486 | $tmpl->linked_file_size($size); |
|---|
| 487 | $tmpl->linked_file_mtime($mtime); |
|---|
| 488 | 1; |
|---|
| 489 | } |
|---|
| 490 | |
|---|
| 491 | sub rescan { |
|---|
| 492 | my $tmpl = shift; |
|---|
| 493 | my ($tokens) = @_; |
|---|
| 494 | unless ($tokens) { |
|---|
| 495 | # top of tree; reset |
|---|
| 496 | $tmpl->{__ids} = {}; |
|---|
| 497 | $tmpl->{__classes} = {}; |
|---|
| 498 | # Use tokens if we already have them, otherwise compile |
|---|
| 499 | $tokens = $tmpl->{__tokens} || $tmpl->compile; |
|---|
| 500 | } |
|---|
| 501 | return unless $tokens; |
|---|
| 502 | foreach my $t (@$tokens) { |
|---|
| 503 | if ($t->[0] ne 'TEXT') { |
|---|
| 504 | if ($t->[1]->{id}) { |
|---|
| 505 | my $ids = $tmpl->{__ids} ||= {}; |
|---|
| 506 | $ids->{lc $t->[1]->{id}} = $t; |
|---|
| 507 | } |
|---|
| 508 | elsif ($t->[1]->{class}) { |
|---|
| 509 | my $classes = $tmpl->{__classes} ||= {}; |
|---|
| 510 | push @{ $classes->{lc $t->[1]->{class}} ||= [] }, $t; |
|---|
| 511 | } |
|---|
| 512 | $tmpl->rescan($t->[2]) if $t->[2]; |
|---|
| 513 | } |
|---|
| 514 | } |
|---|
| 515 | } |
|---|
| 516 | |
|---|
| 517 | sub compile { |
|---|
| 518 | my $tmpl = shift; |
|---|
| 519 | require MT::Builder; |
|---|
| 520 | my $b = new MT::Builder; |
|---|
| 521 | $b->compile($tmpl) or return $tmpl->error($b->errstr); |
|---|
| 522 | return $tmpl->{__tokens}; |
|---|
| 523 | } |
|---|
| 524 | |
|---|
| 525 | sub errors { |
|---|
| 526 | my $tmpl = shift; |
|---|
| 527 | $tmpl->{errors} = shift if @_; |
|---|
| 528 | $tmpl->{errors}; |
|---|
| 529 | } |
|---|
| 530 | |
|---|
| 531 | sub reset_tokens { |
|---|
| 532 | my $tmpl = shift; |
|---|
| 533 | $tmpl->{__tokens} = undef; |
|---|
| 534 | $tmpl->{__classes} = undef; |
|---|
| 535 | $tmpl->{__ids} = undef; |
|---|
| 536 | } |
|---|
| 537 | |
|---|
| 538 | sub reset_ids { |
|---|
| 539 | my $tmpl = shift; |
|---|
| 540 | $tmpl->{__ids} = undef; |
|---|
| 541 | } |
|---|
| 542 | |
|---|
| 543 | sub reset_classes { |
|---|
| 544 | my $tmpl = shift; |
|---|
| 545 | $tmpl->{__classes} = undef; |
|---|
| 546 | } |
|---|
| 547 | |
|---|
| 548 | sub reset_markers { |
|---|
| 549 | my $tmpl = shift; |
|---|
| 550 | $tmpl->{__classes} = undef; |
|---|
| 551 | $tmpl->{__ids} = undef; |
|---|
| 552 | } |
|---|
| 553 | |
|---|
| 554 | sub token_ids { |
|---|
| 555 | my $tmpl = shift; |
|---|
| 556 | if (@_) { |
|---|
| 557 | return $tmpl->{__ids} = shift; |
|---|
| 558 | } |
|---|
| 559 | $tmpl->rescan unless $tmpl->{__ids}; |
|---|
| 560 | return $tmpl->{__ids}; |
|---|
| 561 | } |
|---|
| 562 | |
|---|
| 563 | sub token_classes { |
|---|
| 564 | my $tmpl = shift; |
|---|
| 565 | if (@_) { |
|---|
| 566 | return $tmpl->{__classes} = shift; |
|---|
| 567 | } |
|---|
| 568 | $tmpl->rescan unless $tmpl->{__classes}; |
|---|
| 569 | return $tmpl->{__classes}; |
|---|
| 570 | } |
|---|
| 571 | |
|---|
| 572 | sub tokens { |
|---|
| 573 | my $tmpl = shift; |
|---|
| 574 | if (@_) { |
|---|
| 575 | return bless $tmpl->{__tokens} = shift, 'MT::Template::Tokens'; |
|---|
| 576 | } |
|---|
| 577 | my $t = $tmpl->{__tokens} || $tmpl->compile; |
|---|
| 578 | return bless $t, 'MT::Template::Tokens' if $t; |
|---|
| 579 | return undef; |
|---|
| 580 | } |
|---|
| 581 | |
|---|
| 582 | sub published_url { |
|---|
| 583 | my $tmpl = shift; |
|---|
| 584 | |
|---|
| 585 | return undef unless $tmpl->outfile; |
|---|
| 586 | return undef unless ($tmpl->type eq 'index'); |
|---|
| 587 | |
|---|
| 588 | my $blog = $tmpl->blog; |
|---|
| 589 | return undef unless $blog; |
|---|
| 590 | my $site_url = $blog->site_url || ''; |
|---|
| 591 | $site_url .= '/' if $site_url !~ m!/$!; |
|---|
| 592 | my $url = $site_url . $tmpl->outfile; |
|---|
| 593 | |
|---|
| 594 | if ($tmpl->build_dynamic) { |
|---|
| 595 | require MT::FileInfo; |
|---|
| 596 | my @finfos = MT::FileInfo->load({blog_id => $tmpl->blog_id, |
|---|
| 597 | template_id => $tmpl->id}); |
|---|
| 598 | if (scalar @finfos == 1) { |
|---|
| 599 | return $url; |
|---|
| 600 | } |
|---|
| 601 | } else { |
|---|
| 602 | my $site_path = $blog->site_path || ''; |
|---|
| 603 | my $tmpl_path = File::Spec->catfile($site_path, $tmpl->outfile); |
|---|
| 604 | if (-f $tmpl_path) { |
|---|
| 605 | return $url; |
|---|
| 606 | } |
|---|
| 607 | } |
|---|
| 608 | undef; |
|---|
| 609 | } |
|---|
| 610 | |
|---|
| 611 | sub pre_remove_children { |
|---|
| 612 | my $tmpl = shift; |
|---|
| 613 | $tmpl->remove_children({ key => 'template_id' }); |
|---|
| 614 | } |
|---|
| 615 | |
|---|
| 616 | # Some DOM-inspired methods (replicating the interface, so it's more |
|---|
| 617 | # familiar to those who know DOM) |
|---|
| 618 | sub getElementsByTagName { |
|---|
| 619 | my $tmpl = shift; |
|---|
| 620 | return MT::Template::Tokens::getElementsByTagName($tmpl->tokens, @_); |
|---|
| 621 | } |
|---|
| 622 | |
|---|
| 623 | sub getElementsByClassName { |
|---|
| 624 | my $tmpl = shift; |
|---|
| 625 | my ($name) = @_; |
|---|
| 626 | my $classes = $tmpl->token_classes; |
|---|
| 627 | my $tokens = $classes->{lc $name}; |
|---|
| 628 | if ($tokens && @$tokens) { |
|---|
| 629 | #@$tokens = map { bless $_, NODE } @$tokens; |
|---|
| 630 | return @$tokens; |
|---|
| 631 | } |
|---|
| 632 | return (); |
|---|
| 633 | } |
|---|
| 634 | |
|---|
| 635 | sub getElementsByName { |
|---|
| 636 | my $tmpl = shift; |
|---|
| 637 | return MT::Template::Tokens::getElementsByName($tmpl->tokens, @_); |
|---|
| 638 | } |
|---|
| 639 | |
|---|
| 640 | sub getElementById { |
|---|
| 641 | my $tmpl = shift; |
|---|
| 642 | my ($id) = @_; |
|---|
| 643 | if (my $node = $tmpl->token_ids->{$id}) { |
|---|
| 644 | return bless $node, NODE; |
|---|
| 645 | } |
|---|
| 646 | undef; |
|---|
| 647 | } |
|---|
| 648 | |
|---|
| 649 | sub createElement { |
|---|
| 650 | my $tmpl = shift; |
|---|
| 651 | my ($tag, $attr) = @_; |
|---|
| 652 | my $node = bless [ $tag, $attr, undef, undef, undef, undef, $tmpl ], NODE; |
|---|
| 653 | weaken($node->[6]); |
|---|
| 654 | return $node; |
|---|
| 655 | } |
|---|
| 656 | |
|---|
| 657 | sub createTextNode { |
|---|
| 658 | my $tmpl = shift; |
|---|
| 659 | my ($text) = @_; |
|---|
| 660 | my $node = bless [ 'TEXT', $text, undef, undef, undef, undef, $tmpl ], NODE; |
|---|
| 661 | weaken($node->[6]); |
|---|
| 662 | return $node; |
|---|
| 663 | } |
|---|
| 664 | |
|---|
| 665 | sub insertAfter { |
|---|
| 666 | my $tmpl = shift; |
|---|
| 667 | my ($node1, $node2) = @_; |
|---|
| 668 | my $parent_node = $node2 ? $node2->parentNode : $tmpl; |
|---|
| 669 | my $parent_array = $parent_node->childNodes; |
|---|
| 670 | if ( $node2 ) { |
|---|
| 671 | for (my $i = 0; $i < scalar @$parent_array; $i++) { |
|---|
| 672 | if ($parent_array->[$i] == $node2) { |
|---|
| 673 | $node1->parentNode($parent_node); |
|---|
| 674 | splice(@$parent_array, $i + 1, 0, $node1); |
|---|
| 675 | return 1; |
|---|
| 676 | } |
|---|
| 677 | } |
|---|
| 678 | return 0; |
|---|
| 679 | } |
|---|
| 680 | else { |
|---|
| 681 | $node1->parentNode($parent_node); |
|---|
| 682 | push @$parent_array, $node1; |
|---|
| 683 | return 1; |
|---|
| 684 | } |
|---|
| 685 | return 0; |
|---|
| 686 | } |
|---|
| 687 | |
|---|
| 688 | sub insertBefore { |
|---|
| 689 | my $tmpl = shift; |
|---|
| 690 | my ($node1, $node2) = @_; |
|---|
| 691 | my $parent_node = $node2 ? $node2->parentNode : $tmpl; |
|---|
| 692 | my $parent_array = $parent_node->childNodes; |
|---|
| 693 | if ( $node2 ) { |
|---|
| 694 | for (my $i = 0; $i < scalar @$parent_array; $i++) { |
|---|
| 695 | if ($parent_array->[$i] == $node2) { |
|---|
| 696 | $node1->parentNode($parent_node); |
|---|
| 697 | splice(@$parent_array, $i, 0, $node1); |
|---|
| 698 | return 1; |
|---|
| 699 | } |
|---|
| 700 | } |
|---|
| 701 | return 0; |
|---|
| 702 | } |
|---|
| 703 | else { |
|---|
| 704 | $node1->parentNode($parent_node); |
|---|
| 705 | unshift @$parent_array, $node1; |
|---|
| 706 | return 1; |
|---|
| 707 | } |
|---|
| 708 | return 0; |
|---|
| 709 | } |
|---|
| 710 | |
|---|
| 711 | sub childNodes { |
|---|
| 712 | my $tmpl = shift; |
|---|
| 713 | return $tmpl->tokens; |
|---|
| 714 | } |
|---|
| 715 | |
|---|
| 716 | sub hasChildNodes { |
|---|
| 717 | my $tmpl = shift; |
|---|
| 718 | my $nodes = $tmpl->childNodes; |
|---|
| 719 | return $nodes && (@$nodes) ? 1 : 0; |
|---|
| 720 | } |
|---|
| 721 | |
|---|
| 722 | sub appendChild { |
|---|
| 723 | my $tmpl = shift; |
|---|
| 724 | my ($new_node) = @_; |
|---|
| 725 | my $nodes = $tmpl->childNodes; |
|---|
| 726 | push @$nodes, $new_node; |
|---|
| 727 | $tmpl->{reflow_flag} = 1; |
|---|
| 728 | } |
|---|
| 729 | |
|---|
| 730 | # Alias to perl_function_names for those that may prefer that. |
|---|
| 731 | # *get_elements_by_tag_name = \&getElementsByTagName; |
|---|
| 732 | # *get_elements_by_name = \&getElementsByName; |
|---|
| 733 | # *get_element_by_id = \&getElementById; |
|---|
| 734 | # *create_element = \&createElement; |
|---|
| 735 | |
|---|
| 736 | # functionality for individual nodes gathered by DOM-like query operations. |
|---|
| 737 | |
|---|
| 738 | package MT::Template::Tokens; |
|---|
| 739 | |
|---|
| 740 | use strict; |
|---|
| 741 | sub NODE_TEXT () { 1 } |
|---|
| 742 | sub NODE_BLOCK () { 2 } |
|---|
| 743 | sub NODE_FUNCTION () { 3 } |
|---|
| 744 | |
|---|
| 745 | sub getElementsByTagName { |
|---|
| 746 | my ($tokens, $name) = @_; |
|---|
| 747 | my @list; |
|---|
| 748 | $name = lc $name; |
|---|
| 749 | foreach my $t (@$tokens) { |
|---|
| 750 | if (lc $t->[0] eq $name) { |
|---|
| 751 | push @list, $t; |
|---|
| 752 | } |
|---|
| 753 | if ($t->[2]) { |
|---|
| 754 | my $subt = getElementsByTagName($t->[2], $name); |
|---|
| 755 | push @list, @$subt if $subt; |
|---|
| 756 | } |
|---|
| 757 | } |
|---|
| 758 | scalar @list ? \@list : undef; |
|---|
| 759 | } |
|---|
| 760 | |
|---|
| 761 | sub getElementsByName { |
|---|
| 762 | my ($tokens, $name) = @_; |
|---|
| 763 | my @list; |
|---|
| 764 | $name = lc $name; |
|---|
| 765 | foreach my $t (@$tokens) { |
|---|
| 766 | if ((ref($t->[1]) eq 'HASH') && (lc ($t->[1]{'name'} || '') eq $name)) { |
|---|
| 767 | push @list, $t; |
|---|
| 768 | } |
|---|
| 769 | if ($t->[2]) { |
|---|
| 770 | my $subt = getElementsByName($t->[2], $name); |
|---|
| 771 | push @list, @$subt if $subt; |
|---|
| 772 | } |
|---|
| 773 | } |
|---|
| 774 | scalar @list ? \@list : undef; |
|---|
| 775 | } |
|---|
| 776 | |
|---|
| 777 | package MT::Template::Node; |
|---|
| 778 | |
|---|
| 779 | use strict; |
|---|
| 780 | use MT::Util qw( weaken ); |
|---|
| 781 | |
|---|
| 782 | sub setAttribute { |
|---|
| 783 | my $node = shift; |
|---|
| 784 | my ($attr, $val) = @_; |
|---|
| 785 | if ($attr eq 'id') { |
|---|
| 786 | # assign into ids |
|---|
| 787 | my $tmpl = $node->template; |
|---|
| 788 | my $ids = $tmpl->token_ids; |
|---|
| 789 | my $old_id = $node->getAttribute("id"); |
|---|
| 790 | if ($old_id && $ids) { |
|---|
| 791 | delete $ids->{$old_id}; |
|---|
| 792 | } |
|---|
| 793 | } |
|---|
| 794 | elsif ($attr eq 'class') { |
|---|
| 795 | # assign into classes |
|---|
| 796 | my $tmpl = $node->template; |
|---|
| 797 | my $classes = $tmpl->token_classes; |
|---|
| 798 | my $old_class = $node->getAttribute("class"); |
|---|
| 799 | if ($old_class && $classes->{$old_class}) { |
|---|
| 800 | @{$classes->{$old_class}} = grep { $_ != $node } |
|---|
| 801 | @{$classes->{$old_class}}; |
|---|
| 802 | } |
|---|
| 803 | push @{$classes->{$val} ||= []}, $node; |
|---|
| 804 | } |
|---|
| 805 | ($node->[1] ||= {})->{$attr} = $val; |
|---|
| 806 | } |
|---|
| 807 | |
|---|
| 808 | sub template { |
|---|
| 809 | my $node = shift; |
|---|
| 810 | return $node->[6]; |
|---|
| 811 | } |
|---|
| 812 | |
|---|
| 813 | sub getAttribute { |
|---|
| 814 | my $node = shift; |
|---|
| 815 | my ($attr) = @_; |
|---|
| 816 | ($node->[1] || {})->{$attr}; |
|---|
| 817 | } |
|---|
| 818 | |
|---|
| 819 | # sub attributes { |
|---|
| 820 | # my $node = shift; |
|---|
| 821 | # return $node->[1] ||= {}; |
|---|
| 822 | # } |
|---|
| 823 | |
|---|
| 824 | sub nextSibling { |
|---|
| 825 | my $node = shift; |
|---|
| 826 | my $parent = $node->parentNode->childNodes; |
|---|
| 827 | my $max = (scalar @$parent) - 1; |
|---|
| 828 | return undef unless $max; |
|---|
| 829 | my $last = $parent->[0]; |
|---|
| 830 | foreach my $n ($parent->[1..$max]) { |
|---|
| 831 | return $n if $node == $last; |
|---|
| 832 | $last = $n; |
|---|
| 833 | } |
|---|
| 834 | return $parent->[$max] if $node == $last; |
|---|
| 835 | return undef; |
|---|
| 836 | } |
|---|
| 837 | |
|---|
| 838 | sub lastChild { |
|---|
| 839 | my $node = shift; |
|---|
| 840 | my $children = $node->childNodes or return undef; |
|---|
| 841 | @$children ? $children->[scalar @$children - 1] : undef; |
|---|
| 842 | } |
|---|
| 843 | |
|---|
| 844 | sub firstChild { |
|---|
| 845 | my $node = shift; |
|---|
| 846 | my $children = $node->[2] or return undef; |
|---|
| 847 | @$children ? $children->[0] : undef; |
|---|
| 848 | } |
|---|
| 849 | |
|---|
| 850 | sub previousSibling { |
|---|
| 851 | my $node = shift; |
|---|
| 852 | my $parent = $node->parentNode->childNodes; |
|---|
| 853 | my $last; |
|---|
| 854 | foreach my $n (@$parent) { |
|---|
| 855 | return $last if $node == $n; |
|---|
| 856 | $last = $n; |
|---|
| 857 | } |
|---|
| 858 | return undef; |
|---|
| 859 | } |
|---|
| 860 | |
|---|
| 861 | sub parentNode { |
|---|
| 862 | my $node = shift; |
|---|
| 863 | weaken($node->[5] = shift) if @_; |
|---|
| 864 | $node->[5]; |
|---|
| 865 | } |
|---|
| 866 | |
|---|
| 867 | sub childNodes { |
|---|
| 868 | my $node = shift; |
|---|
| 869 | $node->[2] = shift if @_; |
|---|
| 870 | $node->[2]; |
|---|
| 871 | } |
|---|
| 872 | |
|---|
| 873 | sub ownerDocument { #template |
|---|
| 874 | my $node = shift; |
|---|
| 875 | return $node->template; |
|---|
| 876 | } |
|---|
| 877 | |
|---|
| 878 | sub hasChildNodes { |
|---|
| 879 | my $node = shift; |
|---|
| 880 | $node->[2] && (@{$node->[2]}) ? 1 : 0; |
|---|
| 881 | } |
|---|
| 882 | |
|---|
| 883 | sub nodeType { |
|---|
| 884 | my $node = shift; |
|---|
| 885 | if ($node->[0] eq 'TEXT') { |
|---|
| 886 | return NODE_TEXT(); |
|---|
| 887 | } elsif (defined $node->[2]) { |
|---|
| 888 | return NODE_BLOCK(); |
|---|
| 889 | } else { |
|---|
| 890 | return NODE_FUNCTION(); |
|---|
| 891 | } |
|---|
| 892 | } |
|---|
| 893 | |
|---|
| 894 | sub nodeName { |
|---|
| 895 | my $node = shift; |
|---|
| 896 | if ($node->[0] eq 'TEXT') { |
|---|
| 897 | return undef; |
|---|
| 898 | } |
|---|
| 899 | # normalize: |
|---|
| 900 | # MTEntry => mt:entry |
|---|
| 901 | # MTAPP:WIDGET => mtapp:widget |
|---|
| 902 | my $tag = lc $node->[0]; |
|---|
| 903 | if (($tag !~ m/:/) && ($tag =~ m/^mt/)) { |
|---|
| 904 | $tag =~ s/^mt/mt:/; |
|---|
| 905 | } |
|---|
| 906 | return $tag; |
|---|
| 907 | } |
|---|
| 908 | |
|---|
| 909 | # Returns text of a text node; inner text for a block tag, or undef |
|---|
| 910 | # for a function tag. |
|---|
| 911 | sub nodeValue { |
|---|
| 912 | my $node = shift; |
|---|
| 913 | if ($node->[0] eq 'TEXT') { |
|---|
| 914 | return $node->[1]; |
|---|
| 915 | } else { |
|---|
| 916 | if (defined $node->[3]) { |
|---|
| 917 | return $node->[3]; |
|---|
| 918 | } |
|---|
| 919 | } |
|---|
| 920 | return undef; |
|---|
| 921 | } |
|---|
| 922 | |
|---|
| 923 | sub innerHTML { |
|---|
| 924 | my $node = shift; |
|---|
| 925 | if (@_) { |
|---|
| 926 | my ($text) = @_; |
|---|
| 927 | $node->[3] = $text; |
|---|
| 928 | my $builder = new MT::Builder; |
|---|
| 929 | my $ctx = MT::Template::Context->new; |
|---|
| 930 | $node->[2] = $builder->compile($ctx, $text); |
|---|
| 931 | my $tmpl = $node->ownerDocument; |
|---|
| 932 | if ($tmpl) { |
|---|
| 933 | $tmpl->reset_markers; |
|---|
| 934 | $tmpl->{reflow_flag} = 1; |
|---|
| 935 | } |
|---|
| 936 | } |
|---|
| 937 | return $node->[3]; |
|---|
| 938 | } |
|---|
| 939 | |
|---|
| 940 | # TBD: what about new nodes that are added with id elements? |
|---|
| 941 | sub appendChild { |
|---|
| 942 | my $node = shift; |
|---|
| 943 | my ($new_node) = @_; |
|---|
| 944 | my $nodes = $node->childNodes; |
|---|
| 945 | push @$nodes, $new_node; |
|---|
| 946 | my $tmpl = $node->ownerDocument; |
|---|
| 947 | if ($tmpl) { |
|---|
| 948 | $tmpl->{reflow_flag} = 1; |
|---|
| 949 | } |
|---|
| 950 | } |
|---|
| 951 | |
|---|
| 952 | sub removeChild { |
|---|
| 953 | my $node = shift; |
|---|
| 954 | } |
|---|
| 955 | |
|---|
| 956 | *inner_html = \&innerHTML; |
|---|
| 957 | *append_child = \&appendChild; |
|---|
| 958 | *insert_before = \&insertBefore; |
|---|
| 959 | *remove_child = \&removeChild; |
|---|
| 960 | |
|---|
| 961 | # trans('Index') |
|---|
| 962 | # trans('Archive') |
|---|
| 963 | # trans('Category Archive') |
|---|
| 964 | # trans('Individual') |
|---|
| 965 | # trans('Page') |
|---|
| 966 | # trans('Comment Listing') |
|---|
| 967 | # trans('Ping Listing') |
|---|
| 968 | # trans('Comment Preview') |
|---|
| 969 | # trans('Comment Error') |
|---|
| 970 | # trans('Comment Pending') |
|---|
| 971 | # trans('Dynamic Error') |
|---|
| 972 | # trans('Uploaded Image') |
|---|
| 973 | # trans('Search Results') |
|---|
| 974 | # trans('Module') |
|---|
| 975 | # trans('Widget') |
|---|
| 976 | |
|---|
| 977 | 1; |
|---|
| 978 | __END__ |
|---|
| 979 | |
|---|
| 980 | =head1 NAME |
|---|
| 981 | |
|---|
| 982 | MT::Template - Movable Type template record |
|---|
| 983 | |
|---|
| 984 | =head1 SYNOPSIS |
|---|
| 985 | |
|---|
| 986 | use MT::Template; |
|---|
| 987 | my $tmpl = MT::Template->load($tmpl_id); |
|---|
| 988 | defined(my $html = $tmpl->build($ctx)) |
|---|
| 989 | or die $tmpl->errstr; |
|---|
| 990 | |
|---|
| 991 | $tmpl->name('New Template name'); |
|---|
| 992 | $tmpl->save |
|---|
| 993 | or die $tmpl->errstr; |
|---|
| 994 | |
|---|
| 995 | =head1 DESCRIPTION |
|---|
| 996 | |
|---|
| 997 | An I<MT::Template> object represents a template in the Movable Type system. |
|---|
| 998 | It contains the actual template body, along with metadata used for keeping |
|---|
| 999 | the template in sync with a linked file, etc. It also contains the |
|---|
| 1000 | functionality necessary to build an output file from a generic template. |
|---|
| 1001 | |
|---|
| 1002 | Linking a template to an external file means that any updates to the template |
|---|
| 1003 | through the Movable Type CMS will be synced automatically to the file on |
|---|
| 1004 | disk, and vice versa. This allows authors to edit their templates in an |
|---|
| 1005 | external editor that supports FTP, which is preferable for users who do not |
|---|
| 1006 | like editing in textareas. |
|---|
| 1007 | |
|---|
| 1008 | =head1 USAGE |
|---|
| 1009 | |
|---|
| 1010 | As a subclass of I<MT::Object>, I<MT::Template> inherits all of the |
|---|
| 1011 | data-management and -storage methods from that class; thus you should look |
|---|
| 1012 | at the I<MT::Object> documentation for details about creating a new object, |
|---|
| 1013 | loading an existing object, saving an object, etc. |
|---|
| 1014 | |
|---|
| 1015 | The following methods are unique to the I<MT::Template> interface: |
|---|
| 1016 | |
|---|
| 1017 | =head2 $tmpl->build($ctx [, \%cond ]) |
|---|
| 1018 | |
|---|
| 1019 | Given a context I<$ctx> (an I<MT::Template::Context> object) and an optional |
|---|
| 1020 | set of conditions \%cond, builds a template into its output form. The |
|---|
| 1021 | template is first parsed into a list of tokens, then is interpreted/executed |
|---|
| 1022 | to generate the final output. |
|---|
| 1023 | |
|---|
| 1024 | If specified, I<\%cond> should be a reference to a hash with MT tag names |
|---|
| 1025 | (without the leading C<MT>) as the keys, and boolean flags as the values--the |
|---|
| 1026 | flags specify whether the template interpreter should include any |
|---|
| 1027 | conditional containers found in the template body. |
|---|
| 1028 | |
|---|
| 1029 | Returns the output as a scalar string, C<undef> on error. Because the empty |
|---|
| 1030 | string C<''> and the value C<0> are both valid return values for this method, |
|---|
| 1031 | you should check specifically for C<undef>: |
|---|
| 1032 | |
|---|
| 1033 | defined(my $html = $tmpl->build($ctx)) |
|---|
| 1034 | or die $tmpl->errstr; |
|---|
| 1035 | |
|---|
| 1036 | =head2 $tmpl->published_url |
|---|
| 1037 | |
|---|
| 1038 | Only applicable if the template is an Index Template, this method returns |
|---|
| 1039 | the url for the page which is the result of building the index template. |
|---|
| 1040 | If the template is not of type index, or if the index template has not built |
|---|
| 1041 | yet (if the template is static), or if the index template does not have |
|---|
| 1042 | corresponding FileInfo record (if the template is dynamic), this method |
|---|
| 1043 | returns undef. |
|---|
| 1044 | |
|---|
| 1045 | =head2 $tmpl->blog |
|---|
| 1046 | |
|---|
| 1047 | Returns the I<MT::Blog> object associated with the template. |
|---|
| 1048 | |
|---|
| 1049 | =head2 $tmpl->save |
|---|
| 1050 | |
|---|
| 1051 | Saves the template and if linked to a physical file, updates the file. |
|---|
| 1052 | |
|---|
| 1053 | =head2 $tmpl->remove |
|---|
| 1054 | |
|---|
| 1055 | Removes the template object and any related objects in I<MT::TemplateMap> |
|---|
| 1056 | and I<MT::FileInfo>. |
|---|
| 1057 | |
|---|
| 1058 | =head2 $tmpl->set_values |
|---|
| 1059 | |
|---|
| 1060 | Updates the values of the C<$tmpl> object. When this is called through |
|---|
| 1061 | the I<MT::ObjectDriver> classes (upon loading a template object), and if |
|---|
| 1062 | the template is linked to a file, it will also load the contents of that |
|---|
| 1063 | file, setting the 'text' property. |
|---|
| 1064 | |
|---|
| 1065 | =head1 DATA ACCESS METHODS |
|---|
| 1066 | |
|---|
| 1067 | The I<MT::Template> object holds the following pieces of data. These fields |
|---|
| 1068 | can be accessed and set using the standard data access methods described in |
|---|
| 1069 | the I<MT::Object> documentation. |
|---|
| 1070 | |
|---|
| 1071 | =over 4 |
|---|
| 1072 | |
|---|
| 1073 | =item * id |
|---|
| 1074 | |
|---|
| 1075 | The numeric ID of the template. |
|---|
| 1076 | |
|---|
| 1077 | =item * blog_id |
|---|
| 1078 | |
|---|
| 1079 | The numeric ID of the blog to which this template belongs. |
|---|
| 1080 | |
|---|
| 1081 | =item * name |
|---|
| 1082 | |
|---|
| 1083 | The name of the template. This should be unique on a per-blog basis, because |
|---|
| 1084 | templates--particularly template modules, which are stored as regular |
|---|
| 1085 | templates--are included by name, using C<E<lt>MTIncludeE<gt>>. |
|---|
| 1086 | |
|---|
| 1087 | =item * type |
|---|
| 1088 | |
|---|
| 1089 | The type of the template. This can be any of the following values: C<index>, |
|---|
| 1090 | an Index Template; C<archive>, an Archive Template; C<category>, also an |
|---|
| 1091 | Archive Template; C<individual>, also an Archive Template; C<comments>, a |
|---|
| 1092 | Comment Listing Template; C<comment_preview>, a Comment Preview Template; |
|---|
| 1093 | C<comment_error>, a Comment Error Template; C<popup_image>, an Uploaded Image |
|---|
| 1094 | Popup Template; or C<custom>, a Template Module. |
|---|
| 1095 | |
|---|
| 1096 | =item * outfile |
|---|
| 1097 | |
|---|
| 1098 | The name/path of the output file of this template. This is only applicable |
|---|
| 1099 | if the template is an Index Template. |
|---|
| 1100 | |
|---|
| 1101 | =item * text |
|---|
| 1102 | |
|---|
| 1103 | The body of the template, containing the markup and Movable Type template |
|---|
| 1104 | tags. |
|---|
| 1105 | |
|---|
| 1106 | If the template is linked to an external file, the body of the template is |
|---|
| 1107 | automatically synced between this data field and the external file. |
|---|
| 1108 | |
|---|
| 1109 | =item * rebuild_me |
|---|
| 1110 | |
|---|
| 1111 | A boolean flag specifying whether or not the index template will be rebuilt |
|---|
| 1112 | automatically when rebuilding indexes, rebuilding all, or saving a new entry. |
|---|
| 1113 | |
|---|
| 1114 | =item * linked_file |
|---|
| 1115 | |
|---|
| 1116 | The name/path of the file to which this template is linked in the filesystem, |
|---|
| 1117 | if it is linked. |
|---|
| 1118 | |
|---|
| 1119 | =item * linked_file_mtime |
|---|
| 1120 | |
|---|
| 1121 | The last modified time of the linked file. This, along with |
|---|
| 1122 | I<linked_file_size>, is used to determine whether a file has been updated on |
|---|
| 1123 | disk, and needs to be re-synced. |
|---|
| 1124 | |
|---|
| 1125 | =item * linked_file_size |
|---|
| 1126 | |
|---|
| 1127 | The size of the linked file, in bytes. This, along with I<linked_file_mtime>, |
|---|
| 1128 | is used to determine whether a file has been updated on disk, and needs to |
|---|
| 1129 | be re-synced. |
|---|
| 1130 | |
|---|
| 1131 | =back |
|---|
| 1132 | |
|---|
| 1133 | =head1 DATA LOOKUP |
|---|
| 1134 | |
|---|
| 1135 | In addition to numeric ID lookup, you can look up or sort records by any |
|---|
| 1136 | combination of the following fields. See the I<load> documentation in |
|---|
| 1137 | I<MT::Object> for more information. |
|---|
| 1138 | |
|---|
| 1139 | =over 4 |
|---|
| 1140 | |
|---|
| 1141 | =item * blog_id |
|---|
| 1142 | |
|---|
| 1143 | =item * name |
|---|
| 1144 | |
|---|
| 1145 | =item * type |
|---|
| 1146 | |
|---|
| 1147 | =back |
|---|
| 1148 | |
|---|
| 1149 | =head1 NOTES |
|---|
| 1150 | |
|---|
| 1151 | =over 4 |
|---|
| 1152 | |
|---|
| 1153 | =item * |
|---|
| 1154 | |
|---|
| 1155 | When you remove a template using I<MT::Template::remove>, in addition to |
|---|
| 1156 | removing the template record, all of the I<MT::TemplateMap> objects |
|---|
| 1157 | associated with this template will be removed. |
|---|
| 1158 | |
|---|
| 1159 | =item * |
|---|
| 1160 | |
|---|
| 1161 | If a template is linked to an external file, I<MT::Template::save> will sync |
|---|
| 1162 | the template body to disk, and I<MT::Template::text> (the data access method |
|---|
| 1163 | to retrieve the body of the template) will sync the template body from disk. |
|---|
| 1164 | |
|---|
| 1165 | =back |
|---|
| 1166 | |
|---|
| 1167 | =head1 AUTHOR & COPYRIGHTS |
|---|
| 1168 | |
|---|
| 1169 | Please see the I<MT> manpage for author, copyright, and license information. |
|---|
| 1170 | |
|---|
| 1171 | =cut |
|---|