root/branches/release-36/lib/MT/Template.pm @ 2049

Revision 2049, 31.5 kB (checked in by auno, 19 months ago)

"cache_enable" valuable is removed from the page and cleaned up back-end codes. BugzID:79445

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