root/branches/release-35/lib/MT/Template.pm @ 1937

Revision 1937, 30.8 kB (checked in by bchoate, 20 months ago)

Preserve any pre-existing page_layout variable through includes, etc. BugId:79314

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