root/branches/release-30/lib/MT/Template.pm @ 1427

Revision 1427, 30.3 kB (checked in by mpaschal, 21 months ago)

Add MT::Util::weaken() that lets us weaken references when available from a properly compiled Scalar::Util
Use weaken() to prevent some circular references from leaking some objects
(apply patches by Hirotaka Ogawa and Brad Choate--thanks!)
BugzID: 66845

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