From 7b7fac78b3d11609f987cb3100937fb750640437 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 16 May 2011 14:39:13 +0100 Subject: [PATCH] Initial version of webperl added. --- Block.pm | 383 ++++++++++++ ConfigMicro.pm | 301 +++++++++ Doxyfile | 1511 +++++++++++++++++++++++++++++++++++++++++++++ HTMLValidator.pm | 207 +++++++ Logging.pm | 119 ++++ Modules.pm | 280 +++++++++ SessionHandler.pm | 581 +++++++++++++++++ Template.pm | 609 ++++++++++++++++++ Utils.pm | 201 ++++++ phpBB3.pm | 1062 +++++++++++++++++++++++++++++++ 10 files changed, 5254 insertions(+) create mode 100644 Block.pm create mode 100644 ConfigMicro.pm create mode 100644 Doxyfile create mode 100644 HTMLValidator.pm create mode 100644 Logging.pm create mode 100644 Modules.pm create mode 100644 SessionHandler.pm create mode 100644 Template.pm create mode 100644 Utils.pm create mode 100644 phpBB3.pm diff --git a/Block.pm b/Block.pm new file mode 100644 index 0000000..e43dc62 --- /dev/null +++ b/Block.pm @@ -0,0 +1,383 @@ +## @file +# This file contains the implementation of the base Block class. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 1.2 +# @date 12 May 2009 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class Block +# The Block class serves as the base class for all plugin block modules in +# the system. It provides the basic constructor required to initialise a +# plugin properly, stub functions for the two key content generation +# functions that plugins can override to provide meaningful output, and a +# number of general utility functions usefil for all blocks. +# +# Block subclasses may provide two different 'views': an inline block fragment +# that is intended to be embedded within a page generated by another block +# (for example, sidebar menu contents); or the complete contents of a page +# which may be generated solely by the Block subclass, or by the subclass +# loading other Blocks and using their inline block fragments to construct the +# overall page content. +package Block; + +use HTMLValidator; +use Utils qw(is_defined_numeric); + +use HTML::Entities; +use strict; + +# Globals within this and available to subclasses +use vars qw{$VERSION $errstr}; + +BEGIN { + $VERSION = 1.2; + $errstr = ''; +} + +# ============================================================================ +# Constructor + +## @cmethod $ new($id, $args, $cgi, $dbh, $phpbb, $template, $settings, $session, $module) +# Create a new Block object and store the provided objects in the new object's data. id and +# args are optional, all the remaining arguments must be provided. +# +# @param id The module id set for the block module's entry in the database. +# @param args Any arguments passed to the plugin at runtime, usually pulled from the database. +# @param cgi A reference to the script's CGI object. +# @param dbh A database handle to talk to the database through. +# @param phpbb A phpbb3 handle object used to perform operations on a phpbb3 database. +# @param template A template engine module object to load templates through. +# @param settings The global configuration hashref. +# @param session A reference to the current session object +# @param module The module handler object, used to load other blocks on demand. +# @return A newly created Block object. +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $id = shift; + my $args = shift; + my $cgi = shift or return set_error("Block::new(): No cgi object specified"); + my $dbh = shift or return set_error("Block::new(): No datbase object specified"); + my $phpbb = shift or return set_error("Block::new(): No phpBB3 object specified"); + my $template = shift or return set_error("Block::new(): No template object specified"); + my $settings = shift or return set_error("Block::new(): No settings object specified"); + my $session = shift or return set_error("Block::new(): No session object specified"); + my $module = shift or return set_error("Block::new(): No module object specified"); + + my $self = { + modid => $id, + args => $args, + cgi => $cgi, + dbh => $dbh, + phpbb => $phpbb, + template => $template, + settings => $settings, + session => $session, + module => $module, + }; + + # Work out which block we're being invoked with + $self -> {"block"} = is_defined_numeric($self -> {"cgi"}, "block") || $self -> {"settings"} -> {"config"} -> {"default_block"}; + + return bless $self, $class; +} + + +# =========================================================================== +# Parameter validation support functions + +## @method @ validate_string($param, $settings) +# Determine whether the string in the namedcgi parameter is set, clean it +# up, and apply various tests specified in the settings. The settings are +# stored in a hash, the recognised contents are as below, and all are optional +# unless noted otherwise: +# +# required - If true, the string must have been given a value in the form. +# default - The default string to use if the form field is empty. This is not +# used if required is set! +# nicename - The required 'human readable' name of the field to show in errors. +# minlen - The minimum length of the string. +# maxlen - The maximum length of the string. +# chartest - A string containing a regular expression to apply to the string. If this +# matches the field the validation fails! +# chardesc - Must be provided if chartest is provided. A description of why matching +# chartest fails the validation. +# formattest - A string containing a regular expression to apply to the string. If the +# string does not match the regexp, validation fails. +# formatdesc - Must be provided if formattest is provided. A description of why not +# matching formattest fails the validation. +# +# @param param The name of the cgi parameter to check/ +# @param settings A reference to a hash of settings to control the validation +# done to the string. +# @return An array of two values: the first contains the text in the parameter, or +# as much of it as can be salvaged, while the second contains an error message +# or undef if the text passes all checks. +sub validate_string { + my $self = shift; + my $param = shift; + my $settings = shift; + + # Grab the parameter value, fall back on the default if it hasn't been set. + my $text = $self -> {"cgi"} -> param($param); + + # Handle the situation where the parameter has not been provided at all + if(!defined($text) || $text eq '' || (!$text && $settings -> {"nonzero"})) { + # If the parameter is required, return empty and an error + if($settings -> {"required"}) { + return ("", $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_NOTSET", "", {"***field***" => $settings -> {"nicename"}})); + # Otherwise fall back on the default. + } else { + $text = $settings -> {"default"} || ""; + } + } + + # If there's a test regexp provided, apply it + my $chartest = $settings -> {"chartest"}; + return ($text, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_BADCHARS", "", {"***field***" => $settings -> {"nicename"}, + "***desc***" => $settings -> {"chardesc"}})) + if($chartest && $text =~ /$chartest/); + + # Is there a format check provided, if so apply it + my $formattest = $settings -> {"formattest"}; + return ($text, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_BADFORMAT", "", {"***field***" => $settings -> {"nicename"}, + "***desc***" => $settings -> {"formatdesc"}})) + if($formattest && $text !~ /$formattest/); + + # Convert all characters in the string to safe versions + $text = encode_entities($text); + + # Now trim spaces + $text =~ s/^\s+//; + $text =~ s/\s+$//; + + # Get here and we have /something/ for the parameter. If the maximum length + # is specified, does the string fit inside it? If not, return as much of the + # string as is allowed, and an error + return (substr($text, 0, $settings -> {"maxlen"}), $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_TOOLONG", "", {"***field***" => $settings -> {"nicename"}, + "***maxlen***" => $settings -> {"maxlen"}})) + if($settings -> {"maxlen"} && length($text) > $settings -> {"maxlen"}); + + # Is the string too short (we only need to check if it's required or has content) ? If so, store it and return an error. + return ($text, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_TOOSHORT", "", {"***field***" => $settings -> {"nicename"}, + "***minlen***" => $settings -> {"minlen"}})) + if(($settings -> {"required"} || length($text)) && $settings -> {"minlen"} && length($text) < $settings -> {"minlen"}); + + # Get here and all the tests have been passed or skipped + return ($text, undef); +} + + +## @method @ validate_options($param, $settings) +# Determine whether the value provided for the specified parameter is valid. This will +# either look for the value specified in an array, or in a database table, depending +# on the value provided for source in the settings hash. Valid contents for settings are: +# +# required - If true, the option can not be "". +# default - A default value to return if the option is '' or not present, and not required. +# source - The source of the options. If this is a reference to an array, the +# value specified for the parameter is checked agains the array. If this +# if a string, the option is checked against the table named in the string. +# where - The 'WHERE' clause to add to database queries. Required when source is a +# string, otherwise it is ignored. +# nicename - Required, human-readable version of the parameter name. +# +# @param param The name of the cgi parameter to check. +# @param settings A reference to a hash of settings to control the validation +# done to the parameter. +# @return An array of two values: the first contains the value in the parameter, or +# as much of it as can be salvaged, while the second contains an error message +# or undef if the parameter passes all checks. +sub validate_options { + my $self = shift; + my $param = shift; + my $settings = shift; + + my $value = $self -> {"cgi"} -> param($param); + + # Bomb if the value is not set and it is required. + return ("", $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_NOTSET", "", {"***field***" => $settings -> {"nicename"}})) + if($settings -> {"required"} && (!defined($value) || $value eq '')); + + # If the value not specified and not required, we can just return immediately + return ($settings -> {"default"}, undef) if(!defined($value) || $value eq ""); + + # Determine how we will check it. If the source is an array reference, we do an array check + if(ref($settings -> {"source"}) eq "ARRAY") { + foreach my $check (@{$settings -> {"source"}}) { + return ($value, undef) if($check eq $value); + } + + # If the source is not a reference, we assue it is the table name to check + } elsif(not ref($settings -> {"source"})) { + my $checkh = $self -> {"dbh"} -> prepare("SELECT * + FROM ".$settings -> {"source"}." + ".$settings -> {"where"}); + # Check for the value in the table... + $checkh -> execute($value) + or return (undef, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_DBERR", "", {"***field***" => $settings -> {"nicename"}, + "***dberr***" => $self -> {"dbh"} -> errstr})); + my $checkr = $checkh -> fetchrow_arrayref(); + + # If we have a match, the value is valid + return ($value, undef) if($checkr); + } + + # Get here and validation has failed. We can't rely on the value at all, so return + # nothing for it, and an error + return (undef, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_BADOPT", "", {"***field***" => $settings -> {"nicename"}})); +} + + +## @method @ validate_htmlarea($param, $settings) +# Attempt to validate the contents of a html area. This is an excessively complicated +# job and is, ultimately, never going to be 100% secure - the code needs to be put through +# filters and validation by a html validator before we can be be even remotely sure it +# is vaguely safe. Even then, there is a small possibility that a malicious user can +# carefully craft something to bypass the checks. +# +# @param param The name of the textarea to check. +# @param settings A reference to a hasn containing settings to control the validation. +sub validate_htmlarea { + my $self = shift; + my $param = shift; + my $settings = shift; + + # first we need the textarea contents... + my $text = $self -> {"cgi"} -> param($param); + # If the text area is empty, deal with the whole default/required malarky + if(!defined($text)) { + # If the parameter is required, return empty and an error + if($settings -> {"required"}) { + return ("", $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_NOTSET", "", {"***field***" => $settings -> {"nicename"}})); + # Otherwise fall back on the default. + } else { + $text = $settings -> {"default"} || ""; + } + } + # Don't bother doing anything if the text is empty at this point + return ("", undef) if(!$text || length($text) == 0); + + # Now we get to the actual validation and stuff. Begin by scrubbing any tags + # and other crap we don't want out completely. As far as I can tell, this should + # always result in some kind... + $text = scrub_html($text); + + # ... but check, just in case + return ("", $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_SCRUBFAIL", "", {"***field***" => $settings -> {"nicename"}})) + if(!defined($text)); + + # Explicitly nuke any CDATA sections that might have got through, as they have + # no bloody business being there at all + $text =~ s{}{}gio; + + # Load the text into the testing hardness now, so it appears like a valid chunk of html + # to tidy and the validator... + my $xhtml = $self -> {"template"} -> load_template("validator_harness.tem", {"***body***" => $text}); + + # Throw the xhtml through tidy to make sure it is actually xhtml + # This will result in undef if tidy failed catastrophically... + my $tidied = tidy_html($xhtml); + return ("", , $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_TIDYFAIL", "", {"***field***" => $settings -> {"nicename"}})) + if(!$tidied); + + # Now we can go ahead and check with the validator to see whether the tidied + # code is valid xhtml + my $valid = check_xhtml($tidied); + + # Strip out the harness + $tidied =~ s{^.*\s*(.*)\s*\s*\s*$}{$1}is; + + # Zero indicates that there were no errors - the html is valid + if($valid == 0) { + return ($tidied, undef); + + # If the return from check_xhtml is one or more digits, it is an error count + } elsif($valid =~ /^\d+$/) { + return ($tidied, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_CHKERRS", "", {"***field***" => $settings -> {"nicename"}, + "***error***" => $valid})); + + # Otherwise it should be a failure message + } else { + return ($tidied, $self -> {"template"} -> replace_langvar("BLOCK_VALIDATE_CHKFAIL", "", {"***field***" => $settings -> {"nicename"}, + "***error***" => $valid})); + } +} + + +# ============================================================================ +# Display functions + +## @method @ build_error_box($message) +# Generate the contents of a system error message to send back to the user. +# This wraps the template message_box() function as a means to make error +# messages easier to show. +# +# @param message The message explaining the problem that triggered the error. +# @return An array of two values. The first is the page title, the second is +# the text of the error box. +sub build_error_box { + my $self = shift; + my $message = shift; + + my $title = $self -> {"template"} -> replace_langvar("BLOCK_ERROR_TITLE"); + $message = $self -> {"template"} -> message_box($self -> {"template"} -> replace_langvar("BLOCK_ERROR_TITLE"), + 'info', + $self -> {"template"} -> replace_langvar("BLOCK_ERROR_SUMMARY"), + $self -> {"template"} -> replace_langvar("BLOCK_ERROR_TEXT", {"***error***" => $message})); + return ($title, $message); +} + + +## @method $ block_display() +# Produce the string containing this block's 'block fragment' if it has one. By default, +# this will return a string containing an error message. If block fragment content is +# needed, this must be overridden in the subclass. +# +# @return The string containing this block's content fragment. +sub block_display { + my $self = shift; + + return "

