File: Provider.pm

package info (click to toggle)
slash 2.2.6-8etch1
  • links: PTS
  • area: main
  • in suites: etch
  • size: 3,672 kB
  • ctags: 1,915
  • sloc: perl: 23,113; sql: 1,878; sh: 433; makefile: 233
file content (277 lines) | stat: -rwxr-xr-x 7,881 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# This code is a part of Slash, and is released under the GPL.
# Copyright 1997-2001 by Open Source Development Network. See README
# and COPYING for more information, or see http://slashcode.com/.
# $Id: Provider.pm,v 1.2.2.7 2001/10/02 20:41:22 pudge Exp $

package Slash::Display::Provider;

=head1 NAME

Slash::Display::Provider - Template Toolkit provider for Slash

=head1 SYNOPSIS

	use Slash::Display::Provider;
	my $template = Template->new(
		LOAD_TEMPLATES	=> [ Slash::Display::Provider->new ]
	);


=head1 DESCRIPTION

This here module provides templates to a Template Toolkit processor
by way of the Slash API (which basically means that it grabs templates
from the blocks table in the database).  It caches them, too.  It also
can process templates passed in as text, like the base Provider module,
but this one will create a unique name for the "anonymous" template so
it can be cached.  Overriden methods include C<fetch>, C<_load>,
and C<_refresh>.

=cut

use strict;
use vars qw($VERSION $DEBUG);
use base qw(Template::Provider);
use File::Spec::Functions;
use Slash::Utility::Environment;

($VERSION) = ' $Revision: 1.2.2.7 $ ' =~ /\$Revision:\s+([^\s]+)/;
$DEBUG     = $Template::Provider::DEBUG || 0 unless defined $DEBUG;

# BENDER: Oh, no room for Bender, huh?  Fine.  I'll go build my own lunar
# lander.  With blackjack.  And hookers.  In fact, forget the lunar lander
# and the blackjack!  Ah, screw the whole thing.

use constant PREV => 0;
use constant NAME => 1;
use constant DATA => 2;
use constant LOAD => 3;
use constant NEXT => 4;

# store names for non-named templates by using text of template as
# hash key; that it is not VirtualHost-specific is not a problem;
# this just does a name lookup, and the actual template is compiled
# and stored in the VirtualHosts' template objects
{
	my($anon_num, %anon_template);
	sub _get_anon_name {
		my($text) = @_;
		return $anon_template{$text} if exists $anon_template{$text};
		return $anon_template{$text} = 'anon_' . ++$anon_num;
	}
}

sub fetch {
	my($self, $text) = @_;
	my($name, $data, $error, $slot, $size, $compname, $compfile);
	$size = $self->{ SIZE };

	# if reference, then get a unique name to cache by
	if (ref $text eq 'SCALAR') {
		$text = $$text;
		print STDERR "fetch text : $text\n" if $DEBUG > 2;
		$name = _get_anon_name($text);
		$compname = $name if $self->{COMPILE_DIR};

	# if regular scalar, get proper template ID ("name") from DB
	} else {
		print STDERR "fetch text : $text\n" if $DEBUG > 1;
		my $slashdb = getCurrentDB();

		my $temp = $slashdb->getTemplateByName($text, [qw(tpid page section)]);
		$compname = "$text;$temp->{page};$temp->{section}"
			if $self->{COMPILE_DIR};
		$name = $temp->{tpid};
		undef $text;
	}

	if ($self->{COMPILE_DIR}) {
		my $ext = $self->{COMPILE_EXT} || '.ttc';
		$compfile = catfile($self->{COMPILE_DIR}, $compname . $ext);
		warn "compiled output: $compfile\n" if $DEBUG;
	}

	# caching disabled so load and compile but don't cache
	if (defined $size && !$size) {
		print STDERR "fetch($name) [nocache]\n" if $DEBUG;
		($data, $error) = $self->_load($name, $text);
		($data, $error) = $self->_compile($data, $compfile) unless $error;
		$data = $data->{ data } unless $error;

	# cached entry exists, so refresh slot and extract data
	} elsif ($name && ($slot = $self->{ LOOKUP }{ $name })) {
		print STDERR "fetch($name) [cached:$size]\n" if $DEBUG;
		($data, $error) = $self->_refresh($slot);
		$data = $slot->[ DATA ] unless $error;

	# nothing in cache so try to load, compile and cache
	} else {
		print STDERR "fetch($name) [uncached:$size]\n" if $DEBUG;
		($data, $error) = $self->_load($name, $text);
		($data, $error) = $self->_compile($data, $compfile) unless $error;
		$data = $self->_store($name, $data) unless $error;
	}

	return($data, $error);
}

