mourningdove/cgi-bin/DW/Template.pm
2026-05-24 01:03:05 +00:00

410 lines
10 KiB
Perl

#!/usr/bin/perl
#
# DW::Template
#
# Template Toolkit helpers for Apache2.
#
# Authors:
# Andrea Nall <anall@andreanall.com>
#
# Copyright (c) 2009-2013 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
package DW::Template;
use strict;
use Template;
use Template::Plugins;
use Template::Namespace::Constants;
use DW::FragmentCache;
use DW::Request;
use LJ::Directories;
=head1 NAME
DW::Template - Template Toolkit helpers for Apache2.
=head1 SYNOPSIS
=cut
# setting this to false -- have to explicitly specify which plugins we want.
$Template::Plugins::PLUGIN_BASE = '';
my $site_constants = Template::Namespace::Constants->new(
{
name => $LJ::SITENAME,
nameshort => $LJ::SITENAMESHORT,
nameabbrev => $LJ::SITENAMEABBREV,
company => $LJ::SITECOMPANY,
address => $LJ::SITEADDRESS,
addressline => $LJ::SITEADDRESSLINE,
domain => $LJ::DOMAIN,
domainweb => $LJ::DOMAIN_WEB,
help => \%LJ::HELPURL,
email => {
abuse => $LJ::ABUSE_EMAIL,
coppa => $LJ::COPPA_EMAIL,
privacy => $LJ::PRIVACY_EMAIL,
},
maxlength_user => $LJ::USERNAME_MAXLENGTH,
maxlength_pass => $LJ::PASSWORD_MAXLENGTH,
}
);
# precreating this
my $view_engine = Template->new(
{
INCLUDE_PATH => join( ':', LJ::get_all_directories('views') ),
NAMESPACE => {
site => $site_constants,
},
CACHE_SIZE =>
$LJ::TEMPLATE_CACHE_SIZE, # this can be undef, and that means cache everything.
STAT_TTL => $LJ::IS_DEV_SERVER ? 1 : 3600,
RECURSION => 1,
PLUGINS => {
autoformat => 'Template::Plugin::Autoformat',
date => 'Template::Plugin::Date',
url => 'Template::Plugin::URL',
dw => 'DW::Template::Plugin',
form => 'DW::Template::Plugin::FormHTML',
},
PRE_PROCESS => '_init.tt',
}
);
my $scheme_engine = Template->new(
{
INCLUDE_PATH => join( ':', LJ::get_all_directories('schemes') ),
NAMESPACE => {
site => $site_constants,
},
CACHE_SIZE =>
$LJ::TEMPLATE_CACHE_SIZE, # this can be undef, and that means cache everything.
STAT_TTL => $LJ::IS_DEV_SERVER ? 1 : 3600,
PLUGINS => {
dw => 'DW::Template::Plugin',
dw_scheme => 'DW::Template::Plugin::SiteScheme',
form => 'DW::Template::Plugin::FormHTML',
},
}
);
=head1 API
=head2 C<< $class->template_string( $filename, $opts, $extra ) >>
Render a template to a string.
=cut
sub template_string {
my ( $class, $filename, $opts, $extra ) = @_;
my $r = DW::Request->get;
$opts->{sections} = $extra;
$opts->{sections}->{errors} = $opts->{errors};
# now we have to save the scope and update it for this rendering
my $oldscope = $r->note('ml_scope');
$r->note( ml_scope => ( $extra->{ml_scope} || "/$filename" ) );
my $out;
$view_engine->process( $filename, $opts, \$out )
or die $view_engine->error->as_string;
# now revert the scope if we had one
$r->note( ml_scope => $oldscope ) if $oldscope;
return $out;
}
=head2 C<< $class->cached_template_string( $key, $filename, $opts_subref, $cache_opts, $extra ) >>
Render a template to a string -- optionally fragment caching it.
$opts_subref returns the options for template_string.
fragment opts:
=over
=item B< lock_failed > - The text returned by this subref is returned if the lock is failed and the grace period is up.
=item B< expire > - Number of seconds the fragment is valid for
=item B< grace_period > - Number of seconds that an expired fragment could still be served if the lock is in place
=back
=cut
sub cached_template_string {
my ( $class, $key, $filename, $opts_subref, $cache_opts, $extra ) = @_;
return DW::FragmentCache->get(
$key,
{
lock_failed => $cache_opts->{lock_failed},
expire => $cache_opts->{expire},
grace_period => $cache_opts->{grace_period},
render => sub {
return $class->template_string( $filename, $opts_subref->( $_[0] ), $extra );
}
},
$extra
);
}
=head2 C<< $class->render_cached_template( $key, $filename, $subref, $extra ) >>
Render a template inside the sitescheme or alone.
See render_template, except note that the opts hash is returned by opts_subref if it's needed.
$cache_opts can contain:
=over
=item B< no_sitescheme > == render alone
=item B< title / windowtitle / head / bodyopts / ... > == text to get thrown in the section if inside sitescheme
=item B< lock_failed > = subref for lock failed.
=item B< expire > - Number of seconds the fragment is valid for
=item B< grace_period > - Number of seconds that an expired fragment could still be served if the lock is in place
=back
=cut
sub render_cached_template {
my ( $class, $key, $filename, $opts_subref, $cache_opts, $extra ) = @_;
$extra ||= {};
my $out = $class->cached_template_string( $key, $filename, $opts_subref, $cache_opts, $extra );
return $class->render_string( $out, $extra );
}
=head2 C<< $class->render_template( $filename, $opts, $extra ) >>
Render a template inside the sitescheme or alone.
$extra can contain:
=over
=item B< no_sitescheme > == render alone
=item B< title / windowtitle / head / bodyopts / ... > == text to get thrown in the section if inside sitescheme
=back
=cut
sub render_template {
my ( $class, $filename, $opts, $extra ) = @_;
$extra ||= {};
my $out = $class->template_string( $filename, $opts, $extra );
return $class->render_string( $out, $extra );
}
=head2 C<< $class->render_template_misc( $filename, $opts, $extra ) >>
Render a template inside the sitescheme or alone.
This can also be safely called ( with some work on the other side )
from a BML context and still spit the content where required.
( Note, the "alone" bit will be ignored from BML contexts )
Can safely directly return this from either trans/Controller, internal journal page generation or (most) BML contexts.
$extra can contain:
=over
=item B< no_sitescheme > == render alone
=over
This will be ignored for 'bml' scopes.
=back
=item B< title / windowtitle / head / bodyopts / ... > == text to get thrown in the section if inside sitescheme
=item B< scope > = Scope, accepts nothing, 'bml', or 'journal'
=item B< scope_data > = Depends on B< scope >
=over
=item B< bml > Hashref of scalar-refs of where to throw the sections
=item B< journal > $opts hashref passed into LJ::make_journal and beyond.
=back
=back
=cut
# FIXME(dre): Remove this method when BML is completely dead
# and refactor the journal scope bits up into render_template or render_string.
sub render_template_misc {
my ( $class, $filename, $opts, $extra ) = @_;
$extra ||= {};
my $out = $class->template_string( $filename, $opts, $extra );
my $scope = $extra->{scope} // '';
if ( $scope eq 'bml' ) {
my $r = DW::Request->get;
my $bml = $extra->{scope_data};
for my $item (qw(title windowtitle head bodyopts)) {
${ $bml->{$item} } = $extra->{$item} || "";
}
return $out;
}
my $rv = $class->render_string( $out, $extra );
if ( $scope eq 'journal' ) {
$extra->{scope_data}->{handler_return} = $rv;
return;
}
else {
return $rv;
}
}
=head2 C<< $class->render_string( $string, $extra ) >>
Render a string inside the sitescheme or alone.
$extra can contain:
=over
=item B< no_sitescheme > == render alone
=over
If you are just printing text or other data, do not call DW::Template->render_string and instead just
$r->print and return $r->OK.
This is mostly for being used from render_template.
=back
=item B< title / windowtitle / head / bodyopts / ... > == text to get thrown in the section if inside sitescheme
=back
=cut
sub render_string {
my ( $class, $out, $extra ) = @_;
my $r = DW::Request->get;
my $scheme = DW::SiteScheme->get;
if ( $extra->{no_sitescheme} ) {
$r->print($out);
return $r->OK;
}
elsif ( $extra->{fragment} ) {
LJ::set_active_resource_group("fragment");
$out .= LJ::res_includes( nojs => 1, nolib => 1 );
$r->print($out);
return $r->OK;
}
elsif ( $scheme->supports_tt ) {
$r->content_type("text/html; charset=utf-8");
$r->print( $class->render_scheme( $scheme, $out, $extra ) );
return $r->OK;
}
else {
die "Can not use invalid/unknown engine "
. $scheme->engine
. " for scheme "
. $scheme->name;
}
}
=head2 C<< $class->render_scheme( $sitescheme, $body, $sections ) >>
Render the body and sections in a TT sitescheme
=over
=item B< sitescheme >
A DW::SiteScheme object
=back
=cut
sub render_scheme {
my ( $class, $scheme, $body, $sections ) = @_;
my $r = DW::Request->get;
my $out;
my $args = $r->query_string;
my $baseuri = "$LJ::PROTOCOL://" . $r->host . $r->uri;
$baseuri .= $args ? "?$args" : "";
my $opts = $scheme->get_vars;
$opts->{sections} = $sections;
$opts->{inheritance} = [ map { "$_.tt" } reverse $scheme->inheritance ];
$opts->{content} = $body;
$opts->{get} = $r->get_args;
$opts->{resource_group} = $LJ::ACTIVE_RES_GROUP;
$opts->{msgs} = $r->msgs;
$opts->{returnto} = $baseuri;
$scheme_engine->process( "_init.tt", $opts, \$out )
or die $scheme_engine->error->as_string;
$r->clear_msgs;
return $out;
}
=head1 AUTHOR
=over
=item Andrea Nall <anall@andreanall.com>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2009-2011 by Dreamwidth Studios, LLC.
This program is free software; you may redistribute it and/or modify it under
the same terms as Perl itself. For a copy of the license, please reference
'perldoc perlartistic' or 'perldoc perlgpl'.
=cut
1;