| OpenInteract documentation | Contained in the OpenInteract distribution. |
OpenInteract::Template::Provider - Retrieve templates for the Template Toolkit
$Template::Config::CONTEXT = 'OpenInteract::Template::Context';
my $template = Template->new(
COMPILE_DIR => '/tmp/ttc',
COMPILE_EXT => '.ttc',
LOAD_TEMPLATES => [ OpenInteract::Template::Provider->new ] );
my ( $output );
$template->process( 'package::template', \%params, \$output );
NOTE: As shown above, you need to use OpenInteract::Template::Context as a context for your templates since our naming scheme ('package::name') collides with the TT naming scheme for specifying a prefix before a template.
This package is a provider for the Template Toolkit while running under OpenInteract. Being a provider means that TT hands off any requests for templates to this class, which has OpenInteract-specific naming conventions (e.g., 'package::template') and knows how to find templates in the sitewide package template directory or the normal package template directory
All of the following are object methods and have as the first argument the object itself.
fetch( $text )
Overrides Template::Provider.
Uses $text to somehow retrieve a template. The actual work to
retrieve a template is done in _load(), although this method
ensures that the template name is 'safe' and creates a name we use to
save the compiled template.
Returns a two-element list: the first is a compiled template, the second is an error message. (Of course, if there is no error the second item will be undefined.)
_load( $name, $content )
Loads the template content, returning a two-element list. The first item in the list is the TT hashref, the second is an error message.
We try three ways to retrieve a template, in this order:
scalar reference: If the template is a scalar reference it does not
need to be retrieved, so we just put $content in the TT hashref
structure as the data to process and return it.
glob reference: If the template is a glob reference we treat it as
a filehandle and read all data from $content in the TT hashref
structure as the data to process as return it.
filesystem template: Templates can be stored in the filesystem. If
a template does not use $package it can be found under
$WEBSITE_DIR/template; if it does, it can be found under
$WEBSITE_DIR/template/$package or
$WEBSITE_DIR/pkg/$package-version/template, in that order.
_refresh( $cache_slot )
Called when we use $cache_slot for a template. This refreshes the
time of the slot and brings it to the head of the LRU cache.
You can tune the expiration time of the cache by setting the key:
{cache}{template}{expire}
in your server configuration file to the amount of time (in seconds) to keep an entry in the cache.
_validate_template_name( $full_template_name )
Ensures that $full_template_name does not have any tricky
filesystem characters (e.g., '..') in it.
_get_anon_name( $text )
If we get an anonymous template to provide, we need to create a unique
name for it so we can compile and cache it properly. This method
returns a unique name based on $text.
None known.
Testing
Needs more testing in varied environments.
Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Chris Winters <chris@cwinters.com>
Robert McArthur <mcarthur@dstc.edu.au>
Authors of Slashcode http://www.slashcode.com/
| OpenInteract documentation | Contained in the OpenInteract distribution. |
package OpenInteract::Template::Provider; # $Id: Provider.pm,v 1.25 2002/09/16 20:18:35 lachoy Exp $ use strict; use base qw( Template::Provider ); use Data::Dumper qw( Dumper ); use Digest::MD5 qw(); use File::Spec qw(); $OpenInteract::Template::Provider::VERSION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/); use constant DEFAULT_MAX_CACHE_TIME => 60 * 30; use constant DEFAULT_TEMPLATE_EXTENSION => 'template'; use constant DEFAULT_PACKAGE_TEMPLATE_DIR => 'template/'; use constant DEFAULT_TEMPLATE_TYPE => 'filesystem'; # This is the level to set for $R->scrib -- if you set it to 0 # everything will be logged. (And this is a lot!) my $DEBUG = 2; sub DEBUG_LEVEL { return $DEBUG } sub SET_DEBUG_LEVEL { $DEBUG = $_[1] } # Copied from Template::Provider since they're not exported use constant PREV => 0; use constant NAME => 1; use constant DATA => 2; use constant LOAD => 3; use constant NEXT => 4; use constant STAT => 5; # This should return a two-item list: the first is the template to be # processed, the second is an error (if any). $name is a simple name # of a template, which in our case is often of the form # 'package::template_name'. sub fetch { my ( $self, $text ) = @_; my $R = OpenInteract::Request->instance; my ( $name ); # if scalar or glob reference, then get a unique name to cache by if ( ref( $text ) eq 'SCALAR' ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "anonymous template passed in" ); $name = $self->_get_anon_name( $text ); } elsif ( ref( $text ) eq 'GLOB' ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "GLOB passed in to fetch" ); $name = $self->_get_anon_name( $text ); } # Otherwise, it's a 'package::template' name or a unique filename # found in '$WEBSITE_DIR/template', both of which are handled in # _load() below. Also check that the template name doesn't have # any invalid characters (e.g., '../../../etc/passwd') else { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "info passed in is site filename or package::template;", "will check file system or database for ($text)" ); $name = $text; undef $text; eval { $self->_validate_template_name( $name ) }; if ( $@ ) { return ( $@, Template::Constants::STATUS_ERROR ) } } # If we have a directory to compile the templates to, create a # unique filename for this template # Just keep the compile name the same as the name passed # in, replacing '::' with '-' my ( $compile_file ); if ( $self->{COMPILE_DIR} ) { my $ext = $self->{COMPILE_EXT} || '.ttc'; my $compile_name = $name; $compile_name =~ s/::/-/g; $compile_file = File::Spec->catfile( $self->{COMPILE_DIR}, $compile_name . $ext ); $R->DEBUG && $R->scrib( DEBUG_LEVEL, "compiled output filename ", "[$compile_file]" ); } my ( $data, $error ); # caching disabled (cache size is 0) so load and compile but don't cache if ( $self->{SIZE} == 0 ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "fetch( $name ) [caching disabled]" ); ( $data, $error ) = $self->_load( $name, $text ); ( $data, $error ) = $self->_compile( $data, $compile_file ) unless ( $error ); $data = $data->{data} unless ( $error ); } # cached entry exists, so refresh slot and extract data elsif ( $name and ( my $cache_slot = $self->{LOOKUP}{ $name } ) ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "fetch( $name ) [cached (limit: $self->{SIZE})]" ); ( $data, $error ) = $self->_refresh( $cache_slot ); $data = $cache_slot->[ DATA ] unless ( $error ); } # nothing in cache so try to load, compile and cache else { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "fetch( $name ) [uncached (limit: $self->{SIZE})]" ); ( $data, $error ) = $self->_load( $name, $text ); ( $data, $error ) = $self->_compile( $data, $compile_file ) unless ( $error ); $data = $self->_store( $name, $data ) unless ( $error ); } return( $data, $error ); } # NOTE: You should NEVER even check to see if $name exists anywhere # else on the filesystem besides under the $WEBSITE_DIR. The # SiteTemplate object takes care of this, but it's just another # warning... # # From Template::Provider -- here's what the hashref includes: # # name filename or $content, if provided, or 'input text', etc. # text template text # time modification time of file, or current time for handles/strings # (we also use this for the 'last_update' field of an SPOPS object) # load time file/object was loaded (now!) sub _load { my ( $self, $name, $content ) = @_; my $R = OpenInteract::Request->instance; $R->DEBUG && $R->scrib( DEBUG_LEVEL, "_load(@_[1 .. $#_])\n" ); # If no name, $self->{TOLERANT} being true means we can decline # safely. Otherwise return an error. We might modify this in the # future to not even check TOLERANT -- if it's not defined we # don't want anything to do with it, and nobody else should either # (NYAH!). Note that $name should be defined even if we're doing a # scalar ref or glob template unless ( defined $name ) { if ( $self->{TOLERANT} ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "No name passed in and ", "TOLERANT set, so decline" ); return ( undef, Template::Constants::STATUS_DECLINED ); } $R->DEBUG && $R->scrib( DEBUG_LEVEL, "No name passed in and TOLERANT ", "not set, so return error" ); return ( "No template", Template::Constants::STATUS_ERROR ); } # is this an anonymous template? if so, return it # Note: it would be cool if we could figure out where 'name' is # passed to and have it deal with references properly, and then # propogate that reference through to processing, etc. if ( ref( $content ) eq 'SCALAR' ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "Nothing to load since ", "template is scalar ref." ); return ({ 'name' => $name, 'text' => $$content, 'time' => time, 'load' => 0 }, undef ); } if ( ref( $content ) eq 'GLOB' ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "Template is glob (file) ", "ref, so read in" ); local $/ = undef; return ({ 'name' => 'file handle', 'text' => <$content>, 'time' => time, 'load' => 0 }, undef ); } my ( $content_template, $data ); eval { $content_template = $R->site_template->fetch( $name ); unless ( $content_template ) { die "Template with name [$name] not found.\n"; } $data = { 'name' => $content_template->full_filename, 'text' => $content_template->contents, 'time' => $content_template->modified_on, 'load' => time }; }; if ( $@ ) { return ( $@, Template::Constants::STATUS_ERROR ); } return ( $data, undef ); } # Override so we can use OI-configured value for seeing whether we # need to refresh sub _refresh { my ( $self, $slot ) = @_; my ( $head, $file, $data, $error ); my $R = OpenInteract::Request->instance; $R->DEBUG && $R->scrib( DEBUG_LEVEL, "_refresh([ @$slot ])" ); # If the cache time has expired reload the entry my $do_reload = 0; my $max_cache_time = $R->CONFIG->{cache}{template}{expire} || DEFAULT_MAX_CACHE_TIME; if ( ( $slot->[ DATA ]->{'time'} - time ) > $max_cache_time ) { $R->DEBUG && $R->scrib( DEBUG_LEVEL, "Refreshing cache for template: ", $slot->[ NAME ] ); ( $data, $error ) = $self->_load( $slot->[ NAME ] ); ( $data, $error ) = $self->_compile( $data ) unless ( $error ); unless ( $error ) { $slot->[ DATA ] = $data->{ data }; $slot->[ LOAD ] = $data->{ time }; } } # remove existing slot from usage chain... if ( $slot->[ PREV ] ) { $slot->[ PREV ][ NEXT ] = $slot->[ NEXT ]; } else { $self->{ HEAD } = $slot->[ NEXT ]; } if ( $slot->[ NEXT ] ) { $slot->[ NEXT ][ PREV ] = $slot->[ PREV ]; } else { $self->{ TAIL } = $slot->[ PREV ]; } # ... and add to start of list $head = $self->{ HEAD }; $head->[ PREV ] = $slot if ( $head ); $slot->[ PREV ] = undef; $slot->[ NEXT ] = $head; $self->{ HEAD } = $slot; return ( $data, $error ); } # Ensure there aren't any funny characters sub _validate_template_name { my ( $self, $name ) = @_; if ( $name =~ m|\.\.| ) { die "Template name must not have any directory tree symbols (e.g., '..')"; } if ( $name =~ m|^/| ) { die "Template name must not begin with an absolute path symbol"; } return 1; } ######################################## # ANONYMOUS TEMPLATE NAME # store names for non-named templates by using a unique fingerprint of # the template text as a hash key my $ANON_NUM = 0; my %ANON_TEMPLATE = (); sub _get_anon_name { my ( $self, $text ) = @_; my $key = Digest::MD5::md5_hex( ref( $text ) ? $$text : $text ); return $ANON_TEMPLATE{ $key } if ( exists $ANON_TEMPLATE{ $key } ); return $ANON_TEMPLATE{ $key } = 'anon_' . ++$ANON_NUM; } 1; __END__