root/branches/release-40/lib/MT/Template.pm @ 2637

Revision 2637, 32.5 kB (checked in by auno, 17 months ago)

Fixed to set template and templatemap build_type for publish options separately. BugzID:80130

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