root/branches/release-30/lib/MT/Template.pm @ 1372

Revision 1372, 30.1 kB (checked in by bchoate, 22 months ago)

Initial work for performance logging.

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