#==============================================================================
#
# CVS Log
#
# $Log: AdManager.pm,v $
# Revision 1.14 2001/10/29 10:52:47 wrigley
# v0.007 - minimal test added
#
# Revision 1.13 2001/10/26 14:40:52 wrigley
# v0.006
#
# Revision 1.12 2001/10/10 17:14:18 wrigley
# various bug fixes: new image is based on max ad no., not nads; alt text is included in new advert; ads with no ct URL are rendered as non-clickable
#
# Revision 1.11 2001/08/29 14:56:37 wrigley
# v0.004
#
# Revision 1.10 2001/08/24 17:39:39 wrigley
# v0.004
#
# Revision 1.9 2001/08/24 13:09:55 wrigley
# v0.003
#
# Revision 1.8 2001/08/23 16:55:17 wrigley
# v0.002
#
# Revision 1.7 2001/08/23 13:19:59 wrigley
# v0.001
#
# Revision 1.6 2001/08/23 11:50:04 wrigley
# *** empty log message ***
#
#
#==============================================================================
=head1 NAME
WWW::AdManager - a perl module to administer and serve online advertising
=head1 SYNOPSIS
=head2 CGI
=head3 advert interface
WWW::AdManager->new(
INTERFACE => 'ADVERT',
ADMANAGER_URL => "/admanager",
)->output();
=head3 admin interface
WWW::AdManager->new(
INTERFACE => 'ADMIN',
ADMANAGER_URL => "/admanager",
ADMANAGER_ADMIN_URL => "/internal/admanager",
)->output();
=head2 mod_perl
SetHandler perl-script
PerlHandler Apache::WWW::AdManager
PerlSetVar INTERFACE ADMIN
PerlSetVar ADMANAGER_URL /admanager
PerlSetVar ADMANAGER_ADMIN_URL /internal/admanager
SetHandler perl-script
PerlHandler Apache::WWW::AdManager
PerlSetVar INTERFACE ADVERT
PerlSetVar ADMANAGER_URL /admanager
=head1 DESCRIPTION
WWW::AdManager is a module which implements a web advert management system.
This is based around linked images, that are organized into "campaigns". Within
each campaign, the admanager randomizes the display of images, and tracks both
"page impressions" - i.e. the number of times the image is displayed - and
"clickthroughs" - i.e. the number of times the image is clicked on.
The module also provides an administration web interface, which can be served
through a access restricted URL, for creating and updating campaigns and
adverts, and displaying usage stats.
The interface support implementation both through CGI and mod_perl. mod_perl is
highly recommended, especially where there are more than one adver to display
per page.
The insertion of adverts into pages is done using SSI (server side includes -
see L, for example). The
module does both logging of each relevant transaction (clickthrough / page
impression) and live compilation of stats.
Campaigns can be sub-divided into sub-campaigns for more convenient
organization of adverts.
=cut
#==============================================================================
#
# Package declaration
#
#==============================================================================
package WWW::AdManager;
#==============================================================================
#
# Standard pragmas
#
#==============================================================================
use strict;
use warnings;
#==============================================================================
#
# Modules
#
#==============================================================================
use CGI_Lite;
use File::Path;
use TempDir;
use Data::Dumper;
use IO::File;
use IO::Dir;
use Fcntl qw( :flock );
use HTML::Entities;
use URI;
use Image::Size;
use LWP::Simple;
use Apache::Constants qw( :response :common );
#==============================================================================
#
# Global variables
#
#==============================================================================
use vars qw( $VERSION %OPTIONS $WINDOW_PADDING $MAX_MARGIN );
$WINDOW_PADDING = 20;
$MAX_MARGIN = 20;
$VERSION = '0.007';
#==============================================================================
#
# Private methods
#
#==============================================================================
#------------------------------------------------------------------------------
#
# _mkpath( $dir ) - utility method to untaint a pathname and create it, or die.
#
#------------------------------------------------------------------------------
sub _mkpath
{
my $self = shift;
my $dir = shift;
return if -d $dir;
$dir = _untaint( $dir );
mkpath( $dir ) or die "Can't create $dir\n";
}
#------------------------------------------------------------------------------
#
# _untaint( @paths ) - untaint path strings by checking against a regex that
# allows alphanumerics, underscores and periods.
#
#------------------------------------------------------------------------------
sub _untaint
{
for ( @_ )
{
$_ = $1 and next if m{([a-zA-Z0-9_./]*)};
die "untaint $_ failed\n";
}
return wantarray ? @_ : $_[0];
}
#------------------------------------------------------------------------------
#
# _untaint_and_open( $file, $mode ) - untaint the filename %file, and then
# open in mode $mode, returning an IO::File object.
#
#------------------------------------------------------------------------------
sub _untaint_and_open
{
my $file = shift;
my $mode = shift;
my $untaintend_file = _untaint( $file );
die "'$untaintend_file' ne '$file'\n" if $untaintend_file ne $file;
return IO::File->new( $untaintend_file, $mode );
}
#------------------------------------------------------------------------------
#
# _freeze( $file, $data ) - "freeze" data in reference $data in filename $file,
# using Data::Dumper
#
#------------------------------------------------------------------------------
sub _freeze
{
my $self = shift;
my $file = shift;
my $data = shift;
my $fh = _untaint_and_open( $file, 'w' )
or die "can't write to $file: $!\n"
;
flock( $fh, LOCK_EX );
print $fh Dumper $data;
$fh->close();
}
#------------------------------------------------------------------------------
#
# _thaw( $file ) - "thaw" data in Data::Dumper file $file, and return as a
# reference.
#
#------------------------------------------------------------------------------
sub _thaw
{
my $self = shift;
my $file = shift;
return {} unless -e $file;
die "can't read $file\n" unless -r $file;
my $fh = _untaint_and_open( $file, 'r' ) or die "can't open $file: $!\n";
flock( $fh, LOCK_SH );
$file = _untaint( $file );
my $data = do $file;
$fh->close();
return $data;
}
#------------------------------------------------------------------------------
#
# _read_data - read advert data from the Data::Dumper datafile
#
#------------------------------------------------------------------------------
sub _read_data
{
my $self = shift;
$self->{ads} = $self->_thaw( $self->{datafile} );
}
#------------------------------------------------------------------------------
#
# _write_data - write advert data to the Data::Dumper datafile
#
#------------------------------------------------------------------------------
sub _write_data
{
my $self = shift;
warn "write data for $self->{campaign_path} to $self->{datafile}\n";
eval {
$self->_freeze( $self->{datafile}, $self->{ads} );
};
warn "error writing data $self->{datafile}: $@\n" if $@;
}
#------------------------------------------------------------------------------
#
# _status - method called to return appropriate status. If called in mod_perl
# mode, returns $self->{APACHE_STATUS}; otherwise, returns $@. In both cases
# also logs errors, if they havce occurred.
#
#------------------------------------------------------------------------------
sub _status
{
my $self = shift;
if ( $ENV{MOD_PERL} )
{
die "No APACHE_REQUEST\n" unless $self->{APACHE_REQUEST};
if ( $@ )
{
die "$self->{APACHE_REQUEST} can't log_error\n"
unless $self->{APACHE_REQUEST}->can( 'log_error' )
;
$self->{APACHE_REQUEST}->log_error( $@ );
$self->{APACHE_STATUS} = SERVER_ERROR;
}
return $self->{APACHE_STATUS};
}
else
{
warn "$@\n" if $@;
return $@;
}
}
#------------------------------------------------------------------------------
#
# _redirect( $url ) - redirect to URL, using whichever method is appropriate to
# whether in mod_perl mode or not. The caller may then exit.
#
#------------------------------------------------------------------------------
sub _redirect
{
my $self = shift;
my $url = shift;
warn "Redirect to $url\n";
if ( $ENV{MOD_PERL} )
{
die "No APACHE_REQUEST\n" unless $self->{APACHE_REQUEST};
die "$self->{APACHE_REQUEST} can't print\n"
unless $self->{APACHE_REQUEST}->can( 'header_out' )
;
$self->{APACHE_REQUEST}->header_out( Location => $url );
$self->{APACHE_STATUS} = REDIRECT;
}
else
{
print "Cache-Control: max-age=0\nLocation: $url\n\n";
}
}
#------------------------------------------------------------------------------
#
# _print( @stuff ) - print stuff to the browser, using print method appropriate
# to whether in mod_perl mode or not.
#
#------------------------------------------------------------------------------
sub _print
{
my $self = shift;
if ( $ENV{MOD_PERL} )
{
die "No APACHE_REQUEST\n" unless $self->{APACHE_REQUEST};
$self->{APACHE_REQUEST}->print( @_ );
}
else
{
print @_;
}
}
#------------------------------------------------------------------------------
#
# _http_header( $content_type ) - set the content type in the HTTP header to
# $content_type, and add a cache control header to prevent caching. Also, print
# out header if not in mod_perl mode.
#
#------------------------------------------------------------------------------
sub _http_header
{
my $self = shift;
my $type = shift;
if ( $ENV{MOD_PERL} )
{
warn "set content-type to $type\n";
die "No APACHE_REQUEST\n" unless $self->{APACHE_REQUEST};
$self->{APACHE_REQUEST}->content_type( $type );
$self->{APACHE_REQUEST}->header_out( 'Cache-Control' => 'max-age=0' );
$self->{APACHE_REQUEST}->send_http_header;
}
else
{
print "Content-Type: $type\nCache-Control: max-age=0\n\n";
}
}
#------------------------------------------------------------------------------
#
# _subcampaigns( $campaign ) - returns all the sub-campagins of $campaign.
#
#------------------------------------------------------------------------------
sub _subcampaigns
{
my $self = shift;
my $campaign_path = shift;
my $dir = "$self->{DATA_DIR}/$campaign_path";
my %dir;
tie %dir, 'IO::Dir', $dir;
my @c = grep !/^\./, grep { -e "$dir/$_/admanager.pl" } %dir;
return @c;
}
#------------------------------------------------------------------------------
#
# _subcampaigns - returns all the current campaigns.
#
#------------------------------------------------------------------------------
sub _campaigns
{
my $self = shift;
my %dir;
my $dir = $self->{DATA_DIR};
tie %dir, 'IO::Dir', $dir;
my @c = grep !/^\./, grep { -e "$dir/$_/admanager.pl" } %dir;
return @c;
}
#------------------------------------------------------------------------------
#
# _log_entry( $type ) - log an entry of type $type - either ct (clickthough) or
# img (page impression) - for advert number $n. Also increment appropriate
# entry in the stats Data::Dumper file.
#
#------------------------------------------------------------------------------
sub _log_entry
{
my $self = shift;
my $type = shift;
my $n = shift;
return unless $self->{ads}{log_usage};
my $fh = _untaint_and_open( $self->{logfile}, 'a' )
or die "can't write to $self->{logfile}\n"
;
my $url = $self->{ads}{$n}{$type};
my $datestr = scalar( localtime );
my $document_name = $ENV{DOCUMENT_NAME} || '-';
my $remote_host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR} || '-';
print $fh "$datestr:$n:$type:$url:$document_name:$remote_host\n";
warn "Log $n $type in $self->{statsfile}\n";
my $stats = $self->_thaw( $self->{statsfile} );
$stats->{$n}{$type}++;
$self->_freeze( $self->{statsfile}, $stats );
}
#------------------------------------------------------------------------------
#
# _get_ad_keys - get a list of the current ad numbers.
#
#------------------------------------------------------------------------------
sub _get_ad_keys
{
my $self = shift;
return grep /^\d+$/, keys %{$self->{ads}};
}
#------------------------------------------------------------------------------
#
# _get_rand_element( @array ) - return a randomly selected element of the array
# @array.
#
#------------------------------------------------------------------------------
sub _get_rand_element
{
my $self = shift;
return $_[rand(@_)];
}
#------------------------------------------------------------------------------
#
# _get_ads - return a list of all the current ad hashrefs.
#
#------------------------------------------------------------------------------
sub _get_ads
{
my $self = shift;
return map { $self->{ads}{$_} } $self->_get_ad_keys();
}
#------------------------------------------------------------------------------
#
# _get_width - get the total width for the current campaign.
#
#------------------------------------------------------------------------------
sub _get_width
{
my $self = shift;
my $width = 0;
for my $ad ( $self->_get_ads() )
{
my $w = $ad->{size}[0];
$width = $w > $width ? $w : $width;
}
$width *= $self->{ads}{nads} if $self->{ads}{nads};
if ( $self->{ads}{margin} )
{
my $nmargins = $self->{ads}{nads} - 1;
$width += $nmargins * $self->{ads}{margin};
}
return $width;
}
#------------------------------------------------------------------------------
#
# _get_height - get the total height for the current campaign
#
#------------------------------------------------------------------------------
sub _get_height
{
my $self = shift;
my $height = 0;
for my $ad ( $self->_get_ads() )
{
my $h = $ad->{size}[1];
$height = $h > $height ? $h : $height;
}
return $height;
}
#------------------------------------------------------------------------------
#
# _get_max_adno - get the max. ad no.
#
#------------------------------------------------------------------------------
sub _get_max_adno
{
my $self = shift;
my $max = -1;
for my $ad ( $self->_get_ads() )
{
$max = $ad->{n} if $ad->{n} > $max;
}
return $max;
}
#------------------------------------------------------------------------------
#
# _get_nads - get the current number of ads.
#
#------------------------------------------------------------------------------
sub _get_nads
{
my $self = shift;
return scalar $self->_get_ads();
}
#------------------------------------------------------------------------------
#
# _html_header - returns an HTML header for the admin interface pages.
#
#------------------------------------------------------------------------------
sub _html_header
{
my $self = shift;
return
"Admanager" .
( $self->{campaign_path} ? " ($self->{campaign_path})" : '' ) .
""
;
}
#------------------------------------------------------------------------------
#
# _campaigns_as_html - returns HTML formated list of current campaigns, as well
# as a form to submit a new campaign creation request.
#
#------------------------------------------------------------------------------
sub _campaigns_as_html
{
my $self = shift;
my $other = $self->{campaign_path} ? "Other " : '';
my $html = <${other}Campaigns
EOF
my @campaigns = $self->_campaigns;
$html .=
join " | ",
map { $_ eq $self->{campaign} ? $_ : <$_
EOF
@campaigns
;
$html .= <
New campaign:
EOF
return $html;
}
#------------------------------------------------------------------------------
#
# _subcampaigns_as_html - returns HTML formated list of the sub-campaigns of the
# current campaign, as well # as a form to submit a new sub-campaign creation
# request.
#
#------------------------------------------------------------------------------
sub _subcampaigns_as_html
{
my $self = shift;
my $campaign_path = $self->{campaign_path};
return '' unless $campaign_path;
my $other = '';
if ( $self->{subcampaign} ) # this is a sub-campaign ...
{
$other = "Other ";
}
my @subcampaigns = $self->_subcampaigns( $self->{campaign} );
my $html = <${other}Sub-Campaigns of $self->{campaign}
EOF
$html .=
join " | ",
map { $_ eq $campaign_path ? $_ : <$_
EOF
map { "$self->{campaign}/$_" }
@subcampaigns
;
$html .= <
New sub-campaign:
$self->{campaign}/
EOF
return $html;
}
#------------------------------------------------------------------------------
#
# _current_campaign_actions_as_html - returns an HTML formatted list of actions
# for the current campaign: ad a new advert, view the usage log, test the
# campaign, and delete the campaign.
#
#------------------------------------------------------------------------------
sub _current_campaign_actions_as_html
{
my $self = shift;
return '' unless $self->{campaign_path};
my $add_ad = $self->_get_max_adno() + 1;
my $html = <Current Campaign ($self->{campaign_path})
Add a new advert
EOF
my $nads = $self->_get_nads();
if ( $nads )
{
my $h = $self->_get_height( $self->{campaign_path} );
my $w = $self->_get_width( $self->{campaign_path} );
my $width = $w + $WINDOW_PADDING;
my $height = $h + $WINDOW_PADDING;
$html .= <View the usage log
| Test the campaign
EOF
}
my $url =
"$self->{ADMANAGER_ADMIN_URL}?" .
'action=delete_campaign&' .
"c=$self->{campaign_path}"
;
$html .= <Delete the campaign
EOF
return $html;
}
#------------------------------------------------------------------------------
#
# _ad_info_as_html( $ad ) - returns an HTML formatted table with the info
# for the $ad'th advert in the currrent campaign.
#
#------------------------------------------------------------------------------
sub _ad_info_as_html
{
my $self = shift;
my $ad = shift;
my $n = $ad->{n};
my $nw = $ad->{nw} ? 'Yes' : 'No';
my $alt = $ad->{alt} || '';
my $size = join( 'x', @{$ad->{size}}[0,1] );
my $ad_as_html = $self->_ad_as_html( $n, 1 );
return <
$ad_as_html
Image:
$ad->{img}
Image Size:
$size
Clickthough:
$ad->{ct}
Alt. text:
$alt
Open in new window:
$nw
EOF
}
#------------------------------------------------------------------------------
#
# _ad_stats_as_html( $ad ) - returns an HTML formatted table with the usage
# stats for the $ad'th advert in the currrent campaign.
#
#------------------------------------------------------------------------------
sub _ad_stats_as_html
{
my $self = shift;
my $ad = shift;
my $n = $ad->{n};
my $stats = $self->_thaw( $self->{statsfile} );
$stats->{$n}{ct} ||= 0;
$stats->{$n}{img} ||= 0;
return <
No. Impressions:
$stats->{$n}{img}
No. Clickthroughs:
$stats->{$n}{ct}
EOF
}
#------------------------------------------------------------------------------
#
# _all_ads_info_as_html - returns an HTML formatted form with the advert
# attributes relevant to all adverts in the current campaign: the number of
# adverts to display, and the image margins, as well as forms to change these
# values.
#
#------------------------------------------------------------------------------
sub _all_ads_info_as_html
{
my $self = shift;
my $log_usage = $self->{ads}{log_usage};
my $html = sprintf( <
EOF
return $form;
}
#------------------------------------------------------------------------------
#
# _logfile - return the contents of the logfile for the current campaign.
#
#------------------------------------------------------------------------------
sub _logfile
{
my $self = shift;
my $fh = _untaint_and_open( $self->{logfile}, 'r' );
return unless $fh;
return join '', <$fh>;
}
#------------------------------------------------------------------------------
#
# _page_impression - log a page impression for th advert number specified by
# the img value in the formdata hash, and redirect to the corresponding image.
#
#------------------------------------------------------------------------------
sub _page_impression
{
my $self = shift;
my $n = $self->{formdata}{img};
die "No img parameter set\n" unless defined $n;
my $url = $self->{ads}{$n}{img}
or die "No image $n im campaign $self->{campaign_path}\n"
;
$self->_log_entry( 'img', $n );
warn "page impression $url\n";
$self->_redirect( $url );
}
#------------------------------------------------------------------------------
#
# _ad_as_html( $n ) - return advert no. $n as HTML.
#
#------------------------------------------------------------------------------
sub _ad_as_html
{
my $self = shift;
my $n = shift;
my $first = shift;
my $campaign_path = $self->{campaign_path};
my $ad = $self->{ads}{$n};
my $img = $ad->{img};
my $size = $ad->{size};
my $size_str = $size ? "width=\"$size->[0]\" height=\"$size->[1]\"" : '';
my $url = $self->{ADMANAGER_URL};
$url =~ s/$self->{path_info}$// if $self->{path_info};
$url .= "/$campaign_path";
my $alt = $ad->{alt} || "$campaign_path advert no. $n";
my $rand = $$ . time . rand(1000);
my $img_url =
$self->{REDIRECT_PAGE_IMPRESSIONS} ?
"$url?img=$n&rand=$rand" : "$img?$rand"
;
my $margin = $self->{ads}{margin} || 0;
my $user_agent = $ENV{HTTP_USER_AGENT};
if (
$user_agent !~ /compatible/ and
$user_agent =~ m!Mozilla/4!
)
{
$margin += $size->[0];
}
my $style = $first ? '' : "style=\"margin-left:${margin}px;\"";
if ( $ad->{ct} )
{
my $target = $ad->{nw} ? 'target="_blank"' : '';
my $ct_url = "$url?ct=$n&rand=$rand";
return <
EOF
}
else
{
return
join( '',
qq{},
qq{}
);
}
}
#------------------------------------------------------------------------------
#
# _random_ad_as_html - return a random ad as HTML, logging a 'img' entry for
# that ad.
#
#------------------------------------------------------------------------------
sub _random_ad_as_html
{
my $self = shift;
my @html;
my $nads = $self->{ads}{nads} || 1;
my @ad_keys = $self->_get_ad_keys();
warn "Displaying $nads random ads\n";
for ( 1 .. $nads )
{
my $n = $self->_get_rand_element( @ad_keys );
warn "$n chosen from @ad_keys\n";
@ad_keys = grep { $_ ne $n } @ad_keys;
$self->_log_entry( 'img', $n )
unless $self->{REDIRECT_PAGE_IMPRESSIONS}
;
push( @html, $self->_ad_as_html( $n, $_ == 1 ) );
warn "Display ad $n as HTML\n";
}
my $margin = $self->{ads}{margin} || 0;
my $spacer = '';
return join( $spacer, @html );
}
#------------------------------------------------------------------------------
#
# _clickthrough - log a 'ct' entry for the ad corresponding to the ct value in
# the formdata hash, and redirect to the corresponding URL.
#
#------------------------------------------------------------------------------
sub _clickthrough
{
my $self = shift;
my $n = $self->{formdata}{ct};
my $url = $self->{ads}{$n}{ct};
warn "Click though to $url\n";
$self->_log_entry( 'ct', $n );
$self->_redirect( $url );
return $url;
}
#------------------------------------------------------------------------------
#
# _setup_dirs - sets up and creates the directories required.
#
#------------------------------------------------------------------------------
sub _setup_dirs
{
my $self = shift;
unless ( $self->{INSTALL_DIR} )
{
my $root = $ENV{HOME} || TempDir->new || die "Can't work out a root\n";
$self->{INSTALL_DIR} = "$root/.admanager";
}
$self->_mkpath( $self->{INSTALL_DIR} );
$self->{STATS_DIR} ||= "$self->{INSTALL_DIR}/stats";
$self->_mkpath( $self->{STATS_DIR} );
$self->{LOG_DIR} ||= "$self->{INSTALL_DIR}/log";
$self->_mkpath( $self->{LOG_DIR} );
$self->{DATA_DIR} ||= "$self->{INSTALL_DIR}/data";
$self->_mkpath( $self->{DATA_DIR} );
$self->{ERR_DIR} ||= "$self->{INSTALL_DIR}/err";
$self->_mkpath( $self->{ERR_DIR} );
}
sub _log_errors
{
my $self = shift;
my $errfile =
"$self->{ERR_DIR}/$self->{INTERFACE}." .
( $ENV{MOD_PERL} ? 'mod_perl' : 'cgi' ) .
".err"
;
$errfile = _untaint( $errfile );
open( STDERR, ">>$errfile" ) or die "Can't write to $errfile: $!\n";
warn "$0: ", scalar( localtime ), "\n";
warn "PATH_INFO: $ENV{PATH_INFO}\n" if $ENV{PATH_INFO};
warn
"FORM DATA:\n",
map { "\t$_ = $self->{formdata}{$_}\n" }
keys %{$self->{formdata}}
;
warn "Running under mod_perl\n" if exists $ENV{MOD_PERL};
}
#------------------------------------------------------------------------------
#
# _munge_filename_from_path( $path ) - create a munged filename from a path, by
# replacing '/' characters with '_'s. NOTE: the replacement character needs to
# not clash with the allowable filename characters from _untaint.
#
#------------------------------------------------------------------------------
sub _munge_filename_from_path
{
my $self = shift;
my $path = shift;
$path =~ s{/}{_}g;
return $path;
}
#------------------------------------------------------------------------------
#
# _setup_files - sets up files required for current campaign
#
#------------------------------------------------------------------------------
sub _setup_files
{
my $self = shift;
my $campaign_path = shift;
return unless $campaign_path;
my $cn = $self->_munge_filename_from_path( $campaign_path );
$self->{statsfile} = "$self->{STATS_DIR}/$cn.pl";
$self->{logfile} = "$self->{LOG_DIR}/$cn.log";
my $datadir = "$self->{DATA_DIR}/$campaign_path";
$self->_mkpath( $datadir );
$self->{datafile} = "$datadir/admanager.pl";
}
#------------------------------------------------------------------------------
#
# _setup_admin_urls - create admin url values
#
#------------------------------------------------------------------------------
sub _setup_admin_urls
{
my $self = shift;
my ( $proto ) = $ENV{SERVER_PROTOCOL} =~ /^(\w+)/;
$self->{abs_admin_url} =
lc( $proto ) . '://' .
$ENV{SERVER_NAME} .
# ( $ENV{SERVER_PORT} != 80 ? ":$ENV{SERVER_PORT}" : '' ) .
$ENV{SCRIPT_NAME} .
( $ENV{PATH_INFO} ? $ENV{PATH_INFO} : '' ) .
( $ENV{QUERY_STRING} ? "?$ENV{QUERY_STRING}" : '' )
;
$self->{ADMANAGER_ADMIN_URL} ||= $ENV{SCRIPT_NAME};
$self->{admin_url} = "$self->{ADMANAGER_ADMIN_URL}/$self->{campaign_path}";
}
#------------------------------------------------------------------------------
#
# _setup_campaign_path - setup campaign_path. This is maintained using the
# $PATH_INFO.
#
#------------------------------------------------------------------------------
sub _setup_campaign_path
{
my $self = shift;
$self->{path_info} = $ENV{PATH_INFO};
my $campaign_path = $self->{path_info} || '';
$campaign_path =~ s{/}{};
$campaign_path ||= '';
( $self->{campaign}, $self->{subcampaign} ) = split( '/', $campaign_path );
return $self->{campaign_path} = $campaign_path;
}
#------------------------------------------------------------------------------
#
# _check_options - check the options passed to the constructor against the
# %OPTIONS hash. Values of this hash are either 'undef' (optional) or contain a
# quoted regex to test the option value against.
#
#------------------------------------------------------------------------------
sub _check_options
{
my $self = shift;
for ( keys %$self )
{
die "Unknown option $_\n" unless exists $OPTIONS{$_};
}
for my $opt ( grep { defined $OPTIONS{$_} } keys %OPTIONS )
{
my $whatami = lc( ref( $OPTIONS{$opt} ) );
die "No $opt option specified\n" unless exists $self->{$opt};
if ( $whatami eq 'regexp' )
{
die "$opt option should be $OPTIONS{$opt}\n"
unless $self->{$opt} =~ $OPTIONS{$opt}
;
}
elsif ( $whatami eq 'code' )
{
my $ret = $OPTIONS{$opt}->( $self, $opt );
die $ret if defined $ret;
}
}
}
#==============================================================================
=head1 CONSTRUCTOR
The constructor for the module takes a number of options (see
L) as a hash of arguments.
=cut
#==============================================================================
sub new
{
my $class = shift;
my %args = @_;
my $self = bless \%args, $class;
$self->{APACHE_REQUEST} ||= undef;
$self->_check_options();
$self->{formdata} = CGI_Lite->new->parse_form_data;
$self->_setup_dirs();
$self->_log_errors();
my $campaign_path = $self->_setup_campaign_path();
$self->_setup_admin_urls();
$self->_setup_files( $campaign_path );
$self->_read_data() if $campaign_path;
return $self;
}
#==============================================================================
=head1 Apache::Registry HANDLER
WWW::Admanger offers a "handler" method that can be used in a mod_perl ennabled
Apache web server (see L). Various options can be
specified using PerlSetVar directives (see L). These
options correspond to the L options, and are listed
in the L section. The handler method simple creates a new
WWW::AdManager object using these options, and calls the L