root/branches/release-32/lib/MT/Template.pm @ 1658

Revision 1658, 30.6 kB (checked in by bchoate, 20 months ago)

Fix to include 'entry' and 'page' archive template types for dynamic archive publishing.

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