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

Revision 1949, 30.9 kB (checked in by takayama, 20 months ago)

Fixed BugId:75137
* Changed to using cache_key for include cache directory

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