root/branches/release-33/lib/MT/Template.pm @ 1735

Revision 1735, 30.7 kB (checked in by bchoate, 20 months ago)

Use available MT::Builder when it exists in the context stash.

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