root/branches/mt4.11/lib/MT/Template.pm @ 1361

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