| 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 | |
|---|
| 7 | package MT::Template::Context; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use base qw( MT::ErrorHandler ); |
|---|
| 11 | |
|---|
| 12 | use constant FALSE => -99999; |
|---|
| 13 | use Exporter; |
|---|
| 14 | *import = \&Exporter::import; |
|---|
| 15 | use vars qw( @EXPORT ); |
|---|
| 16 | @EXPORT = qw( FALSE ); |
|---|
| 17 | use MT::Util qw( weaken ); |
|---|
| 18 | use MT::I18N qw( substr_text length_text ); |
|---|
| 19 | |
|---|
| 20 | our (%Handlers, %Filters); |
|---|
| 21 | |
|---|
| 22 | sub new { |
|---|
| 23 | my $class = shift; |
|---|
| 24 | require MT::Template::ContextHandlers; |
|---|
| 25 | my $ctx = bless {}, $class; |
|---|
| 26 | $ctx->init(@_); |
|---|
| 27 | } |
|---|
| 28 | |
|---|
| 29 | sub init { |
|---|
| 30 | my $ctx = shift; |
|---|
| 31 | weaken($ctx->{config} = MT->config); |
|---|
| 32 | $ctx->init_handlers(); |
|---|
| 33 | $ctx; |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | sub 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 | |
|---|
| 45 | sub 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 | |
|---|
| 122 | sub 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 | |
|---|
| 137 | sub 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 | |
|---|
| 148 | sub 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 | |
|---|
| 174 | sub this_tag { |
|---|
| 175 | my $ctx = shift; |
|---|
| 176 | return 'mt' . lc( $ctx->stash('tag') ); |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | sub 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 | |
|---|
| 187 | sub 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 | |
|---|
| 262 | sub post_process_handler { |
|---|
| 263 | \&stock_post_process_handler; |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | sub 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 | |
|---|
| 276 | sub 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 | |
|---|
| 286 | sub 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 | |
|---|
| 298 | sub 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 $blog_id && $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 | |
|---|
| 346 | sub 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 | |
|---|
| 437 | sub 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 | |
|---|
| 499 | sub 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 | |
|---|
| 527 | sub 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 | |
|---|
| 549 | sub 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 | |
|---|
| 571 | sub _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 | |
|---|
| 581 | sub _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 | |
|---|
| 591 | sub _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 | |
|---|
| 602 | sub _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 | |
|---|
| 613 | sub _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 | |
|---|
| 624 | sub _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 | |
|---|
| 635 | 1; |
|---|
| 636 | __END__ |
|---|
| 637 | |
|---|
| 638 | =head1 NAME |
|---|
| 639 | |
|---|
| 640 | MT::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 | |
|---|
| 657 | I<MT::Template::Context> provides the implementation for all of the built-in |
|---|
| 658 | template tags in Movable Type, as well as the public interface to the |
|---|
| 659 | system's plugin interface. |
|---|
| 660 | |
|---|
| 661 | This document focuses only on the public methods needed to implement plugins |
|---|
| 662 | in Movable Type, and the methods that plugin developers might wish to make |
|---|
| 663 | use of. Of course, plugins can make use of other objects loaded from the |
|---|
| 664 | Movable Type database, in which case you may wish to look at the documentation |
|---|
| 665 | for the classes in question (for example, I<MT::Entry>). |
|---|
| 666 | |
|---|
| 667 | =head1 USAGE |
|---|
| 668 | |
|---|
| 669 | =head2 MT::Template::Context->add_tag($name, \&subroutine) |
|---|
| 670 | |
|---|
| 671 | I<add_tag> registers a simple "variable tag" with the system. An example of |
|---|
| 672 | such a tag might be C<E<lt>$MTEntryTitle$E<gt>>. |
|---|
| 673 | |
|---|
| 674 | I<$name> is the name of the tag, without the I<MT> prefix, and |
|---|
| 675 | I<\&subroutine> a reference to a subroutine (either anonymous or named). |
|---|
| 676 | I<\&subroutine> should return either an error (see L<ERROR HANDLING>) or |
|---|
| 677 | a defined scalar value (returning C<undef> will be treated as an error, so |
|---|
| 678 | instead of returning C<undef>, always return the empty string instead). |
|---|
| 679 | |
|---|
| 680 | For example: |
|---|
| 681 | |
|---|
| 682 | MT::Template::Context->add_tag(ServerUptime => sub { `uptime` }); |
|---|
| 683 | |
|---|
| 684 | This tag would be used in a template as C<E<lt>$MTServerUptime$E<gt>>. |
|---|
| 685 | |
|---|
| 686 | The subroutine reference will be passed two arguments: the |
|---|
| 687 | I<MT::Template::Context> object with which the template is being built, and |
|---|
| 688 | a reference to a hash containing the arguments passed in through the template |
|---|
| 689 | tag. For example, if a tag C<E<lt>$MTFooBar$E<gt>> were called like |
|---|
| 690 | |
|---|
| 691 | <$MTFooBar baz="1" quux="2"$> |
|---|
| 692 | |
|---|
| 693 | the 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 | |
|---|
| 702 | Registers a "container tag" with the template system. Container tags are |
|---|
| 703 | generally used to represent either a loop or a conditional. In practice, you |
|---|
| 704 | should probably use I<add_container_tag> just for loops--use |
|---|
| 705 | I<add_conditional_tag> for a conditional, because it will take care of much |
|---|
| 706 | of the backend work for you (most conditional tag handlers have a similar |
|---|
| 707 | structure). |
|---|
| 708 | |
|---|
| 709 | I<$name> is the name of the tag, without the I<MT> prefix, and |
|---|
| 710 | I<\&subroutine> a reference to a subroutine (either anonymous or named). |
|---|
| 711 | I<\&subroutine> should return either an error (see L<ERROR HANDLING>) or |
|---|
| 712 | a defined scalar value (returning C<undef> will be treated as an error, so |
|---|
| 713 | instead of returning C<undef>, always return the empty string instead). |
|---|
| 714 | |
|---|
| 715 | The subroutine reference will be passed two arguments: the |
|---|
| 716 | I<MT::Template::Context> object with which the template is being built, and |
|---|
| 717 | a reference to a hash containing the arguments passed in through the template |
|---|
| 718 | tag. |
|---|
| 719 | |
|---|
| 720 | Since a container tag generally represents a loop, inside of your subroutine |
|---|
| 721 | you will need to use a loop construct to loop over some list of items, and |
|---|
| 722 | build the template tags used inside of the container for each of those |
|---|
| 723 | items. These inner template tags have B<already been compiled into a list of |
|---|
| 724 | tokens>. You need only use the I<MT::Builder> object to build this list of |
|---|
| 725 | tokens into a scalar string, then add the string to your output value. The |
|---|
| 726 | list of tokens is in C<$ctx-E<gt>stash('tokens')>, and the I<MT::Builder> |
|---|
| 727 | object is in C<$ctx-E<gt>stash('builder')>. |
|---|
| 728 | |
|---|
| 729 | For 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 | |
|---|
| 735 | a 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 | |
|---|
| 756 | C<E<lt>$MTLoopIValue$E<gt>> is a simple variable tag. C<E<lt>MTLoopE<gt>> is |
|---|
| 757 | registered as a container tag, and it loops over the numbers 1 through 5, |
|---|
| 758 | building the list of tokens between C<E<lt>MTLoopE<gt>> and |
|---|
| 759 | C<E<lt>/MTLoopE<gt>> for each number. It checks for an error return value |
|---|
| 760 | from the C<$builder-E<gt>build> invocation each time through. |
|---|
| 761 | |
|---|
| 762 | Use 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 | |
|---|
| 772 | Registers a conditional tag with the template system. |
|---|
| 773 | |
|---|
| 774 | Conditional tags are technically just container tags, but in order to make |
|---|
| 775 | it very easy to write conditional tags, you can use the I<add_conditional_tag> |
|---|
| 776 | method. I<$name> is the name of the tag, without the I<MT> prefix, and |
|---|
| 777 | I<$condition> is a reference to a subroutine which should return true if |
|---|
| 778 | the condition is true, and false otherwise. If the condition is true, the |
|---|
| 779 | block of tags and markup inside of the conditional tag will be executed and |
|---|
| 780 | displayed; otherwise, it will be ignored. |
|---|
| 781 | |
|---|
| 782 | For 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 | |
|---|
| 787 | C<E<lt>MTIfYesE<gt>> will always display its contents, because it always |
|---|
| 788 | returns 1; C<E<lt>MTIfNoE<gt>> will never display is contents, because it |
|---|
| 789 | always 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 | |
|---|
| 794 | Only "Yes, this appears." would be displayed. |
|---|
| 795 | |
|---|
| 796 | A more interesting example is to add a tag C<E<lt>MTEntryIfTitleE<gt>>, |
|---|
| 797 | to be used in entry context, and which will display its contents if the |
|---|
| 798 | entry 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 | |
|---|
| 805 | To 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 | |
|---|
| 815 | Registers a global tag attribute. More information is available in the |
|---|
| 816 | Movable Type manual, in the Template Tags section, in "Global Tag Attributes". |
|---|
| 817 | |
|---|
| 818 | Global tag attributes can be used in any tag, and are essentially global |
|---|
| 819 | filters, used to filter the normal output of the tag and modify it in some |
|---|
| 820 | way. For example, the I<lower_case> global tag attribute can be used like |
|---|
| 821 | this: |
|---|
| 822 | |
|---|
| 823 | <$MTEntryTitle lower_case="1"$> |
|---|
| 824 | |
|---|
| 825 | and will transform all entry titles to lower-case. |
|---|
| 826 | |
|---|
| 827 | Using I<add_global_filter> you can add your own global filters. I<$name> |
|---|
| 828 | is the name of the filter (this should be lower-case for consistency), and |
|---|
| 829 | I<\&subroutine> is a reference to a subroutine that will be called to |
|---|
| 830 | transform the normal output of the tag. I<\&subroutine> will be given three |
|---|
| 831 | arguments: 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> |
|---|
| 833 | object being used to build the template. |
|---|
| 834 | |
|---|
| 835 | For 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 | |
|---|
| 842 | Which can be used like this: |
|---|
| 843 | |
|---|
| 844 | <$MTEntryTitle rot13="1"$> |
|---|
| 845 | |
|---|
| 846 | Another example: if we wished to implement the built-in I<trim_to> filter |
|---|
| 847 | using 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 | |
|---|
| 855 | The second argument (I<$len>) is used here to determine the length to which |
|---|
| 856 | the string (I<$str>) should be trimmed. |
|---|
| 857 | |
|---|
| 858 | Note: If you add multiple global filters, the order in which they are called |
|---|
| 859 | is undefined, so you should not rely on any particular ordering. |
|---|
| 860 | |
|---|
| 861 | =head2 $ctx->stash($key [, $value ]) |
|---|
| 862 | |
|---|
| 863 | A simple data stash that can be used to store data between calls to different |
|---|
| 864 | tags in your plugin. For example, this is very useful when implementing a |
|---|
| 865 | container tag, as we saw above in the implementation of C<E<lt>MTLoopE<gt>>. |
|---|
| 866 | |
|---|
| 867 | I<$key> should be a scalar string identifying the data that you are stashing. |
|---|
| 868 | I<$value>, if provided>, should be any scalar value (a string, a number, a |
|---|
| 869 | reference, an object, etc). |
|---|
| 870 | |
|---|
| 871 | When called with only I<$key>, returns the stashed value for I<$key>; when |
|---|
| 872 | called with both I<$key> and I<$value>, sets the stash for I<$key> to |
|---|
| 873 | I<$value>. |
|---|
| 874 | |
|---|
| 875 | =head1 ERROR HANDLING |
|---|
| 876 | |
|---|
| 877 | If an error occurs in one of the subroutine handlers within your plugin, |
|---|
| 878 | you should return an error by calling the I<error> method on the I<$ctx> |
|---|
| 879 | object: |
|---|
| 880 | |
|---|
| 881 | return $ctx->error("the error message"); |
|---|
| 882 | |
|---|
| 883 | In particular, you might wish to use this if your tag expects to be called |
|---|
| 884 | in a particular context. For example, the C<E<lt>$MTEntry*$E<gt>> tags all |
|---|
| 885 | expect that when they are called, an entry will be in context. So they all |
|---|
| 886 | use |
|---|
| 887 | |
|---|
| 888 | my $entry = $ctx->stash('entry') |
|---|
| 889 | or return $ctx->error("Tag called without an entry in context"); |
|---|
| 890 | |
|---|
| 891 | to ensure this. |
|---|
| 892 | |
|---|
| 893 | =head1 AUTHOR & COPYRIGHT |
|---|
| 894 | |
|---|
| 895 | Please see the I<MT> manpage for author, copyright, and license information. |
|---|
| 896 | |
|---|
| 897 | =cut |
|---|