commit 7b7fac78b3d11609f987cb3100937fb750640437 Author: Chris Date: Mon May 16 14:39:13 2011 +0100 Initial version of webperl added. 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 =