sub _load {
	my($self, $name, $text) = @_;
	my($data, $error, $now, $time);
	$now = time;
	$time = 0;

	print STDERR "_load(@_[1 .. $#_])\n" if $DEBUG;

	if (! defined $text) {
		my $slashdb = getCurrentDB();
		# in arrayref so we also get _modtime
		my $temp = $slashdb->getTemplate($name, ['template']);
		$text = $temp->{template};
		$time = $temp->{_modtime};
	}

	# just in case ... most data from DB will be in CRLF, doesn't
	# hurt to do this quick s///
	$text =~ s/\015\012/\n/g;

	$data = {
		name	=> $name,
		text	=> $text,
		'time'	=> $time,
		load	=> $now,
	};

	return($data, $error);
}

# hm, refresh is almost what we want, except we want to override
# the logic for deciding whether to reload ... can that be determined
# without reimplementing the whole method?
sub _refresh {
	my($self, $slot) = @_;
	my($head, $file, $data, $error);

	print STDERR "_refresh([ @$slot ])\n" if $DEBUG;

	# compare load time with current _modtime from API to see if
	# its modified and we need to reload it
	if ($slot->[ DATA ]{modtime}) {
		my $slashdb = getCurrentDB();
		my $temp = $slashdb->getTemplate($slot->[ NAME ], ['tpid']);

		if ($slot->[ DATA ]{modtime} < $temp->{_modtime}) {
			print STDERR "refreshing cache file ", $slot->[ NAME ], "\n"
				if $DEBUG;

			($data, $error) = $self->_load($slot->[ NAME ]);
			($data, $error) = $self->_compile($data) unless $error;
			$slot->[ DATA ] = $data->{ data } unless $error;
		}
	}

	# i know it is not a huge amount of cycles, but i wish
	# we didn't have to bother with LRU stuff if SIZE is undef,
	# but we don't want to break other methods that also use it

	# 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);
}


# this may be its own module someday if it grows at all
package Slash::Display::Directive;

use base qw(Template::Directive);
use Slash::Utility::Environment;

# this is essentially the same as Template::Directive, but we want
# to hijack simple calls to $constants to optimize it
# I imagine this is still faster than using Stash::XS ... -- pudge
sub ident {
	my ($class, $ident) = @_;
	return "''" unless @$ident;

	my $types = qr/^(constants|form|user)$/;
	if ($ident->[0] =~ $types && (my $type = $1) && @$ident == 4 && $ident->[2] =~ /^'(.+)'$/s) {
		(my $data = $1) =~ s/'/\\'/;
		return "\$${type}->{'$data'}";
	# env
	} elsif ($ident->[0] eq q['env'] && @$ident == 4 && $ident->[2] =~ /^'(.+)'$/s) {
		(my $data = $1) =~ s/'/\\'/;
		return qq[\$ENV{"\\U$data"}];
	# fg/bg
	} elsif ($ident->[0] eq q['user'] && @$ident == 6 && $ident->[2] =~ /^'(fg|bg)'$/s) {
		return "\$user->{'$1'}[$ident->[4]]";
	}

	if (scalar @$ident <= 2 && ! $ident->[1]) {
		$ident = $ident->[0];
	} else {
		$ident = '[' . join(', ', @$ident) . ']';
	}
	return "\$stash->get($ident)";
}


sub template {
	my($class, $block) = @_;
	$block = pad($block, 2) if $Template::Directive::PRETTY;

	return "sub { return '' }" unless $block =~ /\S/;

	my $extra;
	$extra .= "my \$user = Slash::getCurrentUser();\n" if $block =~ /\$user->/;
	$extra .= "my \$form = Slash::getCurrentForm();\n" if $block =~ /\$form->/;
	$extra .= "my \$constants = Slash::getCurrentStatic();\n" if $block =~ /\$constants->/;

    return <<EOF;
sub {
    my \$context = shift || die "template sub called without context\\n";
    my \$stash   = \$context->stash;
    my \$output  = '';
    my \$error;
    
    eval { BLOCK: {
$extra
$block
    } };
    if (\$@) {
        \$error = \$context->catch(\$@, \\\$output);
	die \$error unless \$error->type eq 'return';
    }

    return \$output;
}
EOF
}


1;

__END__


=head1 SEE ALSO

Template(3), Template::Provider(3), Slash(3), Slash::Utility(3),
Slash::Display(3).