root/trunk/lib/MT/Template.pm @ 3082

Revision 3082, 29.2 kB (checked in by bchoate, 14 months ago)

Merging fireball branch changes to-date to trunk: svn merge -r2974:3081 http://code.sixapart.com/svn/movabletype/branches/fireball .

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