".$self -> {"template"} -> replace_langvar("BLOCK_BLOCK_DISPLAY")."

"; +} + + +## @method $ page_display() +# Produce the string containing this blocks full page content, if it provides one. +# By default, this will return a string containing an error message, override it to +# generate pages in subclasses. +# +# @return The string containing this block's page content. +sub page_display { + my $self = shift; + + return "

".$self -> {"template"} -> replace_langvar("BLOCK_PAGE_DISPLAY")."

"; +} + + +# ============================================================================ +# Internal + +## @fn $ set_error($errstr) +# Set a class error string. This will always return undef, and can be used to +# set an error message and return undef at the same time. +# +# @param errstr The error to store in the global errstr variable. +# @return undef, always. +sub set_error { $errstr = shift; return undef; } + +1; diff --git a/ConfigMicro.pm b/ConfigMicro.pm new file mode 100644 index 0000000..b8f57db --- /dev/null +++ b/ConfigMicro.pm @@ -0,0 +1,301 @@ +## @file +# This file contains the implementation of a compact, simple congifuration +# loading and saving class. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 2.0 +# @date 22 Feb 2009 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class ConfigMicro +# A simple configuration class intended to allow ini files to be read and saved. This +# provides the means to read the contents of an ini file into a hash and saving such a +# hash out as an ini file. +# +# @par Example +# +# Given an ini file of the form +#
[sectionA]
+# keyA = valueA
+# keyB = valueB
+#
+# [sectionB]
+# keyA = valueC
+# keyC = valueD
+# this will load the file into a hash of the form +#
{ "sectionA" => { "keyA" => "valueA",
+#                   "keyB" => "valueB" },
+#   "sectionB" => { "keyA" => "valueC",
+#                   "keyC" => "valueD" } 
+# }
+package ConfigMicro; + +require 5.005; +use DBI; +use strict; + +our ($VERSION, $errstr); + +BEGIN { + $VERSION = 2.0; + $errstr = ''; +} + +# ============================================================================ +# Constructor and basic file-based config functions + +## @cmethod $ new(%args) +# Create a new ConfigMicro object. This creates an object that provides functions +# for loading and saving configurations, and pulling config data from a database. +# Meaningful options for this are: +# filename - The name of the configuration file to read initial settings from. This +# is optional, and if not specified you will get an empty object back. +# You may also pass in one or more initial configuration settings. +# @param args A hash of key, value pairs to initialise the object with. +# @return A new ConfigMicro object, or undef if a problem occured. +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $filename = shift; + + # Object constructors don't get much more minimal than this... + my $self = { "__privdata" => { "modified" => 0 }, + @_, + }; + + my $obj = bless $self, $class; + + # Return here if we have no filename to load from + return $obj if(!$filename); + + # Otherwise, try to read the file + return $obj if($obj -> read($filename)); + + # Get here and things have gone wahoonie-shaped + return undef; +} + + +## @method $ read($filename) +# Read a configuration file into a hash. This will process the file identified by +# the specified filename, attempting to load its contents into a hash. Any key/value +# pairs that occur before a [section] header are added to the '_' section. +# +# @param filename The name of the file to read the config data from. +# @return True if the configuration has been loaded sucessfully, false otherwise. +sub read { + my $self = shift; + my $filename = shift or return set_error("No file name provided"); + + # The current section, default it to '_' in case there is no leading [section] + my $section = "_"; + + # TODO: should this return the whole name? Possibly a security issue here + return set_error("Failed to open '$filename': $!") + if(!open(CFILE, "< $filename")); + + my $counter = 0; + while(my $line = ) { + chomp($line); + ++$counter; + + # Skip comments and empty lines + next if($line =~ /^\s*(\#|;|\z)/); + + # Handle section headers, allows for comments after the ], but [foo #comment] will + # treat the section name as 'foo #comment'! + if($line =~ /^\s*\[([^\]]+)\]/) { + $section = $1; + + # Attribues with quoted values. value can contain anything other than " + } elsif($line =~ /^\s*([\w\-]+)\s*=\s*\"([^\"]+)\"/ ) { + $self -> {$section} -> {$1} = $2; + + # Handle attributes without quoted values - # or ; at any point will mark comments + } elsif($line =~ /^\s*([\w\-]+)\s*=\s*([^\#;]+)/ ) { + $self -> {$section} -> {$1} = $2; + + # bad input... + } else { + close(CFILE); + return set_error("Syntax error on line $counter: '$line'"); + } + } + + close(CFILE); + + return 1; +} + + +## @method $ text_config(@skip) +# Create a text version of the configuration stored in this ConfigMicro object. +# This creates a string representation of the configuration suitable for writing to +# an ini file or otherwise printing. +# +# @param skip If you specify one or more section names, the sections will not be +# added to the string generated by this function. +# @return A string representation of this ConfigMicro's config settings. +sub text_config { + my $self = shift; + my @skip = @_; + my $result; + + my ($key, $skey); + foreach $key (sort(keys(%$self))) { + # Skip the internal settings + next if($key eq "__privdata"); + + # If we have any sections to skip, and the key is one of the ones to skip... skip! + next if(scalar(@skip) && grep($key, @skip)); + + # Otherwise, we want to start a new section. Entries in the '_' section go out + # with no section header. + $result .= "[$key]\n" if($key ne "_"); + + # write out all the key/value pairs in the current section + foreach $skey (sort(keys(%{$self -> {$key}}))) { + $result .= $skey." = \"".$self -> {$key} -> {$skey}."\"\n"; + } + $result .= "\n"; + } + return $result; +} + + +## @method $ write($filename, @skip) +# Save a configuration hash to a file. Writes the contents of the configuration to +# a file, formatting the output as an ini-style file. +# +# @param filename The file to save the configuration to. +# @param skip An optional list of names of sections to ignore when writing the +# configuration. +# @return true if the configuration was saved successfully, false if a problem +# occurred. +sub write { + my $self = shift; + my $filename = shift or return set_error("No file name provided"); + my @skip = @_; + + # Do nothing if the config has not been modified. + return 0 if(!$self -> {"__privdata"} -> {"modified"}); + + return set_error("Failed to save '$filename': $!") + if(!open(CFILE, "> $filename")); + + print CFILE $self -> text_config(@skip); + + close(CFILE); + + return 1; +} + + +# ============================================================================ +# Database config functions + +## @method $ load_db_config($dbh, $table, $name, $value) +# Load settings from a database table. This will pull name/value pairs from the +# named database table, storing them in a hashref called 'config'. +# +# @param dbh A database handle to issue queries through. +# @param table The name of the table containing key/value pairs. +# @param name Optional name of the table column for the key name, defaults to 'name' +# @param value Optional name of the table column for the value, defaults to 'value' +# @return true if the configuration table was read into the config object, false +# if a problem occurred. +sub load_db_config { + my $self = shift; + my $dbh = shift or return set_error("No database handle provided"); + my $table = shift or return set_error("Settings table name not provided"); + my $name = shift || "name"; + my $value = shift || "value"; + + my $confh = $dbh -> prepare("SELECT * FROM $table"); + $confh -> execute() + or return set_error("Unable to execute SELECT query - ".$dbh -> errstr); + + my $row; + while($row = $confh -> fetchrow_hashref()) { + $self -> {"config"} -> {$row -> {$name}} = $row -> {$value}; + } + + return 1; +} + + +## @method $ save_db_config($dbh, $table, $name, $value) +# Save the database configuration back into the database table. This will write the +# key/value pairs inside the 'config' configuration hash back into the database. +# +# @param dbh A database handle to issue queries through. +# @param table The name of the table containing key/value pairs. +# @param name Optional name of the table column for the key name, defaults to 'name' +# @param value Optional name of the table column for the value, defaults to 'value' +# @return true if the configuration table was updated from the config object, false +# if a problem occurred. +sub save_db_config { + my $self = shift; + my $dbh = shift or return set_error("No database handle provided"); + my $table = shift or return set_error("Settings table name not provided"); + my $name = shift || "name"; + my $value = shift || "value"; + + my $confh = $dbh -> prepare("UPDATE $table SET `$value` = ? WHERE `$name` = ?"); + + foreach my $key (keys(%{$self -> {"config"}})) { + $confh -> execute($self -> {"config"} -> {$key}, $key) + or return set_error("Unable to execute UPDATE query - ".$dbh -> errstr); + } + + return 1; +} + + +## @method $ set_db_config($dbh, $table, $name, $value, $namecol, $valcol) +# Set the named configuration variable to the specified calye. +# +# @param dbh A database handle to issue queries through. +# @param table The name of the table containing key/value pairs. +# @param name The name of the variable to update. +# @param value The value to change the variable to. +# @param namecol Optional name of the table column for the key name, defaults to 'name' +# @param valuecol Optional name of the table column for the value, defaults to 'value' +# @return true if the config variable was changed, false otherwise. +sub set_db_config { + my $self = shift; + my $dbh = shift or return set_error("No database handle provided"); + my $table = shift or return set_error("Settings table name not provided"); + my $name = shift; + my $value = shift; + my $namecol = shift || "name"; + my $valuecol = shift || "value"; + + my $confh = $dbh -> prepare("UPDATE $table SET `$valuecol` = ? WHERE `$namecol` = ?"); + $confh -> execute($value, $name) + or return set_error("Unable to execute UPDATE query - ".$dbh -> errstr); + + $self -> {"config"} -> {$name} = $value; + + return 1; +} + +# ============================================================================ +# Error functions + +sub set_error { $errstr = shift; return undef; } + +1; diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..dffa7b7 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1511 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = PerlWebModules + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = . + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = "copy=\par Copyright:\n© " + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.pm \ + *.pl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = doxygenfilter + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = NO + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = NO + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/HTMLValidator.pm b/HTMLValidator.pm new file mode 100644 index 0000000..df0dbd1 --- /dev/null +++ b/HTMLValidator.pm @@ -0,0 +1,207 @@ +## @file +# HTML validation and checking functions. This file contains functions to +# support the cleaning and checking of html using a combination of +# HTML::Scrubber to do first-stage cleaning, HTML::Tidy to clear up the +# content as needed, and the W3C validator via the WebService::Validator::HTML::W3C +# to ensure that the xhtml generated by HTML::Tidy is valid. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 1.0 +# @date 22 May 09 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +package HTMLValidator; + +require Exporter; +use Encode; +use HTML::Scrubber; +use HTML::Tidy; +use WebService::Validator::HTML::W3C; +use strict; + +our @ISA = qw(Exporter); +our @EXPORT = qw(scrub_html tidy_html check_xhtml); + +our $VERSION = 1.0; + + +# ============================================================================= +# HTML::Scrubber related code + +# List of tags we are going to let through, lifted from the security +# discussion on http://wiki.moxiecode.com/index.php/TinyMCE:Security +# Several tags removed to make xhtml conformance easier and to remove +# deprecated and eyestabbery. +my @allow = ("a", "b", "blockquote", "br", "caption", "col", "colgroup", "comment", + "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "img", "li", "ol", "p", + "pre", "small", "span", "strong", "sub", "sup", "table", "tbody", "td", + "tfoot", "th", "thead", "tr", "tt", "ul"); + +# Explicit rules for allowed tags, required to provide per-tag tweaks to the filter. +my @rules = ( + img => { + src => qr{^(?:http|https)://}i, + alt => 1, + style => 1, + width => 1, + height => 1, + '*' => 0, + }, + a => { + href => qr{^(?:http|https)://}i, + name => 1, + '*' => 0, + }, + table => { + cellspacing => 1, + cellpadding => 1, + style => 1, + class => 1, + '*' => 0, + }, + td => { + colspan => 1, + rowspan => 1, + style => 1, + '*' => 0, + }, + blockquote => { + cite => qr{^(?:http|https)://}i, + style => 1, + '*' => 0, + }, + span => { + class => 1, + style => 1, + title => 1, + '*' => 0, + }, + div => { + class => 1, + style => 1, + title => 1, + '*' => 0, + }, +); + +# Default ruleset applied when no explicit rule is found for a tag. +my @default = ( + 0 => # default rule, deny all tags + { + 'href' => qr{^(?:http|https)://[-\w]+(?:\.[-\w]+)/}i, # Force basic URL forms + 'src' => qr{^(?:http|https)://[-\w]+(?:\.[-\w]+)/}i, # Force basic URL forms + 'style' => qr{^((?!expr|java|script|eval|\r|\n|\t).)*$}i, # kill godawful insane dynamic css shit (who the fuck thought this would be a good idea?) + 'name' => 1, + '*' => 0, # default rule, deny all attributes + } +); + + +## @fn $ scrub_html($html) +# Remove dangerous/unwanted elements and attributes from a html document. This will +# use HTML::Scrubber to remove the elements and attributes from the specified html +# that could be used maliciously. There is still the potential for a clever attacker +# to craft a page that bypasses this, but that exists pretty much regardless once +# html input is permitted... +# +# @param html The string containing the html to clean up +# @return A string containing the scrubbed html. +sub scrub_html { + my $html = shift; + + # Die immediately if there's a nul character in the string, that should never, ever be there. + die_log("HACK ATTEMPT", "Hack attempt detected. Sod off.") + if($html =~ /\0/); + + # First, a new scrubber + my $scrubber = HTML::Scrubber -> new(allow => \@allow, + rules => \@rules, + default => \@default, + comment => 0, + process => 0); + + # fix problems with the parser setup. This is hacky and nasty, + # but from CPAN's bug tracker, this appears to have been present for + # the past 3 years at least. + if(exists $scrubber -> {_p}) { + # Allow for ,
,

, and so on + $scrubber -> {_p} -> empty_element_tags(1); + + # Make sure that HTML::Parser doesn't scream about utf-8 from the form + $scrubber -> {_p} -> utf8_mode(1) + if($scrubber -> {_p} -> can('utf8_mode')); + } + + # And throw the html through the scrubber + return $scrubber -> scrub($html); +} + + +# ============================================================================== +# HTML::Tidy related code + +## @fn $ tidy_html($html, $options) +# Pass a chunk of html through htmltidy. This should produce well-formed xhtml +# that can be passed on to the validator to check. +# +# @param html The string containing html to tidy. +# @param options A reference to a hash containing options to pass to HTML::Tidy. +# @return The html generated by htmltidy. +sub tidy_html { + my $html = shift; + my $options = shift; + + # Create a new tidy object + my $tidy = HTML::Tidy->new($options); + return $tidy -> clean($html); +} + + +# ============================================================================== +# WebService::Validator::HTML::W3C related code + +## @fn @ check_xhtml($xhtml, $options) +# Check that the xhtml is valid by passing it through the W3C validator service. +# If this is unable to contact the validation service, it will return the reason, +# otherwise the number of errors will be returned (0 indicates that the xhtml +# passed validation with no errors) +# +# @param xhtml The xhtml to validate with the W3C validator +# @param options A hash containing options to pass to the validator module. +# Currently supports 'timeout' and 'uri'. +# @return The number of errors during validation (0 = valid), or a string +# from the validator module explaining why the validation bombed. +sub check_xhtml { + my $xhtml = shift; + my $options = shift; + + # Create a validator + my $validator = WebService::Validator::HTML::W3C -> new(http_timeout => $options -> {"timeout"}, + validator_uri => $options -> {"uri"}); + # Throw the xhtml at the validator + if($validator -> validate_markup(Encode::encode_utf8($xhtml))) { + # return 0 to indicate it is valid + return 0 + if($validator -> is_valid()); + + # otherwise, the xhtml is not valid, so return the error count + return $validator -> num_errors(); + } + + # Get here and the validation request fell over, return the 'oh shit' result... + return $validator -> validator_error(); +} + +1; diff --git a/Logging.pm b/Logging.pm new file mode 100644 index 0000000..311a1b1 --- /dev/null +++ b/Logging.pm @@ -0,0 +1,119 @@ +## @file +# System-wide logging functions. The functions in this file provide logging and +# printing facilities for the whole system. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 1.0 +# @date 2 March 2009 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class +# System-wide logging functions. The functions in this file provide logging and +# printing facilities for the whole system. +# +package Logging; +require Exporter; +use strict; + +our @ISA = qw(Exporter); +our @EXPORT = qw(warn_log die_log); +our @EXPORT_OK = qw(start_log end_log); + +our $VERSION = 1.0; + + +my $logfile; # If defined, this is handle to the file that entries a written to +my $logtime; # The time that the log file was opened + + +## @fn void warn_log($ip, $message) +# Write a warning message to STDERR and to a log file if it is opened. Warnings +# are prepended with the process ID and an optional IP address, and entries +# written to the log file are timestamped. +# +# @param ip The IP address to log with the message. Defaults to 'unknown' +# @param message The message to write to the log +sub warn_log { + my $ip = shift || "unknown"; + my $message = shift; + + print $logfile scalar(localtime)," [$$:$ip]: $message\n" + if($logfile); + + warn "[$$:$ip]: $message\n"; +} + + +## @fn void die_log($ip, $message) +# Write an error message a log file if it is opened, and then die. Errors +# are prepended with the process ID and an optional IP address, and entries +# written to the log file are timestamped. +# +# @param ip The IP address to log with the message. Defaults to 'unknown' +# @param message The message to write to the log +sub die_log { + my $ip = shift || "unknown"; + my $message = shift; + + print $logfile scalar(localtime)," [$$:$ip]: $message\n" + if($logfile); + + die "[$$:$ip]: $message\n"; +} + + +## @fn void start_log($filename, $progname) +# Start logging warnings and errors to a file. If logging is already enabled, +# this will close the currently open log before opening the new one. The log +# file is appended to rather than truncated. +# +# @param filename The name of the file to log to. +# @param progname A optional program name to show in the log. Defaults to $0 +sub start_log { + my $filename = shift; + my $progname = shift || $0; + + # Close the logfile if it has been opened already + end_log($progname) if($logfile); + + # Open in append mode + open($logfile, ">> $filename") + or die "Unable to open log file $filename: $!"; + + my $tm = scalar localtime; + print $logfile "\n----------= Starting $progname [pid: $$] at $tm =----------\n"; + $logtime = time(); +} + + +## @fn void end_log($progname) +# Stop logging warnings and errors to a file. This will write an indicator +# that logging is stopping to the file and then close it. +# +# @param progname A optional program name to show in the log. Defaults to $0 +sub end_log { + my $progname = shift || $0; + + if($logfile) { + my $tm = scalar localtime; + my $elapsed = time() - $logtime; + + print $logfile "----------= Completed $progname [pid: $$] at $tm, execution time $elapsed seconds =----------\n"; + close($logfile); + } +} + +1; diff --git a/Modules.pm b/Modules.pm new file mode 100644 index 0000000..868947e --- /dev/null +++ b/Modules.pm @@ -0,0 +1,280 @@ +## @file +# This file contains the implementation of the Module loading class. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 0.1 +# @date 14 Feb 2009 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class +# A class to simplify runtime loading of plugin modules. This class provides +# methods to allow the various block plugin modules to be loaded on demand +# during script execution. +package Modules; + +#use lib qw(/home/webperl); # modules needed for utils, blocks needed for plugins +use DBI; +use Logging qw(die_log); +use strict; + +use vars qw{$VERSION $errstr}; + +BEGIN { + $VERSION = 0.1; + $errstr = ''; +} + +# ============================================================================== +# Creation + +## @cmethod $ new(%args) +# Create a new Modules object. This will create an object that provides functions +# to create block modules on the fly. +# cgi - The CGI object to access parameters and cookies through. +# dbh - The database handle to use for queries. +# phpbb - An object through which to talk to a phpBB3 database +# settings - The system settings object +# template - The system template object +# session - The session object +# blockdir - The directory containing blocks. +# @param args A hash of key, value pairs to initialise the object with. +# @return A new Modules object, or undef if a problem occured. +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $self = { + cgi => undef, + dbh => undef, + phpbb => undef, + settings => undef, + template => undef, + session => undef, + blockdir => undef, + @_, + }; + + # If we get here and still don't have a database connection, we need to fall over + return set_error("No database connection available.") if(!$self -> {"dbh"}); + + # Check we also have a cgi object to play with + return set_error("No CGI object available.") if(!$self -> {"cgi"}); + + # Aaand settings.... + return set_error("No settings object available.") if(!$self -> {"settings"}); + + # ... finally, template + return set_error("No settings template available.") if(!$self -> {"template"}); + + # update @INC if needed + unshift(@INC, $self -> {"blockdir"}) if($self -> {"blockdir"}); + + my $obj = bless $self, $class; + + # Set the template object's module reference + $obj -> {"template"} -> set_module_obj($obj); + + # and we're done + return $obj +} + + +# ============================================================================ +# Loading support +# + +## @method $ new_module_byblockid($blockid) +# Given a block id, create an instance of the module that implements that block. This +# will look in the blocks table to obtain the module id that implements the block, and +# then create an instance of that module. +# +# @param blockid The id of the block to generate an instance for. +# @return An instance of the module, or undef on error. +sub new_module_byblockid { + my $self = shift; + my $blockid = shift; + + my $sth = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"blocks"}." + WHERE id = ?"); + $sth -> execute($blockid) or + die_log($self -> {"cgi"} -> remote_host(), "new_module_byblockid: Unable to execute query: ". $self -> {"dbh"} -> errstr); + + my $modrow = $sth -> fetchrow_hashref(); + + # If we have a block row, return an instance of the module for it + return $self -> new_module_byid($modrow -> {"module_id"}, + $modrow -> {"args"}) + if($modrow); + + return undef; +} + + +## @method $ new_module_byname($modname, $argument) +# Load a module based on its name, checking against the database to obtain the real +# module name, and whether the module is active. Returns a new object of the module +# on success, undef if the module is disabled or if there's a problem. +# +# @param modname The name of the module to load. +# @param argument Argument to pass to the module constructor. +# @return An instance of the module, or undef on error. +sub new_module_byname { + my $self = shift; + my $modname = shift; + my $argument = shift; + + return $self -> _new_module_internal("WHERE name LIKE ?", + $modname, + $argument); +} + + +## @method $ new_module_byid($modid, $argument) +# Load a module based on its id, checking against the database to obtain the real +# module name, and whether the module is active. Returns a new object of the module +# on success, undef if the module is disabled or if there's a problem. +# +# @param modid The id of the module to load. +# @param argument Argument to pass to the module constructor. +# @return An instance of the module, or undef on error. +sub new_module_byid { + my $self = shift; + my $modid = shift; + my $argument = shift; + + return $self -> _new_module_internal("WHERE module_id = ?", + $modid, + $argument); +} + + +## @method $ _new_module_internal($where, $argument, $modargs) +# Create an instance of a module. This uses the where and argument parameters as part of a database +# query to determine what the actual name of the module is, and then load and instantiate it. +# +# @param where The WHERE clause to add to the module select query. +# @param argument The argument for the select query. +# @param modargs The argument to pass to the module. +# @return A new object, or undef if a problem occured or the module is disabled. +sub _new_module_internal { + my $self = shift; + my $where = shift; + my $argument = shift; + my $modarg = shift; + + my $modh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"modules"}." $where"); + $modh -> execute($argument) + or die_log($self -> {"cgi"} -> remote_host(), "Unable to execute module resolve query: ".$self -> {"dbh"} -> errstr); + + my $modrow = $modh -> fetchrow_hashref(); + + # bomb if the mofule record is not found, or the module is inactive + return set_error("Unable to locate module $argument using $where, or module is inactive.") if(!$modrow || !$modrow -> {"active"}); + + my $name = $modrow -> {"perl_module"}; + no strict "refs"; # must disable strict references to allow named module loading. + require "$name.pm"; + my $modobj = $name -> new($modrow -> {"id"}, + $modarg, + $self -> {"cgi"}, + $self -> {"dbh"}, + $self -> {"phpbb"}, + $self -> {"template"}, + $self -> {"settings"}, + $self -> {"session"}, + $self) + or set_error("Unable to load module: ".$Block::errstr); + use strict; + + return $modobj; +} + + +## @method $ build_sidebar($side, $page) +# Generate the contents of a sidebar. This will load the modules listed as appearing on the specified +# side of the page, and call on their block_display() functions, concatenating the results into one +# large string. +# +# @param side The side to generate the blocks for. Must be 'left' or 'right'. +# @param page An optional page ID (corresponding to the module currently shown on in the core of the +# page) that can be used to filter the blocks shown in the sidebar. +# @return A string containing the sidebar HTML, or undef if there was an error. +sub build_sidebar { + my $self = shift; + my $side = shift; + my $page = shift || 0; + + # Bomb with an error is side is not valid + return set_error("build_sidebar called with an illegal value for side: $side") + unless($side eq "left" || $side eq "right"); + + # If a page is specified, we need to filter on it, or zero. OTherwise we'll be filtering on just 0 + my $filter = $page ? "(filter = ? OR filter = 0)" : "filter = ?"; + + # Pull out blocks that match the specified side type, filtering them so that only 'always show' or blocks + # that show on the current page are shown. + my $sth = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"blocks"}." + WHERE TYPE = ? AND $filter + ORDER BY position"); + $sth -> execute($side, $page) or + die_log($self -> {"cgi"} -> remote_host(), "build_sidebar: Unable to execute query: ". $self -> {"dbh"} -> errstr); + + my $result = ""; + while(my $row = $sth -> fetchrow_hashref()) { + # Load the block module + my $headerobj = $self -> new_module_byid($row -> {"module_id"}, + $row -> {"args"}); + + # If we have an object, ask it do generate its block display. + $result .= $headerobj -> block_display() if($headerobj); + } + + return $result; +} + + +# ============================================================================ +# Block identification support + +## @method $ get_block_id($blockname) +# Obtain the id of a block given its unique name. This will, hopefully, allow templates +# to include references to modules without hard-coding IDs (ironically, hard coding +# the module names seems so much less nasty... weird...) +# +# @param blockname The name of the block to obtain the id for. +# @return The block id, or undef if the name can not be located. +sub get_block_id { + my $self = shift; + my $blockname = shift; + + my $blockh = $self -> {"dbh"} -> prepare("SELECT id FROM ".$self -> {"settings"} -> {"database"} -> {"blocks"}." + WHERE name LIKE ?"); + $blockh -> execute($blockname) + or die_log($self -> {"cgi"} -> remote_host(), "get_block_id: Unable to execute query: ". $self -> {"dbh"} -> errstr); + + # Do we have the block? + my $blockr = $blockh -> fetchrow_arrayref(); + + # If we have the block id return it, otherwise return undef. + return $blockr -> [0] if($blockr); + return undef; +} + +# ============================================================================ +# Error functions + +sub set_error { $errstr = shift; return undef; } + +1; diff --git a/SessionHandler.pm b/SessionHandler.pm new file mode 100644 index 0000000..d6579a3 --- /dev/null +++ b/SessionHandler.pm @@ -0,0 +1,581 @@ +## @file +# This file contains the implementation of the perl phpBB3 interaction class. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 0.5 +# @date 23 Sep 2009 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class +# The SessionHandler class provides cookie-based session facilities for +# maintaining user state over http transactions. This code depends on +# integration with a phpBB3 database: a number of custom tables are needed +# (see config docs), but user handling is tied to phpBB3 user tables, and +# a number of joins between custom tables and phpBB3 ones require the two +# to share database space. This code provides session verification, and +# takes some steps towards ensuring security against cookie hijacking, but +# as with any cookie based auth system there is the potential for security +# issues. +# +# This code is heavily based around the session code used by phpBB3, with +# features removed or added to fit the different requirements of the ORB, +# starforge site, etc +package SessionHandler; + +require 5.005; +use strict; + +# Standard module imports +use DBI; +use Digest::MD5 qw(md5_hex); +use Compress::Bzip2; +use MIME::Base64; + +use Data::Dumper; + +# Custom module imports +use phpBB3; +use Logging qw(die_log); + +# Globals... +use vars qw{$VERSION $errstr}; + +BEGIN { + $VERSION = 0.2; + $errstr = ''; +} + +# ============================================================================ +# Constructor + +## @cmethod SessionHandler new(@args) +# Create a new SessionHandler object, and start session handling. +# +# @param args A hash of key, value pairs to initialise the object with. +# @return A reference to a new SessionHandler object. +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $self = { + cgi => undef, + dbh => undef, + phpbb => undef, + template => undef, + settings => undef, + @_, + }; + + # Ensure that we have objects that we need + return set_error("cgi object not set") unless($self -> {"cgi"}); + return set_error("dbh object not set") unless($self -> {"dbh"}); + return set_error("phpbb object not set") unless($self -> {"phpbb"}); + return set_error("template object not set") unless($self -> {"template"}); + return set_error("settings object not set") unless($self -> {"settings"}); + + # Bless class so we canuse it properly + $self = bless $self, $class; + + # cleanup if necessary + return undef + unless($self -> session_cleanup()); + + # Determine the name of the cookie, and fall over if it isn't available for some reason + my $cookiebase = $self -> {"settings"} -> {"config"} -> {"cookie_name"} + or return set_error("Unable to determine sessioncookie name"); + + # Now try to obtain a session id - start by looking at the cookies + $self -> {"sessid"} = $self -> {"cgi"} -> cookie($cookiebase."_sid"); # The session id cookie itself + $self -> {"sessuser"} = $self -> {"cgi"} -> cookie($cookiebase."_u"); # Which user does this session claim to be for? + $self -> {"autokey"} = $self -> {"cgi"} -> cookie($cookiebase."_k"); # Do we have an autologin key for the user? + + # If we don't have a session id now, try to pull it from the query string + $self -> {"sessid"} = $self -> {"cgi"} -> param("sid") if(!$self -> {"sessid"}); + + # If we have a session id, we need to check it + if($self -> {"sessid"}) { + # Try to get the session... + my $session = $self -> get_session($self -> {"sessid"}); + + $self -> {"session_time"} = $session -> {"session_time"}; + + # Do we have a valid session? + if($session) { + # Is the user accessing the site from the same(-ish) IP address? + if($self -> ip_check($ENV{"REMOTE_ADDR"}, $session -> {"session_ip"})) { + # Has the session expired? + if(!$self -> session_expired($session)) { + # The session is valid, and can be touched. + $self -> touch_session($session); + + return $self; + } # if(!$self -> session_expired($session)) { + } # if($self -> ip_check($ENV{"REMOTE_ADDR"}, $session -> {"session_ip"})) { + } # if($session) { + } # if($sessid) { + + # Get here, and we don't have a session at all, so make one. + return $self -> create_session(); +} + + +## @method $ create_session($user, $persist) +# Create a new session. If the user is not specified, this creates an anonymous session, +# otherwise the session is attached to the user. +# +# @param user Optional user ID to associate with the session. +# @param persist If true, and autologins are permitted, an autologin key is generated for +# this session. +# @return true if the session was created, undef otherwise. +sub create_session { + my $self = shift; + my $user = shift; + my $persist = shift; + my $userdata; + + # nuke the cookies, it's the only way to be sure + delete($self -> {"cookies"}) if($self -> {"cookies"}); + + # get the current time... + my $now = time(); + + # If persistent logins are not permitted, disable them + $self -> {"autokey"} = $persist = '' if(!$self -> {"phpbb"} -> get_config("allow_autologin")); + + # Set a default last visit, might be updated later + $self -> {"last_visit"} = $now; + + # If we have a key, and a user in the cookies, try to get it + if($self -> {"autokey"} && $self -> {"sessuser"} && $self -> {"sessuser"} != $phpBB3::ANONYMOUS) { + my $autocheck = $self -> {"dbh"} -> prepare("SELECT u.* FROM ". + $self -> {"phpbb"} -> {"prefix"}."users AS u, ". + $self -> {"settings"} -> {"database"} -> {"keys"}." AS k + WHERE u.user_id = ? + AND u.user_type IN (0, 3) + AND k.user_id = u.user_id + AND k.key_id = ?"); + $autocheck -> execute($self -> {"sessuser"}, md5_hex($self -> {"autokey"})) + or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); + + $userdata = $autocheck -> fetchrow_hashref; + + # If we don't have a key and user in the cookies, do we have a user specified? + } elsif($user) { + $self -> {"autokey"} = ''; + $self -> {"sessuser"} = $user; + + my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"phpbb"} -> {"prefix"}."users + WHERE user_id = ? + AND user_type IN (0, 3)"); + $userh -> execute($self -> {"sessuser"}) + or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); + + $userdata = $userh -> fetchrow_hashref; + } + + # If we don't have any user data then either the key didn't match in the database, + # the user doesn't exist, is inactive, or is a bot. Just get the anonymous user + if(!$userdata) { + $self -> {"autokey"} = ''; + $self -> {"sessuser"} = $phpBB3::ANONYMOUS; + + my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"phpbb"} -> {"prefix"}."users + WHERE user_id = ?"); + $userh -> execute($self -> {"sessuser"}) + or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); + + $userdata = $userh -> fetchrow_hashref; + + # If we have user data, we also want their last login time if possible + } elsif($self -> {"settings"} -> {"detabase"} -> {"lastvisit"}) { + my $visith = $self -> {"dbh"} -> prepare("SELECT last_visit FROM ".$self -> {"settings"} -> {"detabase"} -> {"lastvisit"}. + " WHERE user_id = ?"); + $visith -> execute($userdata -> {"user_id"}) + or return set_error("Unable to peform last visit lookup query\nError was: ".$self -> {"dbh"} -> errstr); + + my $visitr = $visith -> fetchrow_arrayref; + + # Fall back on now if we have no last visit time + $self -> {"last_visit"} = $visitr -> [0] if($visitr); + } + + # Determine whether the session can be made persistent (requires the user to be registered, and normal) + my $is_registered = ($userdata -> {"user_id"} && $userdata -> {"user_id"} != $phpBB3::ANONYMOUS && ($userdata -> {"user_type"} == 0 || $userdata -> {"user_type"} == 3)); + $persist = (($self -> {"autokey"} || $persist) && $is_registered) ? 1 : 0; + + # Do we already have a session id? If we do, and it's an anonymous session, we want to nuke it + if($self -> {"sessid"}) { + my $killsess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " WHERE session_id = ? AND session_user_id = ?"); + $killsess -> execute($self -> {"sessid"}, $phpBB3::ANONYMOUS) + or return set_error("Unable to remove anonymous session\nError was: ".$self -> {"dbh"} -> errstr); + } + + # generate a new session id. The md5 of a unique ID should be unique enough... + $self -> {"sessid"} = md5_hex($self -> {"phpbb"} -> unique_id()); + + # store the time + $self -> {"session_time"} = $now; + + # create a new session + my $sessh = $self -> {"dbh"} -> prepare("INSERT INTO ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " VALUES(?, ?, ?, ?, ?, ?)"); + $sessh -> execute($self -> {"sessid"}, + $self -> {"sessuser"}, + $now, + $now, + $ENV{"REMOTE_ADDR"}, + $persist) + or return set_error("Unable to peform session creation\nError was: ".$self -> {"dbh"} -> errstr); + + $self -> set_login_key($self -> {"sessuser"}, $ENV{"REMOTE_ADDR"}) if($persist); + + return $self; +} + + +## @method $ delete_session() +# Delete the current session, resetting the user's data to anonymous. This will +# remove the user's current session, and any associated autologin key, and then +# generate a new anonymous session for the user. +sub delete_session { + my $self = shift; + + # Okay, the important part first - nuke the session + my $nukesess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " WHERE session_id = ? AND session_user_id = ?"); + $nukesess -> execute($self -> {"sessid"}, $self -> {"sessuser"}) + or return set_error("Unable to remove session\nError was: ".$self -> {"dbh"} -> errstr); + + # If we're not dealing with anonymous, we need to store the visit time, + # and nuke any autologin key for the now defunct session + if($self -> {"sessuser"} != $phpBB3::ANONYMOUS) { + + # If we don't have a session time for some reason, make it now + $self -> {"session_time"} = time() if(!$self -> {"session_time"}); + + # set this user's last visit time to the session time if possible + if($self -> {"settings"} -> {"database"} -> {"lastvisit"}) { + my $newtime = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"lastvisit"}. + " SET last_visit = ? + WHERE user_id = ?"); + $newtime -> execute($self -> {"session_time"}, $self -> {"sessuser"}) + or return set_error("Unable to update last visit time\nError was: ".$self -> {"dbh"} -> errstr); + } + + # And now remove any session keys + if($self -> {"autokey"}) { + my $nukekeys = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"keys"}. + " WHERE key_id = ? AND user_id = ?"); + $nukekeys -> execute(md5_hex($self -> {"autokey"}), $self -> {"sessuser"}) + or return set_error("Unable to remove session key\nError was: ".$self -> {"dbh"} -> errstr); + } + } + + # clear all the session settings internally for safety + $self -> {"sessuser"} = $self -> {"sessid"} = $self -> {"autokey"} = $self -> {"session_time"} = undef; + + # And create a new anonymous session (note that create_session should handle deleting the cookie cache!) + return $self -> create_session(); +} + + +## @method $ encode_querystring($query, $nofix) +# Encode the query string so that it is safe to include it in a hidden input field +# in the login form. +# +# @param query The querystring to encode +# @param nofix If true, this disables the fix needed to make CGI::query_string()'s output usable. +# @return The safely encoded querystring. +sub encode_querystring { + my $self = shift; + my $query = shift; + my $nofix = shift; + + $query =~ s/;/&/g unless($nofix); # fix query_string() return... GRRRRRRR... + + return encode_base64($query, ''); +} + + +## @method $ decode_querystring($query) +# Converts the encoded query string back to standard query string form. +# +# @param query The encoded querystring to decode +# @return The decoded version of the querystring. +sub decode_querystring { + my $self = shift; + my $query = shift; + + # Bomb if we don't have a query, or it is not valid base64 + return "" if(!$query || $query =~ m{[^A-Za-z0-9+/=]}); + + return decode_base64($query); +} + + +## @method $ session_cookies() +# Obtain a reference to an array containing the session cookies. +# +# @return A reference to an array of session cookies. +sub session_cookies { + my $self = shift; + + # Cache the cookies if needed, calls to create_session should ensure the cache is + # removed before any changes are made... but this shouldn't really be called before + # create_session in reality anyway. + if(!$self -> {"cookies"}) { + my $expires = "+".($self -> {"phpbb"} -> get_config("max_autologin_time") || 365)."d"; + my $sesscookie = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_sid', $self -> {"sessid"}, $expires); + my $sessuser = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_u', $self -> {"sessuser"}, $expires); + my $sesskey; + if($self -> {"sessuser"} != $phpBB3::ANONYMOUS) { + if($self -> {"autokey"}) { + $sesskey = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_k', $self -> {"autokey"}, $expires); + } + } else { + $sesskey = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_k', '', '-1y'); + } + + $self -> {"cookies"} = [ $sesscookie, $sessuser, $sesskey ]; + } + + return $self -> {"cookies"}; +} + + +# ============================================================================== +# Theoretically internal stuff + + +## @method ip_check($userip, $sessip) +# Checks whether the specified IPs match. The degree of match required depends +# on the ip_check setting in the SessionHandler object this is called on: 0 means +# that no checking is done, number between 1 and 4 indicate sections of the +# dotted decimal IPs are checked (1 = 127., 2 = 127.0, 3 = 127.0.0., etc) +# +# @param userip The IP the user is connecting from. +# @param sessip The IP associated with the session. +# @return True if the IPs match, false if they do not. +sub ip_check { + my $self = shift; + my $userip = shift; + my $sessip = shift; + + # How may IP address segments should be compared? + my $iplen = $self -> {"phpbb"} -> get_config('ip_check'); + + # bomb immediately if we aren't checking IPs + return 1 if($iplen == 0); + + # pull out as much IP as we're interested in + my ($usercheck) = $userip =~ /((?:\d+.?){$iplen})/; + my ($sesscheck) = $sessip =~ /((?:\d+.?){$iplen})/; + + # Do the IPs match? + return $usercheck eq $sesscheck; +} + + +## @method $ session_cleanup() +# Run garbage collection over the sessions table. This will remove all expired +# sessions and session keys, but in the process it may need to update user +# last visit information. +# +# @return true oin successful cleanup (or cleanup not needed), false on error. +sub session_cleanup { + my $self = shift; + + my $now = time(); + my $timelimit = $now - $self -> {"phpbb"} -> get_config("session_length"); + + # We only want to run the garbage collect occasionally + if($self -> {"settings"} -> {"config"} -> {"lastgc"} < $now - $self -> {"phpbb"} -> get_config("session_gc")) { + # Okay, we're due a garbage collect, update the config to reflect that we're doing it + $self -> {"settings"} -> set_db_config($self -> {"dbh"}, $self -> {"settings"} -> {"database"} -> {"settings"}, "lastgc", $now); + + # Remove expired guest sessions first + my $nukesess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " WHERE session_user_id = ? + AND session_time < ?"); + $nukesess -> execute($phpBB3::ANONYMOUS, $timelimit) + or return set_error("Unable to remove expired guest sessions\nError was: ".$self -> {"dbh"} -> errstr); + + # now get the most recent expired sessions for each user + my $lastsess = $self -> {"dbh"} -> prepare("SELECT session_user_id,MAX(session_time) FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " WHERE session_time < ? + GROUP BY session_user_id"); + $lastsess -> execute($timelimit) + or return set_error("Unable to obtain expired session list\nError was: ".$self -> {"dbh"} -> errstr); + + # Prepare an update query so we don't remake it each time through the loop... + my $updatelast; + if($self -> {"settings"} -> {"database"} -> {"lastvisit"}) { + $updatelast = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"lastvisit"}. + " SET last_visit = ? + WHERE user_id = ?"); + } + + # Go through each returned user updating their last visit to the session time + while(my $lastrow = $lastsess -> fetchrow_arrayref()) { + # set the user's last visit if needed + if($self -> {"settings"} -> {"database"} -> {"lastvisit"}) { + $updatelast -> execute($lastrow -> [1], $lastrow -> [0]) + or return set_error("Unable to update last visit for user ".$lastrow -> [0]."\nError was: ".$self -> {"dbh"} -> errstr); + } + + # and then nuke any expired sessions + $nukesess -> execute($lastrow -> [0], $timelimit) + or return set_error("Unable to remove expired sessions for user ".$lastrow -> [0]."\nError was: ".$self -> {"dbh"} -> errstr); + } + } + + return 1; +} + + +## @method $ session_expired($sessdata) +# Determine whether the specified session has expired. Returns true if it has, +# false if it is still valid. +# +# @param $sessdata A reference to a hash containing the session information +# @return true if the session has expired, false otherwise +sub session_expired { + my $self = shift; + my $sessdata = shift; + + # If the session is not an autologin session, and the last update was before the session length, it is expired + if(!$sessdata -> {"session_autologin"}) { + return 1 if($sessdata -> {"session_time"} < time() - ($self -> {"phpbb"} -> get_config("session_length") + 60)); + + } else { + my $max_autologin = $self -> {"phpbb"} -> get_config("max_autologin_time"); + + # If the session is autologin, and it is older than the max autologin time, or autologin is not enabled, it's expired + return 1 if(!$self -> {"phpbb"} -> get_config("allow_autologin") || + ($max_autologin && $sessdata -> {"session_time"} < time() - ((86400 * $max_autologin) + 60))); + } + + # otherwise, the session is valid + return 0; +} + + +## @method $ get_session($sessid) +# Obtain the data for the session with the specified session ID. If there is no +# session with the specified id in the database, this returns undef, otherwise it +# returns a reference to a hash containing the session data. +# +# @param sessid The ID of the session to search for. +# @return A reference to a hash containing the session data, or undef on error. +sub get_session { + my $self = shift; + my $sessid = shift; + + my $sessh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " WHERE session_id = ?"); + $sessh -> execute($sessid) + or return set_error("Unable to peform session lookup query - ".$self -> {"dbh"} -> errstr); + + return $sessh -> fetchrow_hashref(); +} + + +## @method void touch_session($session) +# Touch the specified session, updating its timestamp to the current time. This +# will only touch the session if it has not been touched in the last minute, +# otherwise this function does nothing. +# +# @param session A reference to a hash containing the session data. +sub touch_session { + my $self = shift; + my $session = shift; + + if(time() - $session -> {"session_time"} > 60) { + $self -> {"session_time"} = time(); + + my $finger = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"sessions"}. + " SET session_time = ? + WHERE session_id = ?"); + $finger -> execute($self -> {"session_time"}, $session -> {"session_id"}) + or die_log($self -> {"cgi"} -> remote_host(), "Unable to touch session. Error was: ".$self -> {"dbh"} -> errstr); + } +} + + +## @method void set_login_key() +# Create the auto login key for the current session user. +# +sub set_login_key { + my $self = shift; + + my $key = $self -> {"autokey"}; + my $key_id = $self -> {"phpbb"} -> unique_id(substr($self -> {"sessid"}, 0, 8)); + + # If we don't have a key, we want to create a new key in the table + if(!$key) { + my $keyh = $self -> {"dbh"} -> prepare("INSERT INTO ".$self -> {"settings"} -> {"database"} -> {"keys"}. + " VALUES(?, ?, ?, ?)"); + $keyh -> execute(md5_hex($key_id), $self -> {"sessuser"}, $ENV{REMOTE_ADDR}, time()) + or die_log($self -> {"cgi"} -> remote_host(), "Unable to create autologin key. Error was: ".$self -> {"dbh"} -> errstr); + + # If we have a key, we want to overwrite it with the new stuff + } else { + my $keyh = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"keys"}. + " SET key_id = ?, last_ip = ?, last_login = ? WHERE user_id = ? AND key_id = ?"); + $keyh -> execute(md5_hex($key_id), $ENV{REMOTE_ADDR}, 0 + time(), 0 + $self -> {"sessuser"}, md5_hex($key)) + or die_log($self -> {"cgi"} -> remote_host(), "Unable to update autologin key. Error was: ".$self -> {"dbh"} -> errstr); + } + + $self -> {"autokey"} = $key_id; +} + + +## @method $ create_cookie($name, $value, $expires) +# Creates a cookie that can be sent back to the user's browser to provide session +# information. +# +# @param name The name of the cookie to set +# @param value The value to set for the cookie +# @param expires An optional expiration value +# @return A cookie suitable to send to the browser. +sub create_cookie { + my $self = shift; + my $name = shift; + my $value = shift; + my $expires = shift; + + return $self -> {"cgi"} -> cookie(-name => $name, + -value => $value, + -expires => $expires, + -path => $self -> {"settings"} -> {"config"} -> {"cookie_path"}, + -domain => $self -> {"settings"} -> {"config"} -> {"cookie_domain"}, + -secure => $self -> {"settings"} -> {"config"} -> {"cookie_secure"}); +} + + +## @fn $ set_error($error) +# Set the error string to the specified value. This updates the class error +# string and returns undef. +# +# @param error The message to set in the error string +# @return undef, always. +sub set_error { + $errstr = shift; + + return undef; +} + +1; diff --git a/Template.pm b/Template.pm new file mode 100644 index 0000000..c12a7ad --- /dev/null +++ b/Template.pm @@ -0,0 +1,609 @@ +## @file +# This file contains the implementation of the template engine. +# +# @author Chris Page <chris@starforge.co.uk> +# @version 1.0 +# @date 23 November 09 +# @copy 2009, Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## @class Template +# A simple Template class with internationalisation support. Note that +# this class does not cache templates or any fancy stuff like that - it +# just provides a simple interface to generate content based on +# replacing markers in files. +package Template; + +use Logging; +use POSIX qw(strftime); +use Utils qw(path_join superchomp); +use strict; + +our ($VERSION, $errstr, $utfentities, $entities); + +BEGIN { + $VERSION = 1.0; + $errstr = ''; + + $utfentities = { '\xC2\xA3' => '£', + '\xE2\x80\x98' => '‘', + '\xE2\x80\x99' => '’', + '\xE2\x80\x9C' => '“', + '\xE2\x80\x9D' => '”', + '\xE2\x80\x93' => '–', + '\xE2\x80\x94' => '—', + '\xE2\x80\xA6' => '…', + }; + $entities = {'\x91' => '‘', # 0x91 (145) and 0x92 (146) are 'smart' singlequotes + '\x92' => '’', + '\x93' => '“', # 0x93 (147) and 0x94 (148) are 'smart' quotes + '\x94' => '”', + '\x96' => '–', # 0x96 (150) and 0x97 (151) are en and emdashes + '\x97' => '—', + '\x88' => '…', # 0x88 (133) is an ellisis + }; +} + + +# ============================================================================ +# Constructor and language loading + +## @cmethod $ new(%args) +# Create a new Template object. This will create a new Template object that will +# allow templates to be loaded into strings, or printed to stdout. Meaningful +# arguments to this constructor are: +# basedir - The directory containing template themes. Defaults to "templates". +# langdir - The directory containing language files. Defaults to "lang". +# lang - The language file to use. Defaults to "en" +# theme - The theme to use. Defaults to "default" +# timefmt - The time format string, strftime(3) format. Defaults to "%a, %d %b %Y %H:%M:%S" +sub new { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + + # Object constructors don't get much more minimal than this... + my $self = { "basedir" => "templates", + "langdir" => "lang", + "lang" => "en", + "theme" => "default", + "timefmt" => '%a, %d %b %Y %H:%M:%S', + "mailfmt" => '%a, %d %b %Y %H:%M:%S %z', + "mailcmd" => '/usr/sbin/sendmail -t -f chris@starforge.co.uk',#pevesupport@cs.man.ac.uk', # Change -f as needed! + @_, + }; + + my $obj = bless $self, $class; + + # Load the language definitions + $obj -> load_language() or return undef; + + return $obj; +} + + +## @method void DESTROY() +# Destructor method to prevent a circular list formed from a reference to the modules +# hash from derailing normal destruction. +sub DESTROY { + my $self = shift; + + $self -> {"modules"} = undef; +} + + +## @method void set_module_obj($modules) +# Store a reference to the module handler object so that the template loader can +# do block name replacements. +# +# @param modules A reference to the system module handler object. +sub set_module_obj { + my $self = shift; + + $self -> {"modules"} = shift; +} + + +## @method $ load_language(void) +# Load all of the language files in the appropriate language directory into a hash. +# This will attempt to load all .lang files inside the langdir/lang/ directory, +# attempting to parse VARNAME = string into a hash using VARNAME as the key and string +# as the value. The hash is build up inside the Template object rather than returned. +# +# @return true if the language files loaded correctly, undef otherwise. +sub load_language { + my $self = shift; + + # First work out which directory we are dealing with + my $langdir = path_join($self -> {"langdir"}, $self -> {"lang"}); + + # open it, so we can process files therein + opendir(LANG, $langdir) + or return set_error("Unable to open language directory '$langdir' for reading: $!"); + + while(my $name = readdir(LANG)) { + # Skip anything that doesn't identify itself as a .lang file + next unless($name =~ /\.lang$/); + + my $filename = path_join($langdir, $name); + + # Attempt to open and parse the lang file + if(open(WORDFILE, "<:utf8", $filename)) { + while(my $line = ) { + superchomp($line); + + # skip comments + next if($line =~ /^\s*#/); + + # Pull out the key and value, and + my ($key, $value) = $line =~ /^\s*(\w+)\s*=\s*(.*)$/; + next unless(defined($key) && defined($value)); + + # Unslash any \"s + $value =~ s/\\\"/\"/go; + + # warn if we are about to redefine a word + warn_log("Unknown", "$key already exists in language hash!") if($self -> {"words"} -> {$key}); + + $self -> {"words"} -> {$key} = $value; + } + + close(WORDFILE); + } else { + warn_log("Unknown", "Unable to open language file $filename: $!"); + } + } + + closedir(LANG); + + # Did we get any language data at all? + return set_error("Unable to load any lanugage data. Check your language selection!") + if(!defined($self -> {"words"})); + + return 1; +} + + +# ============================================================================ +# Templating functions + +## @method $ replace_langvar($varname, $default, $varhash) +# Replace the specified language variable with the appropriate text string. This +# takes a language variable name and returns the value stored for that variable, +# if there is on. If there is no value available, and the default is provided, +# that is returned. If all else fails this just returns "<$varname>" +# +# @param varname The name of the language variable to obtain a value for. +# @param default An optional default value. +# @param varhash An optional reference to a hash containing key-value pairs, any +# occurance of the key in the text string is replaced with the value. +# @return The value for the language variable, or the default if the value is not +# available. If the default is not available either this returns the +# variable name in angled brackets. +sub replace_langvar { + my $self = shift; + my $varname = shift; + my $default = shift; + my $varhash = shift; + + # Fix up the arguments - if default is a reference, then it's really the varhash + # and default was omitted + if(ref($default) eq "HASH") { + $varhash = $default; + + # Make the default value be the variable name in red to hilight problems + $default = "$varname"; + } + + # strip the leadin L_ if present + $varname =~ s/^L_//o; + + if(defined($self -> {"words"} -> {$varname})) { + my $txtstr = $self -> {"words"} -> {$varname}; + + # If we have a hash of variables to substitute, do the substitute + if($varhash) { + foreach my $key (keys(%$varhash)) { + my $value = defined($varhash -> {$key}) ? $varhash -> {$key} : ""; # make sure we get no undefined problems... + $txtstr =~ s/\Q$key\E/$value/g; + } + } + + # Do any module marker replacements if we can + if($self -> {"modules"}) { + $txtstr =~ s/{B_\[(\w+?)\]}/$self->replace_blockname($1)/ge; + } + + return $txtstr; + } + + return $default; +} + + +## @method $ replace_blockname($blkname, $default) +# Replace a block name with the internal ID for the block. This will replace +# a block name with the equivalent block ID and it can cope with the name +# being embedded in B_[...] strings. +# +# @param blkname The name of the block to replace with a block id. +# @param default Optional default id to use if the block is not found. +# @return The id that corresponds to the specified block name. +sub replace_blockname { + my $self = shift; + my $blkname = shift; + my $default = shift || "0"; + + # Strip the B_[ ] if present + $blkname =~ s/^B_\[(.*)\]$/$1/; + + my $modid = $self -> {"modules"} -> get_block_id($blkname); + + return defined($modid) ? $modid : $default; +} + + +## @method $ load_template($name, $varmap) +# Load a template from a file and replace the tags in it with the values given +# in a hashref, return the string containing the filled-in template. The first +# argument should be the filename of the template, the second should be the +# hashref containing the key-value pairs. The keys should be the tags in the +# template to replace, the values should be the text to replace those keys +# with. Tags can be any format and may contain regexp reserved chracters. +# +# @param name The name of the template to load. +# @param varmap A reference to a hash containing values to replace in the template. +# @return The template with replaced variables and language markers. +sub load_template { + my $self = shift; + my $name = shift; + my $varmap = shift; + + my $filename = path_join($self -> {"basedir"}, $self -> {"theme"}, $name); + + if(open(TEMPLATE, "<:utf8", $filename)) { + undef $/; + my $lines =