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