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

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

Fixed some warnings.

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