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

Revision 1959, 31.5 kB (checked in by bchoate, 20 months ago)

Fixes for build_dynamic assignment. BugId:79379

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