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