root/branches/release-38/lib/MT/Template.pm @ 2396

Revision 2396, 32.5 kB (checked in by bchoate, 19 months ago)

Performance fix for refreshing templates. BugId:79805

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