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

Revision 1369, 29.8 kB (checked in by bchoate, 22 months ago)

Broke CMS into smaller parts to reduce memory footprint and group code into logical parts. BugId:58666

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