root/trunk/lib/MT/Template/Context.pm @ 3035

Revision 3035, 29.8 kB (checked in by bchoate, 15 months ago)

Merging revision 3031 to trunk.

  • 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::Context;
8
9use strict;
10use base qw( MT::ErrorHandler );
11
12use constant FALSE => -99999;
13use Exporter;
14*import = \&Exporter::import;
15use vars qw( @EXPORT );
16@EXPORT = qw( FALSE );
17use MT::Util qw( weaken );
18use MT::I18N qw( substr_text length_text );
19
20our (%Handlers, %Filters);
21
22sub new {
23    my $class = shift;
24    require MT::Template::ContextHandlers;
25    my $ctx = bless {}, $class;
26    $ctx->init(@_);
27}
28
29sub init {
30    my $ctx = shift;
31    weaken($ctx->{config} = MT->config);
32    $ctx->init_handlers();
33    $ctx;
34}
35
36sub clone {
37    my $ctx = shift;
38    my $clone = ref($ctx)->new;
39    for my $key (keys %{$ctx}) {
40        $clone->{$key} = $ctx->{$key}
41    }
42    return $clone;
43}
44
45sub init_handlers {
46    my $ctx = shift;
47    my $mt = MT->instance;
48    if (!$mt->{__tag_handlers}) {
49        my $h = $mt->{__tag_handlers} = {};
50        my $f = $mt->{__tag_filters} = {};
51        my $all_tags = MT::Component->registry('tags');
52        # Put application-specific handlers in front of 'core'
53        # tag set (allows MT::App::Search, etc to replace the
54        # stubbed core handlers)
55        if ($mt->isa('MT::App')) {
56            my $app_tags = MT->registry("applications", $mt->id, "tags");
57            unshift @$all_tags, $app_tags if $app_tags;
58        }
59        for my $tag_set ( @$all_tags ) {
60            if (my $block = $tag_set->{block}) {
61                for my $orig_tag (keys %$block) {
62                    next if $orig_tag eq 'plugin';
63
64                    my $tag = lc $orig_tag;
65                    my $type = 1;
66
67                    # A '?' suffix identifies conditional tags
68                    if ($tag =~ m/\?$/) {
69                        $tag =~ s/\?$//;
70                        $type = 2;
71                    }
72
73                    # Application level tags should not be overwritten
74                    # by 'core' tags (which may be placeholders, as in the
75                    # case of MT-Search). Non-core plugins can override
76                    # other core routines and application level tags though.
77                    my $prev_hdlr;
78                    if (exists $h->{$tag}) {
79                        # a replaced handler
80                        next if ($block->{plugin}{id}||'') eq 'core';
81                        $prev_hdlr = $h->{$tag};
82                    }
83                    if (ref($block->{$orig_tag}) eq 'HASH') {
84                        $h->{$tag} = [ $block->{$orig_tag}{handler}, $type, $prev_hdlr ];
85                    } else {
86                        $h->{$tag} = [ $block->{$orig_tag}, $type, $prev_hdlr ];
87                    }
88                }
89            }
90            if (my $func = $tag_set->{function}) {
91                for my $orig_tag (keys %$func) {
92                    next if $orig_tag eq 'plugin';
93
94                    my $tag = lc $orig_tag;
95                    my $prev_hdlr;
96                    if (exists $h->{$tag}) {
97                        # a replaced handler
98                        next if ($func->{plugin}{id}||'') eq 'core';
99                        $prev_hdlr = $h->{$tag};
100                    }
101                    if (ref($func->{$orig_tag}) eq 'HASH') {
102                        $h->{$tag} = [ $func->{$orig_tag}{handler}, 0, $prev_hdlr ];
103                    } else {
104                        $h->{$tag} = [ $func->{$orig_tag}, 0, $prev_hdlr ];
105                    }
106                }
107            }
108            if (my $mod = $tag_set->{modifier}) {
109                for my $orig_mod (keys %$mod) {
110                    next if $orig_mod eq 'plugin';
111                    my $modifier = lc $orig_mod;
112                    next if exists $f->{$modifier} && ($mod->{plugin}{id} || '') eq 'core';
113                    $f->{$modifier} = $mod->{$orig_mod};
114                }
115            }
116        }
117    }
118    weaken( $ctx->{__handlers} = $mt->{__tag_handlers} );
119    weaken( $ctx->{__filters} = $mt->{__tag_filters} );
120}
121
122sub super_handler {
123    my ($ctx) = @_;
124    my $tag = lc $ctx->stash('tag');
125    my ($hdlr, $type, $orig_tag) = $ctx->handler_for($tag);
126    if ($orig_tag && $orig_tag->[0]) {
127        my $orig_hdlr = $orig_tag->[0];
128        unless (ref $orig_hdlr) {
129            $orig_tag->[0] = $orig_hdlr = MT->handler_to_coderef( $orig_hdlr );
130        }
131        local $ctx->{__handlers}{$tag} = $orig_tag;
132        return $orig_hdlr->(@_);
133    }
134    return undef;
135}
136
137sub stash {
138    my $ctx = shift;
139    my $key = shift;
140    return $ctx->{__stash}->{$key} = shift if @_;
141    if (ref $ctx->{__stash}->{$key} eq 'MT::Promise') {
142        return MT::Promise::force($ctx->{__stash}->{$key});
143    } else {
144        return $ctx->{__stash}->{$key};
145    }
146}
147
148sub var {
149    my $ctx = shift;
150    my $key = lc shift;
151    if ($key =~ m/^(config|request)\.(.+)$/i) {
152        if (lc($1) eq 'request') {
153            my $mt = MT->instance;
154            return '' unless $mt->isa('MT::App');
155            return $mt->param($2);
156        }
157        elsif (lc($1) eq 'config') {
158            my $setting = $2;
159            return '' if $setting =~ m/password/i;
160            return MT->config($setting);
161        }
162        return '';
163    }
164    my $value = $ctx->{__stash}{vars}{$key};
165    # protects $_ value set during template attribute interpolation
166    local $_ = $_;
167    if (ref $value eq 'CODE') {
168        $value = $value->($ctx);
169    }
170    $ctx->{__stash}{vars}{$key} = shift if @_;
171    return $value;
172}
173
174sub this_tag {
175    my $ctx = shift;
176    return 'mt' . lc( $ctx->stash('tag') );
177}
178
179sub tag {
180    my $ctx = shift;
181    my $tag = lc shift;
182    my ($h) = $ctx->handler_for($tag) or return $ctx->error("No handler for tag $tag");
183    local $ctx->{__stash}{tag} = $tag;
184    return $h->($ctx, @_);
185}
186
187sub handler_for {
188    my $ctx = shift;
189    my $tag = lc $_[0];
190    my $v = $ctx->{__handlers}{$tag};
191    if (ref($v) eq 'HASH') { 
192    $v = $ctx->{__handlers}{$tag} = $v->{handler};
193    }
194    my @h = ref($v) eq 'ARRAY' ? @$v : $v;
195    if (!ref($h[0])) {
196        $h[0] = MT->handler_to_coderef($h[0]);
197        if (ref($v)) {
198            $ctx->{__handlers}{$tag}[0] = $h[0];
199        } else {
200            $ctx->{__handlers}{$tag} = $h[0];
201        }
202    }
203    return ref($v) eq 'ARRAY' ? @h : $h[0];
204}
205
206{
207    my (@order, %order);
208    BEGIN {
209        @order = qw(filters trim_to trim ltrim rtrim decode_html
210                    decode_xml remove_html dirify sanitize
211                    encode_html encode_xml encode_js encode_php
212                    encode_url upper_case lower_case strip_linefeeds
213                    space_pad zero_pad sprintf);
214        my $el = 0; %order = map { $_ => ++$el } @order;
215    }
216    sub stock_post_process_handler {
217        my($ctx, $args, $str, $arglist) = @_;
218        my $filters = $ctx->{__filters};
219        $arglist ||= [];
220        if (@$arglist) {
221            # In the event that $args was manipulated by handlers,
222            # locate any new arguments and add them to $arglist for
223            # processing
224            my %arglist_keys = map { $_->[0] => $_->[1] } @$arglist;
225            if (scalar keys %arglist_keys != scalar keys %$args) {
226                my %more_args = %$args;
227                for (keys %arglist_keys) {
228                    delete $more_args{$_} if exists $more_args{$_};
229                }
230                if (%more_args) {
231                    push @$arglist, [ $_ => $more_args{$_} ] foreach
232                        grep { exists $filters->{$_} }
233                        keys %more_args;
234                }
235            }
236        } elsif (keys %$args && !@$arglist) {
237            # in the event that we don't have arglist,
238            # we'll build it using the hashref we do have
239            # we might as well preserve the original ordering
240            # of processing as well, since it's better than
241            # the pseudo random order we get from retrieving the
242            # keys from the hash.
243            push @$arglist, [ $_, $args->{$_} ] foreach
244                sort { exists $order{$a} && exists $order{$b} ? $order{$a} <=> $order{$b} : 0 }
245                grep { exists $filters->{$_} }
246                keys %$args;
247        }
248        for my $arg (@$arglist) {
249            my ($name, $val) = @$arg;
250            next unless exists $args->{$name};
251            if (my $code = $filters->{$name}) {
252                if (ref $code eq 'HASH') {
253                    $code = $code->{code} ||= MT->handler_to_coderef($code->{handler});
254                }
255                $str = $code->($str, $val, $ctx);
256            }
257        }
258        $str;
259    }
260}
261
262sub post_process_handler {
263    \&stock_post_process_handler;
264}
265
266sub slurp {
267    my ($ctx, $args, $cond) = @_;
268    my $tokens  = $ctx->stash('tokens');
269    return '' unless $tokens;
270    my $result = $ctx->stash('builder')->build($ctx, $tokens, $cond);
271    return $ctx->error($ctx->stash('builder')->errstr)
272        unless defined $result;
273    return $result;
274}
275
276sub else {
277    my ($ctx, $args, $cond) = @_;
278    my $tokens = $ctx->stash('tokens_else');
279    return '' unless $tokens;
280    my $result = $ctx->stash('builder')->build($ctx, $tokens, $cond);
281    return $ctx->error($ctx->stash('builder')->errstr)
282        unless defined $result;
283    return $result;
284}
285
286sub build {
287    my ($ctx, $tmpl, $cond) = @_;
288    my $builder = $ctx->stash('builder');
289    my $tokens = $builder->compile($ctx, $tmpl)
290        or return $ctx->error($builder->errstr);
291    local $ctx->{stash}{tokens} = $tokens;
292    my $result = $builder->build($ctx, $tokens, $cond);
293    return $ctx->error($builder->errstr)
294        unless defined $result;
295    return $result;
296}
297
298sub set_blog_load_context {
299    my ($ctx, $attr, $terms, $args, $col) = @_;
300    my $blog_id = $ctx->stash('blog_id');
301    $col ||= 'blog_id';
302
303    # Grab specified blog IDs
304    my $blog_ids = $attr->{blog_ids}
305                || $attr->{include_blogs}
306                || $attr->{exclude_blogs};
307
308    if (defined($blog_ids) && ($blog_ids =~ m/-/)) {
309        my @list = split /\s*,\s*/, $blog_ids;
310        my @ids;
311        foreach my $id (@list) {
312            if ($id =~ m/^(\d+)-(\d+)$/) {
313                push @ids, $_ for $1..$2;
314            } else {
315                push @ids, $id;
316            }
317        }
318        $blog_ids = join ",", @ids;
319    }
320
321    # If no blog IDs specified, use the current blog
322    if ( ! $blog_ids ) {
323        $terms->{$col} = $blog_id if $col eq 'blog_id';
324    } 
325    # If exclude blogs, set the terms and the NOT arg for load
326    # 'All' is not a valid value for exclude_blogs
327    elsif ( $attr->{exclude_blogs} ) {
328        return $ctx->error(MT->translate(
329                "The attribute exclude_blogs cannot take 'all' for a value."
330            )) if lc $args->{exclude_blogs} eq 'all';
331
332        my @excluded_blogs = split /\s*,\s*/, $blog_ids;
333        $terms->{$col} = [ @excluded_blogs ];
334        $args->{not}{$col} = 1;
335    # include_blogs="all" removes the blog_id/id constraint
336    } elsif (lc $blog_ids eq 'all') {
337        delete $terms->{$col} if exists $terms->{$col};
338    # Blogs are specified in include_blogs so set the terms
339    } else {
340        my $blogs = { map { $_ => 1 } split /\s*,\s*/, $blog_ids };
341        $terms->{$col} = [ keys %{$blogs} ];
342    }
343    1;
344}
345
346sub compile_category_filter {
347    my ($ctx, $cat_expr, $cats, $param) = @_;
348
349    $param ||= {};
350    $cats ||= [];
351    my $is_and = $param->{'and'} ? 1 : 0;
352    my $children = $param->{'children'} ? 1 : 0;
353
354    if ($cat_expr) {
355        my @cols = $cat_expr =~ m!/! ? qw(category_label_path label) : qw(label);
356        my %cats_used;
357        foreach my $col (@cols) {
358            my %cats_replaced;
359            @$cats = sort {length($b->$col) <=> length($a->$col)} @$cats;
360
361            foreach my $cat (@$cats) {
362                next unless $cat;
363                my $catl = $cat->$col;
364                my $catid = $cat->id;
365                my @cats = ($cat);
366                my $repl;
367                if ($children) {
368                    my @kids = ($cat);
369                    while (my $c = shift @kids) {
370                        push @cats, $c;
371                        push @kids, ($c->children_categories);
372                    }
373                    $repl = '';
374                    $repl .= '||' . '#'.$_->id for @cats;
375                    $repl = '(' . substr($repl, 2) . ')';
376                } else {
377                    $repl = "#$catid";
378                }
379                if ($cat_expr =~ s/(?<![#\d])(?:\Q$catl\E)/$repl/g) {
380                    $cats_used{$_->id} = $_ for @cats;
381                }
382                # for multi blog case
383                if ($cats_replaced{$catl}) {
384                    my $last_catid = $cats_replaced{$catl};
385                    $cat_expr =~ s/(#$last_catid\b)/($1 OR #$catid)/g;
386                    $cats_used{$catid} = $cat;
387                }
388                $cats_replaced{$catl} = $catid;
389            }
390        }
391        @$cats = values %cats_used;
392
393        $cat_expr =~ s/\bAND\b/&&/gi;
394        $cat_expr =~ s/\bOR\b/||/gi;
395        $cat_expr =~ s/\bNOT\b/!/gi;
396        # replace any other 'thing' with '(0)' since it's a
397        # category that doesn't even exist.
398        $cat_expr =~ s/( |#\d+|&&|\|\||!|\(|\))|([^#0-9&|!()]+)/$2?'(0)':$1/ge;
399
400        # strip out all the 'ok' stuff. if anything is left, we have
401        # some invalid data in our expression:
402        my $test_expr = $cat_expr;
403        $test_expr =~ s/!|&&|\|\||\(0\)|\(|\)|\s|#\d+//g;
404        return undef if $test_expr;
405    } else {
406        my %cats_used;
407        $cat_expr = '';
408        foreach my $cat (@$cats) {
409            my $id = $cat->id;
410            $cat_expr .= ($is_and ? '&&' : '||') if $cat_expr ne '';
411            if ($children) {
412                my @kids = ($cat);
413                my @cats;
414                while (my $c = shift @kids) {
415                    push @cats, $c;
416                    push @kids, ($c->children_categories);
417                }
418                my $repl = '';
419                $repl .= '||' . '#'.$_->id for @cats;
420                $cats_used{$_->id} = $_ for @cats;
421                $repl = '(' . substr($repl, 2) . ')';
422                $cat_expr .= $repl;
423            } else {
424                $cats_used{$cat->id} = $cat;
425                $cat_expr .= "#$id";
426            }
427        }
428        @$cats = values %cats_used;
429    }
430
431    $cat_expr =~ s/#(\d+)/(exists \$p->{$1})/g;
432    my $expr = 'sub{my($p)=@_;'.$cat_expr.';}';
433    my $cexpr = eval($expr);
434    $@ ? undef : $cexpr;
435}
436
437sub compile_tag_filter {
438    my ($ctx, $tag_expr, $tags) = @_;
439
440    # Sort in descending order by length
441    @$tags = sort {length($b->name) <=> length($a->name)} @$tags;
442
443    # Modify the tag argument, replacing the tag name with '#TagID'
444    # Create a ID-based hash of the tags that are used in the arg
445    my %tags_used;
446    foreach my $tag (@$tags) {
447        my $name = $tag->name;
448        my $id = $tag->id;
449        if ($tag_expr =~ s/(?<![#\d])\Q$name\E/#$id/g) {
450            $tags_used{$id} = $tag;
451        }
452    }
453    # Populate array ref (passed in by reference) of used tags
454    @$tags = values %tags_used;
455
456    # Replace logical constructs with their perl equivalents
457    $tag_expr =~ s/\bAND\b/&&/gi;
458    $tag_expr =~ s/\bOR\b/||/gi;
459    $tag_expr =~ s/\bNOT\b/!/gi;
460
461    # If any foreign/unrecognized sequences appear in our
462    # expression (such as a non-extistent tag name),
463    # replace that with '(0)' which will evaluate to false.
464    $tag_expr =~ s/
465        (
466            [ ]  | # space
467            \#\d+ | # #123
468            &&   | # literal &&
469            \|\| | # literal ||
470            !    | # literal !
471            \(   | # literal (
472            \)     # literal )
473        )  |
474        (
475            [^#&|!()]+  # some unknown set of characters
476        )
477    / $2 ? '(0)' : $1 /gex;
478
479    # Syntax check on 'tag' argument
480    # Strip out all the valid stuff. if anything is left, we have
481    # some invalid data in our expression:
482    my $test_expr = $tag_expr;
483    $test_expr =~ s/!|&&|\|\||\(0\)|\(|\)|\s|#\d+//g;
484    return undef if ($test_expr);
485
486    # Replace '#TagID' with a hash lookup function.
487    # Function confirms/denies use of tag on entry (by IDs)
488    # Translation: exists( PlacementHashRef->{EntryID}{TagID} )
489    $tag_expr =~ s/#(\d+)/(exists \$p->{$1})/g;
490
491    # Create an anonymous subroutine of that lookup function
492    # and return it if all is well.  This code ref will be used
493    # later to test for existence of specified tags in entries.
494    my $expr = 'sub{my($p)=@_;' . $tag_expr . '}';
495    my $cexpr = eval $expr;
496    $@ ? undef : $cexpr;
497}
498
499sub compile_role_filter {
500    my ($ctx, $role_expr, $roles) = @_;
501
502    my %roles_used;
503    foreach my $role (@$roles) {
504        my $name = $role->name;
505        my $id = $role->id;
506        if ($role_expr =~ s/(?<![#\d])\Q$name\E/#$id/g) {
507            $roles_used{$id} = $role;
508        }
509    }
510    @$roles = values %roles_used;
511
512    $role_expr =~ s/\bOR\b/||/gi;
513    $role_expr =~ s/\bAND\b/&&/gi;
514    $role_expr =~ s/\bNOT\b/!/gi;
515    $role_expr =~ s/( |#\d+|&&|\|\||!|\(|\))|([^#0-9&|!()]+)/$2?'(0)':$1/ge;
516
517    my $test_expr = $role_expr;
518    $test_expr =~ s/!|&&|\|\||\(0\)|\(|\)|\s|#\d+//g;
519    return undef if $test_expr;
520
521    $role_expr =~ s/#(\d+)/(exists \$p->{\$e}{$1})/g;
522    my $expr = 'sub{my($e,$p)=@_;'.$role_expr.';}';
523    my $cexpr = eval $expr;
524    $@ ? undef : $cexpr;
525}
526
527sub compile_status_filter {
528    my ($ctx, $status_expr, $status) = @_;
529
530    foreach my $s (@$status) {
531        my $name = $s->{name};
532        my $id = $s->{id};
533        $status_expr =~ s/(?<![#\d])\Q$name\E/#$id/g;
534    }
535
536    $status_expr =~ s/\bOR\b/||/gi;
537    $status_expr =~ s/( |#\d+|&&|\|\||!|\(|\))|([^#0-9&|!()]+)/$2?'(0)':$1/ge;
538
539    my $test_expr = $status_expr;
540    $test_expr =~ s/!|&&|\|\||\(0\)|\(|\)|\s|#\d+//g;
541    return undef if $test_expr;
542
543    $status_expr =~ s/#(\d+)/(\$_[0]->status == $1)/g;
544    my $expr = 'sub{'.$status_expr.';}';
545    my $cexpr = eval $expr;
546    $@ ? undef : $cexpr;
547}
548
549sub count_format {
550    my $ctx = shift;
551    my ($count, $args) = @_;
552    my $phrase;
553    $count ||= 0;
554    if ($count == 0) {
555        $phrase = exists $args->{none}
556            ? $args->{none}   : (exists $args->{plural}
557            ? $args->{plural} : '');
558    } elsif ($count == 1) {
559        $phrase = exists $args->{singular} ? $args->{singular} : '';
560    } elsif ($count > 1) {
561        $phrase = exists $args->{plural} ? $args->{plural} : '';
562    }
563    return $count if $phrase eq '';
564    return $phrase unless $phrase =~ m/#/;
565
566    $phrase =~ s/(?<!\\)#/$count/g;
567    $phrase =~ s/\\#/#/g;
568    return $phrase;
569}
570
571sub _no_author_error {
572    my ($ctx) = @_;
573    my $tag_name = $ctx->stash('tag');
574    return $ctx->error(MT->translate(
575        "You used an '[_1]' tag outside of the context of a author; " .
576        "perhaps you mistakenly placed it outside of an 'MTAuthors' " .
577        "container?", $tag_name
578    ));
579}
580
581sub _no_entry_error {
582    my ($ctx) = @_;
583    my $tag_name = $ctx->stash('tag');
584    $tag_name = 'mt' . $tag_name unless $tag_name =~ m/^MT/i;
585    return $_[0]->error(MT->translate(
586        "You used an '[_1]' tag outside of the context of an entry; " .
587        "perhaps you mistakenly placed it outside of an 'MTEntries' container?", $tag_name
588    ));
589}
590
591sub _no_comment_error {
592    my ($ctx) = @_;
593    my $tag_name = $ctx->stash('tag');
594    $tag_name = 'mt' . $tag_name unless $tag_name =~ m/^MT/i;
595    return $ctx->error(MT->translate(
596        "You used an '[_1]' tag outside of the context of a comment; " .
597        "perhaps you mistakenly placed it outside of an 'MTComments' " .
598        "container?", $tag_name
599    ));
600}
601
602sub _no_ping_error {
603    my ($ctx) = @_;
604    my $tag_name = $ctx->stash('tag');
605    $tag_name = 'mt' . $tag_name unless $tag_name =~ m/^MT/i;
606    return $ctx->error(MT->translate(
607        "You used an '[_1]' tag outside of the context of " .
608        "a ping; perhaps you mistakenly placed it outside " .
609        "of an 'MTPings' container?", $tag_name
610    ));
611}
612
613sub _no_asset_error {
614    my ($ctx) = @_;
615    my $tag_name = $ctx->stash('tag');
616    $tag_name = 'mt' . $tag_name unless $tag_name =~ m/^MT/i;
617    return $ctx->error(MT->translate(
618        "You used an '[_1]' tag outside of the context of an asset; " .
619        "perhaps you mistakenly placed it outside of an 'MTAssets' container?", $tag_name
620    ));
621
622}
623
624sub _no_page_error {
625    my ($ctx) = @_;
626    my $tag_name = $ctx->stash('tag');
627    $tag_name = 'mt' . $tag_name unless $tag_name =~ m/^MT/i;
628    return $ctx->error(MT->translate(
629        "You used an '[_1]' tag outside of the context of an page; " .
630        "perhaps you mistakenly placed it outside of an 'MTPages' container?",
631        $tag_name
632    ));
633}
634
6351;
636__END__
637
638=head1 NAME
639
640MT::Template::Context - Movable Type Template Context
641
642=head1 SYNOPSIS
643
644    use MT::Template::Context;
645    MT::Template::Context->add_tag( FooBar => sub {
646        my($ctx, $args) = @_;
647        my $foo = $ctx->stash('foo')
648            or return $ctx->error("No foo in context");
649        $foo->bar;
650    } );
651
652    ## In a template:
653    ## <$MTFooBar$>
654
655=head1 DESCRIPTION
656
657I<MT::Template::Context> provides the implementation for all of the built-in
658template tags in Movable Type, as well as the public interface to the
659system's plugin interface.
660
661This document focuses only on the public methods needed to implement plugins
662in Movable Type, and the methods that plugin developers might wish to make
663use of. Of course, plugins can make use of other objects loaded from the
664Movable Type database, in which case you may wish to look at the documentation
665for the classes in question (for example, I<MT::Entry>).
666
667=head1 USAGE
668
669=head2 MT::Template::Context->add_tag($name, \&subroutine)
670
671I<add_tag> registers a simple "variable tag" with the system. An example of
672such a tag might be C<E<lt>$MTEntryTitle$E<gt>>.
673
674I<$name> is the name of the tag, without the I<MT> prefix, and
675I<\&subroutine> a reference to a subroutine (either anonymous or named).
676I<\&subroutine> should return either an error (see L<ERROR HANDLING>) or
677a defined scalar value (returning C<undef> will be treated as an error, so
678instead of returning C<undef>, always return the empty string instead).
679
680For example:
681
682    MT::Template::Context->add_tag(ServerUptime => sub { `uptime` });
683
684This tag would be used in a template as C<E<lt>$MTServerUptime$E<gt>>.
685
686The subroutine reference will be passed two arguments: the
687I<MT::Template::Context> object with which the template is being built, and
688a reference to a hash containing the arguments passed in through the template
689tag. For example, if a tag C<E<lt>$MTFooBar$E<gt>> were called like
690
691    <$MTFooBar baz="1" quux="2"$>
692
693the second argument to the subroutine registered with this tag would be
694
695    {
696        'quux' => 2,
697        'bar' => 1
698    };
699
700=head2 MT::Template::Context->add_container_tag($name, \&subroutine)
701
702Registers a "container tag" with the template system. Container tags are
703generally used to represent either a loop or a conditional. In practice, you
704should probably use I<add_container_tag> just for loops--use
705I<add_conditional_tag> for a conditional, because it will take care of much
706of the backend work for you (most conditional tag handlers have a similar
707structure).
708
709I<$name> is the name of the tag, without the I<MT> prefix, and
710I<\&subroutine> a reference to a subroutine (either anonymous or named).
711I<\&subroutine> should return either an error (see L<ERROR HANDLING>) or
712a defined scalar value (returning C<undef> will be treated as an error, so
713instead of returning C<undef>, always return the empty string instead).
714
715The subroutine reference will be passed two arguments: the
716I<MT::Template::Context> object with which the template is being built, and
717a reference to a hash containing the arguments passed in through the template
718tag.
719
720Since a container tag generally represents a loop, inside of your subroutine
721you will need to use a loop construct to loop over some list of items, and
722build the template tags used inside of the container for each of those
723items. These inner template tags have B<already been compiled into a list of
724tokens>. You need only use the I<MT::Builder> object to build this list of
725tokens into a scalar string, then add the string to your output value. The
726list of tokens is in C<$ctx-E<gt>stash('tokens')>, and the I<MT::Builder>
727object is in C<$ctx-E<gt>stash('builder')>.
728
729For example, if a tag C<E<lt>MTLoopE<gt>> were used like this:
730
731    <MTLoop>
732    The value of I is: <$MTLoopIValue$>
733    </MTLoop>
734
735a sample implementation of this set of tags might look like this:
736
737    MT::Template::Context->add_container_tag(Loop => sub {
738        my $ctx = shift;
739        my $res = '';
740        my $builder = $ctx->stash('builder');
741        my $tokens = $ctx->stash('tokens');
742        for my $i (1..5) {
743            $ctx->stash('i_value', $i);
744            defined(my $out = $builder->build($ctx, $tokens))
745                or return $ctx->error($builder->errstr);
746            $res .= $out;
747        }
748        $res;
749    });
750
751    MT::Template::Context->add_tag(LoopIValue => sub {
752        my $ctx = shift;
753        $ctx->stash('i_value');
754    });
755
756C<E<lt>$MTLoopIValue$E<gt>> is a simple variable tag. C<E<lt>MTLoopE<gt>> is
757registered as a container tag, and it loops over the numbers 1 through 5,
758building the list of tokens between C<E<lt>MTLoopE<gt>> and
759C<E<lt>/MTLoopE<gt>> for each number. It checks for an error return value
760from the C<$builder-E<gt>build> invocation each time through.
761
762Use of the tags above would produce:
763
764    The value of I is: 1
765    The value of I is: 2
766    The value of I is: 3
767    The value of I is: 4
768    The value of I is: 5
769
770=head2 MT::Template::Context->add_conditional_tag($name, $condition)
771
772Registers a conditional tag with the template system.
773
774Conditional tags are technically just container tags, but in order to make
775it very easy to write conditional tags, you can use the I<add_conditional_tag>
776method. I<$name> is the name of the tag, without the I<MT> prefix, and
777I<$condition> is a reference to a subroutine which should return true if
778the condition is true, and false otherwise. If the condition is true, the
779block of tags and markup inside of the conditional tag will be executed and
780displayed; otherwise, it will be ignored.
781
782For example, the following code registers two conditional tags:
783
784    MT::Template::Context->add_conditional_tag(IfYes => sub { 1 });
785    MT::Template::Context->add_conditional_tag(IfNo => sub { 0 });
786
787C<E<lt>MTIfYesE<gt>> will always display its contents, because it always
788returns 1; C<E<lt>MTIfNoE<gt>> will never display is contents, because it
789always returns 0. So if these tags were to be used like this:
790
791    <MTIfYes>Yes, this appears.</MTIfYes>
792    <MTIfNo>No, this doesn't appear.</MTIfNo>
793
794Only "Yes, this appears." would be displayed.
795
796A more interesting example is to add a tag C<E<lt>MTEntryIfTitleE<gt>>,
797to be used in entry context, and which will display its contents if the
798entry has a title.
799
800    MT::Template::Context->add_conditional_tag(EntryIfTitle => sub {
801        my $e = $_[0]->stash('entry') or return;
802        defined($e->title) && $e->title ne '';
803    });
804
805To be used like this:
806
807    <MTEntries>
808    <MTEntryIfTitle>
809    This entry has a title: <$MTEntryTitle$>
810    </MTEntryIfTitle>
811    </MTEntries>
812
813=head2 MT::Template::Context->add_global_filter($name, \&subroutine)
814
815Registers a global tag attribute. More information is available in the
816Movable Type manual, in the Template Tags section, in "Global Tag Attributes".
817
818Global tag attributes can be used in any tag, and are essentially global
819filters, used to filter the normal output of the tag and modify it in some
820way. For example, the I<lower_case> global tag attribute can be used like
821this:
822
823    <$MTEntryTitle lower_case="1"$>
824
825and will transform all entry titles to lower-case.
826
827Using I<add_global_filter> you can add your own global filters. I<$name>
828is the name of the filter (this should be lower-case for consistency), and
829I<\&subroutine> is a reference to a subroutine that will be called to
830transform the normal output of the tag. I<\&subroutine> will be given three
831arguments: the standard scalar output of the tag, the value of the attribute
832(C<1> in the above I<lower_case> example), and the I<MT::Template::Context>
833object being used to build the template.
834
835For example, the following adds a I<rot13> filter:
836
837    MT::Template::Context->add_global_filter(rot13 => sub {
838        (my $s = shift) =~ tr/a-zA-Z/n-za-mN-ZA-M/;
839        $s;
840    });
841
842Which can be used like this:
843
844    <$MTEntryTitle rot13="1"$>
845
846Another example: if we wished to implement the built-in I<trim_to> filter
847using I<add_global_filter>, we would use this:
848
849    MT::Template::Context->add_global_filter(trim_to => sub {
850        my($str, $len, $ctx) = @_;
851        $str = substr $str, 0, $len if $len < length($str);
852        $str;
853    });
854
855The second argument (I<$len>) is used here to determine the length to which
856the string (I<$str>) should be trimmed.
857
858Note: If you add multiple global filters, the order in which they are called
859is undefined, so you should not rely on any particular ordering.
860
861=head2 $ctx->stash($key [, $value ])
862
863A simple data stash that can be used to store data between calls to different
864tags in your plugin. For example, this is very useful when implementing a
865container tag, as we saw above in the implementation of C<E<lt>MTLoopE<gt>>.
866
867I<$key> should be a scalar string identifying the data that you are stashing.
868I<$value>, if provided>, should be any scalar value (a string, a number, a
869reference, an object, etc).
870
871When called with only I<$key>, returns the stashed value for I<$key>; when
872called with both I<$key> and I<$value>, sets the stash for I<$key> to
873I<$value>.
874
875=head1 ERROR HANDLING
876
877If an error occurs in one of the subroutine handlers within your plugin,
878you should return an error by calling the I<error> method on the I<$ctx>
879object:
880
881    return $ctx->error("the error message");
882
883In particular, you might wish to use this if your tag expects to be called
884in a particular context. For example, the C<E<lt>$MTEntry*$E<gt>> tags all
885expect that when they are called, an entry will be in context. So they all
886use
887
888    my $entry = $ctx->stash('entry')
889        or return $ctx->error("Tag called without an entry in context");
890
891to ensure this.
892
893=head1 AUTHOR & COPYRIGHT
894
895Please see the I<MT> manpage for author, copyright, and license information.
896
897=cut
Note: See TracBrowser for help on using the browser.