package Context; ###### Context for servicing a request # $Id: Context.pm,v 1.19 2003/05/09 19:32:14 steve Exp $ # This file is part of Combo and is licensed under the GPL and LGPL. # Copyright 2001-2002 by the authors. See README and LICENSE files # for more details or see http://penguinsong.org/. ### Context provides the environment for servicing a request. # Context provides a complete environment for servicing a particular # request: it points to the site configuration (Site::Config), request # (CGI), user profile (Site::User), directory (Site::Resource), MIME # type (Type::*), and version control (Site::Version) objects. # In addition, Context provides the global name-value table for # entity substitution, as performed by, e.g., AMP::Process. In this # role, it serves as the top-level namespace in the process stack. # Usage: my $path = $ENV{PATH_INFO}; # the default # my $trans = $ENV{TRANS_PATH}; # my $c = new Context($path, $trans); # my $q = $c->getCGI; # It is not necessary to pass $path if the path to the resource # really is PATH_INFO. In some cases, however, it may be necessary # to modify the path (e.g. to expand "//" constructs) or to use a # different environment variable (e.g. REDIRECT_URL in dir-index). # The environment variable PATH_INFO contains the URL path that follows the # CGI's name, so that's the URL path to the file to be processed. Normally # PATH_TRANSLATED holds the corresponding file path, but if there's a // in # the path it's going to be wrong unless mod_rewrite has taken care of it. # Normally mod_rewrite expands //'s before Apache looks them up, then # unexpands them before the user sees them. So we're ok unless the URL # starting with /Ccgi/ was entered by hand. That's a no-no, but eventually # we'll have to worry about it. ## Note: references: # Perl uses reference counts for storage allocation; this means that # it's important to avoid circular references, since these will cause # memory leaks if we're running under mod_perl. In addition, most # classes in Site:: don't depend on the request, and so may be # persistent: it's important that they have no references back to the # Context. They may, however, refer to one another. In particular, # _anything_ can refer to Site::Config, which is both global and # constant. Site::Version _does_ depend on the Context; it has # both user and path information in its state, but little else. BEGIN { use Exporter(); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $cvsid = '$Id: Context.pm,v 1.19 2003/05/09 19:32:14 steve Exp $ '; $cvsid =~ /,v ([0-9]+.[0-9]+)/; $VERSION=$1; @ISA = qw(Exporter); @EXPORT = ( ); } use AMP::Tagset; use Site::Config; use Site::Util; use Site::User; ########################################################################## ### ### Constructors: ### ### Context->new($path, $trans_path) # Obtain a new Context for the given path. # $path is the URL path from the request, usually from PATH_INFO # $trans_path is the translated path, usually from PATH_TRANSLATED # sub new { my ($class, $path, $trans_path) = (@_); my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); # force leading zero's on 2-byte fields $sec = sprintf("%02d", $sec); $min = sprintf("%02d", $min); $hour = sprintf("%02d", $hour); $mday = sprintf("%02d", $mday); $mon = sprintf("%02d", $mon + 1); # mon has range 0..11 $yday = sprintf("%03d", $yday); my $yy = sprintf("%02d", $year % 100); $path = '/' unless $path; my $config = Site::Config->new($ENV{'SERVER_NAME'}); my $parent_path = parentPath("$path"); my $basename = Site::Util::baseName("$path"); my $trimmed_path = "$parent_path/$basename"; my $real_path = $config->makeRealPath("$path"); $trans_path = ($trans_path) ? $config->makeRealPath("$trans_path") : $config->translatePath($real_path); my $mgr = $config->getTypeManager; # Initialize instance variables and entities my $self = { '_Config'=> $config, '_CGI' => new CGI, '_type_manager' => $mgr, # parent path, basename 'parent.path' => $parent_path, 'page.filename' => Site::Util::fileName("$path"), 'page.basename' => $basename, # Path and path-related entities 'path' => $path, # === legacy 'page.path' => $path, 'page.real.path' => $real_path, 'page.path.links' => linkifyPath($trimmed_path), '_page.file' => $trans_path, # per-directory entities: snagged in initialize # Date-related entities 'time.ss' => $sec, 'time.mm' => $min, 'time.hh' => $hour, 'date.dd' => $mday, 'date.mm' => $mon, 'date.yyyy' => $year+1900, # year is actualYear-1900 'date.yy' => $yy, 'date.yday' => $yday, }; $self = bless $self, $class; $self-> initialize($path); return $self; } ########################################################################## ### ### Access Functions: ### ### $c->get(name, [default]) -> value # get a value. Typically these are also useable as entities, but names # starting with non-alpha-numeric characters will not be recognized as # entities and so can be used internally with impunity. # # Anything not found in the Context will be searched for in the # site configuration. # sub get { my ($self, $n, $def) = @_; my $v = $self->{$n}; $v = $self->getConfig->get($n) unless defined $v; if (!defined $v) { my $u = $self->getUser(); $v = $u->get($n) if $u; } return (defined $v)? $v : $def; } ### $c->qget($param, $name, $default) # Return $param from the query if defined. # Otherwise, return $c->get($name, $default) # sub qget { my ($self, $p, $n, $def) = @_; my $q = $self->getCGI; my $v = (ref $q)? $q->param($p) : undef; return (defined $v)? $v : $self->get($n, $def); } sub param { my ($self, $p) = @_; my $q = $self->getCGI; return (ref $q)? $q->param($p) : undef; } ### $c->getEntityNames() -> (name, ...) # Return the list of entity names defined in the context. # Names beginning with "_" are omitted because they're not # legitimate entity names; these include both non-public # information and non-HTML objects (e.g. _Config and _Version). # sub getEntityNames { my ($self) = @_; my @keys = (); foreach $k (keys(%$self)) { push(@keys, $k) unless $k =~ /^[._]/; } my $cfg = $self->getConfig; foreach $k (keys(%$cfg)) { push(@keys, $k) unless $k =~ /^[._]/ || defined $self->{$k} ; } return @keys; } ### $c->getConfig() # Return the Site::Config instance. sub getConfig { my ($self) = @_; return $self->{'_Config'}; } ### $c->getCGI() # Return the CGI instance sub getCGI { my ($self) = @_; return $self->{'_CGI'}; } ### $c->getVersion() # Return the Site::Version (version control) instance. sub getVersion { my ($self) = @_; return $self->{'_Version'}; } ### $c->getUser() # Return the Site::User (user profile) instance that applies to this # request. sub getUser { my ($self) = @_; return $self->{'_User'}; } ### $c->getResource() # Return the Site::Resource instance that applies to this request. # The Resource is used to operate on files in the directory indicated # by $path. sub getResource { my ($self) = @_; return $self->{'_Resource'}; } ########################################################################## ### ### Indirect Access Functions: ### These could all be done using $self->get, but we can save a step ### because we already know that they're in the Config. ### ### $c->getSiteDir sub getSiteDir { my $self = shift; return $self->getConfig->getSiteDir; } ### $c->getDocRoot sub getDocRoot { my $self = shift; return $self->getConfig->getDocRoot; } ### $c->getServerName sub getServerName { my $self = shift; return $self->getConfig->getServerName; } ### $c->getTemplateDir # Return the directory that contains tag templates sub getTemplateDir { my $self = shift; return $self->getConfig->getTemplateDir; } ### $c->getBaseURL # Return a URL that points to the server base sub getBaseURL { my $self = shift; return $self->getConfig->getBaseURL; } ### === The following are more complex and somewhat dubious. # === they probably need to go somewhere else ### $c->getType($path) sub getType { my ($self, $path) = @_; my $mgr = $self->{'_type_manager'}; $path = $self->getRealPath unless $path; my $type = $mgr->getType($self, $path); $self->{'type.handler'} = ref $type; return $type; } ### $c->getTagset sub getTagset { my $self = shift; # === needs to be cached in $self, obviously === if (!$tagset) { $tagset = AMP::Tagset->new($self, $self->getTemplateDir); } return $tagset; } ### $c->getAreaMap sub getAreaMap { my $self = shift; my @urls = split(" ", $self->get("area.urls")); my @text = split(" ", $self->get("area.text")); my $hash = {}; for (my $i = @text; --$i >= 0; ) { $hash->{$urls[$i]} = $text[$i]; } return $hash; } # === these probably belong in Resource ### $c->getPath sub getPath { my $self = shift; return $self->get('page.path'); } ### $c->getRealPath sub getRealPath { my $self = shift; return $self->get('page.real.path'); } sub getTransPath { my $self = shift; return $self->get('_page.file'); } ### $c->isDir sub isDir { my $self = shift; return -d $root . $self->getRealPath; } ### $c->getRealDirPath sub getRealDirPath { my $self = shift; my $path = $self->get('page.real.path'); my $root = $self->getDocRoot; return (-d "$root$path")? $path : parentPath($path); } ### $c->getFileName sub getFileName { my $self = shift; return $self->get('page.filename'); } sub getTitle { my $self = shift; return $self->get('page.title'); } sub setTitle { my ($self, $v) = @_; $self->{'page.title'} = $v; } ########################################################################## ### ### Initialization: ### ### $c->initialize($path) # Initialize a Context. # Note that _CGI and _Config are already set at this point, as are # all of the date and page things. Most of what we do here has to do # with loading the per-directory and per-user information. # sub initialize { my ($self, $path) = (@_); my $root = $self->getDocRoot; # set up areas # === set up Version, Resource, User # === go through directories starting at root; load config files # === probably belongs in Resource, actually. my $p = (-d "$root$path")? $path : parentPath($path); my @p = split(/\//, $p); $p = $root; my $dirCF = $self->get("dir.cf", "..cf"); # Look for and load per-directory config files, starting at docRoot # Note the "dir." prefix foreach $d (@p, ".") { next unless $d; # print STDERR "looking for $p/$dirCF\n"; if (-f "$p/$dirCF") { readConfig($self, "$p/$dirCF", "dir."); # print STDERR "loading $p/$dirCF\n"; } $p .= "/$d"; } # Obtain the User corresponding to this request: $self->{'_User'} = Site::User->fromContext($self); # Obtain theme information: my $theme = $self->get("dir.theme", "theme"); # probably need to allow for themes in a subdirectory if ($theme !~ /^theme/) { $theme = "theme.$theme"; } if ($theme !~ /.cf$/) { $theme .= ".cf"; } $theme = $self->getSiteDir . '/' . $theme; $self->{'theme.file.path'} = $theme; $theme = $self->getDocRoot . $theme; readConfig($self, $theme, "theme."); } ########################################################################## ### ### Site-specific Utilities: ### ### $c->start_html($title) -> "..." # Replacement for CGI::start_html, using current theme # sub start_html { my ($c, $title) = @_; my $q = $c->getCGI; # === actually need to build arg list in an array $q->start_html(-title => $title, -bgcolor => $c->get("theme.page.bg", "#ffffff"), -text => $c->get("theme.page.text", "#000000"), -link => $c->get("theme.page.link", "#0000ff"), -vlink => $c->get("theme.page.vlink", "#0000cc"), -alink => $c->get("theme.page.alink", "#ff0000"), ); } ### $c->printenv # Print environment, entities, and other information for debugging # sub printenv { my $c = shift; print "\n"; print "\n"; print " \n"; print "\n"; print " \n"; print "\n"; print " \n"; print "" . "\n"; print "" . "\n"; print "" . "\n"; print "\n"; foreach my $e (sort keys(%ENV)) { my $v = $ENV{$e}; if ($e eq 'HTTP_ACCEPT') { $v =~ s/\,/\, /g; } print "" . "\n"; } print "\n"; foreach my $e (sort $c->getEntityNames) { print "" . "\n"; } print "
Debugging Information: 


Internal variables:
site directory " . $c->getSiteDir . "
current working dir " . `pwd` . "
site config file " . $Site::Config::siteCF . "
Environment
$e " . $v . "
Entities
$e " . $c->get($e) . "
\n"; } ### Print directory for debugging sub printdir { my ($c, $dir) = @_; my $root = $c->getDocRoot; my $realPath = $c->getRealPath; $dir = $c->getTransPath unless $dir; opendir(DIR, $dir); my @files = readdir(DIR); closedir DIR; print "\n"; foreach my $f (sort @files) { next if $f =~ /^\.|CVS|~$/&& $f ne '..'; $f .= '/' if -d "$dir/$f"; my $d = (-d "$dir/$f")? "?dir=1&env=1&debug=1" : "?env=1"; my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksiz, $blks) = stat "$dir/$f"; print "\n"; print " \n"; print " \n"; print " \n"; } print "
$size[raw][pre]$f
\n"; } ### $c->webPage($title, $text) # Put out a simple web page sub webPage { my ($self, $title, $text) = @_; print " $title

$title

$text
$ENV{'SERVER_SIGNATURE'} "; } ########################################################################### 'Package loaded';