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

Revision 1957, 31.6 kB (checked in by bchoate, 20 months ago)

Fixes for propogation of build_type from template to templatemaps. BugId:79370

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