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

Revision 2342, 33.1 kB (checked in by bchoate, 19 months ago)

blog method should return undeffor global templates.

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