root/branches/release-34/lib/MT/Template.pm @ 1877

Revision 1877, 30.8 kB (checked in by bchoate, 20 months ago)

Updates to support publish profile dialog operation. BugId:76495

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