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

Revision 2361, 33.1 kB (checked in by fumiakiy, 19 months ago)

Do not double translate a phrase. BugId:79792

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