package Labyrinth::Globals; use warnings; use strict; use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT @EXPORT_OK); $VERSION = '5.13'; =head1 NAME Labyrinth::Globals - Configuration and Parameter Handler for Labyrinth =head1 SYNOPSIS use Labyrinth::Globals qw(:all); # database object creation DBConnect(); # Interface (CGI) parameter handling ParseParams(); =head1 DESCRIPTION The Globals package contains a number of variables and functions that are used across the system. The variables contain input and output values, and the functions are generic. =head1 EXPORT All by default. use Labyrinth::Globals qw(:all); # all methods use Labyrinth::Globals qw(:default); # barebones methods =cut # ------------------------------------- # Export Details require Exporter; @ISA = qw(Exporter); %EXPORT_TAGS = ( 'all' => [ qw( LoadAll LoadSettings LoadRules ParseParams DBConnect dbh ScriptPath ScriptFile ) ], 'default' => [ qw( dbh ScriptPath ScriptFile ) ] ); @EXPORT_OK = ( @{$EXPORT_TAGS{'all'}} ); @EXPORT = ( @{$EXPORT_TAGS{'default'}} ); # ------------------------------------- # Library Modules use Config::IniFiles; use Cwd; use Data::Dumper; use Data::FormValidator; use Data::FormValidator::Constraints::Upload; use Data::FormValidator::Constraints::Words; use Data::FormValidator::Filters::Demoroniser qw(demoroniser); use File::Basename; use File::Path; use File::Spec::Functions; use IO::File; use Labyrinth::Audit; use Labyrinth::Constraints; use Labyrinth::Constraints::Emails; use Labyrinth::DBUtils; use Labyrinth::DIUtils; use Labyrinth::Variables; use Labyrinth::Writer; # ------------------------------------- # Variables my %rules; # internal rules hash # ------------------------------------- # The Subs =head1 FUNCTIONS =head2 Loaders =over 4 =item LoadAll([$dir]) LoadAll() automatically loads and instatiates many global variables. The method assumes default values are required. Can be called with a base install directory path, which is then used by LoadSettings(). This method should be called at the beginning of any script. =item LoadSettings($dir) LoadSettings() loads a settings file (config/settings.ini) and stores them in an internal hash. Typical settings are database settings (eq driver, database, user, password) and general settings (eg administrator email). LoadSettings() can be passed the name of the base install directory, or it will attempt to figure it out via the current working directory. =item LoadRules() LoadRules() loads a rules file (default is parserules.ini or the name of the 'parsefile' in the settings configuration file) and store the rules in an internal hash. This hash is then used to verify the contains of any interface (CGI) parameters passed to the script. Note that as LoadRules() can be called many times with different rules files, only the last value of a given rule is stored. This is useful if you wish to have a standard rules file and wish to load further or different rules dependant upon the script being used. =back =cut sub LoadAll { my $settings = shift; LoadSettings($settings); ParseParams(); DBConnect(); } sub LoadSettings { my $settings = shift; # default file names my $LOGFILE = 'audit.log'; my $PHRASEBOOK = 'phrasebook.ini'; my $PARSEFILE = 'parserules.ini'; # Server/HTTP values my $host = $ENV{'HTTP_HOST'}; my $ipaddr = $ENV{'REMOTE_ADDR'}; my ($protocol) = $ENV{'SERVER_PROTOCOL'} ? ($ENV{'SERVER_PROTOCOL'} =~ m!^(.*)/!) : $ENV{'SERVER_PORT'} && $ENV{'SERVER_PORT'} eq '443' ? ('https') : ('http'); $protocol = lc($protocol); my $path = $ENV{'REQUEST_URI'} ? 'REQUEST_URI' : 'PATH_INFO'; my ($req,$script) = ($ENV{$path} && $ENV{$path} =~ m|^(.*)/([^?]+)|) ? ($1,$2) : ('',''); my $cgiroot = ($req =~ /^$protocol:/) ? $req : $protocol . '://' . ($ENV{'HTTP_HOST'} ? $ENV{'HTTP_HOST'} : '') . $req; my $docroot = ($req && $cgiroot =~ m!^((.*)/.*?)! ? $1 : $cgiroot); $cgiroot =~ s!/$!!; $docroot =~ s!/$!!; # set defaults my ($cgipath,$webpath) = ($cgiroot,$docroot); # load the configuration data if(!-r $settings) { LogError("Cannot read settings file [$settings]"); $tvars{errcode} = 'ERROR'; return; } my $cfg = Config::IniFiles->new( -file => $settings ); unless(defined $cfg) { LogError("Unable to load settings file [$settings]"); $tvars{errcode} = 'ERROR'; return; } for my $sect ($cfg->Sections()) { for my $name ($cfg->Parameters($sect)) { my @value = $cfg->val($sect,$name); next unless(@value); if(@value > 1) { $settings{$name} = \@value; $tvars{$name} = \@value if($sect =~ /^(PROJECT|HTTP|CMS)$/); } elsif(@value == 1) { $settings{$name} = $value[0]; $tvars{$name} = $value[0] if($sect =~ /^(PROJECT|HTTP|CMS)$/); } } } $cfg = undef; SetLogFile( FILE => $settings{'logfile'}, USER => 'labyrinth', LEVEL => ($settings{'loglevel'} || 0), CLEAR => (defined $settings{'logclear'} ? $settings{'logclear'} : 1), CALLER => (defined $settings{'logcaller'} ? $settings{'logcaller'} : 1) ); # evaluate standard path settings $settings{'protocol'} = $protocol; $settings{'host'} = $host; $settings{'ipaddr'} = $ipaddr; $settings{'docroot'} = $docroot; $settings{'cgiroot'} = $cgiroot; $settings{'script'} = $script; $settings{'logdir'} = "$settings{'webdir'}/cache" unless($settings{'logdir'}); $settings{'config'} = "$settings{'cgidir'}/config" unless($settings{'config'}); $settings{'templates'} = "$settings{'cgidir'}/templates" unless($settings{'templates'}); $settings{'webpath'} = $webpath unless(exists $settings{'webpath'}); $settings{'cgipath'} = $cgipath unless(exists $settings{'cgipath'}); $tvars{$_} = $settings{$_} for(qw(host docroot cgiroot webpath cgipath script ipaddr)); $settings{'logfile'} = "$settings{'logdir'}/$LOGFILE" unless($settings{'logfile'}); $settings{'phrasebook'} = "$settings{'config'}/$PHRASEBOOK" unless($settings{'phrasebook'}); $settings{'parsefile'} = "$settings{'config'}/$PARSEFILE" unless($settings{'parsefile'}); # generate the absolute path, in the event of errors foreach my $key (qw(logfile phrasebook parsefile)) { next unless $settings{$key}; next if $settings{$key} =~ m|^/|; $settings{$key} = File::Spec->rel2abs( $settings{$key} ) ; } # path & title mappings for my $map (qw(path title)) { next unless($settings{$map . 'maps'}); if( ref($settings{$map . 'maps'}) eq 'ARRAY') { for(@{ $settings{$map . 'maps'} }) { my ($name,$value) = split(/=/,$_,2); $settings{$map . 'map'}{$name} = $value; } } elsif($settings{$map . 'maps'}) { my ($name,$value) = split(/=/,$settings{$map . 'maps'},2); $settings{$map . 'map'}{$name} = $value; } } #use Data::Dumper; #LogDebug("settings=".Dumper(\%settings)); # set image processing driver, if specified Labyrinth::DIUtils::Tool($settings{diutils}) if($settings{diutils}); $settings{settingsloaded} = 1; } sub LoadRules { return if($settings{rulesloaded}); # ensure we can access the rules file my $rules = shift || $settings{'parsefile'}; die "Cannot read rules file [$rules]\n" if(!-r $rules); my $fh = IO::File->new($rules, 'r'); die "Cannot open rules file [$rules]: $!\n" unless(defined $fh); %rules = ( validator_packages => [qw( Data::FormValidator::Constraints::Upload Data::FormValidator::Constraints::Words Labyrinth::Constraints::Emails Labyrinth::Constraints )], filters => ['trim', demoroniser()], msgs => {prefix=> 'err_'}, # set a custom error prefix # untaint_all_constraints => 1, missing_optional_valid => 1, constraint_methods => { realname => \&realname, basicwords => \&basicwords, simplewords => \&simplewords, paragraph => \¶graph, emails => \&emails, url => \&url, ddmmyy => \&ddmmyy }, ); my ($required_regex,$optional_regex); while(<$fh>) { s/\s+$//; my ($name,$required,$default,$filters,$constraint,$regex) = split(',',$_,6); next unless($name); $name =~ s/\s+$// if(defined $name); $required =~ s/\s+$// if(defined $required); $default =~ s/\s+$// if(defined $default); $filters =~ s/\s+$// if(defined $filters); $constraint =~ s/\s+$// if(defined $constraint); # $rules{$name}->{required} = $required; # $rules{$name}->{default} = $default; # $rules{$name}->{constraint} = $constraint; # $rules{$name}->{regex} = "@regex"; if($name =~ /^:(.*)/) { $name = qr/$1/; if($required) { $required_regex .= "$name|" } else { $optional_regex .= "$name|" } if($constraint) { $rules{constraint_regexp_map}->{$name} = _constraint($constraint) } elsif($regex) { $rules{constraint_regexp_map}->{$name} = qr/^$regex$/ } else { die "no constraint or regex for entry: $name" } if($filters) { $rules{field_filter_regexp_map}->{$name} = [split(":",$filters)] } } else { if($required) { push @{$rules{required}}, $name } else { push @{$rules{optional}}, $name } if($constraint) { $rules{constraints}->{$name} = _constraint($constraint) } elsif($regex) { $rules{constraints}->{$name} = qr/^$regex$/ } else { die "no constraint or regex for entry: $name" } if($default) { $rules{defaults}->{$name} = $default } if($filters) { $rules{field_filters}->{$name} = [split(":",$filters)] } } } $fh->close; #LogDebug("Constraints: rules=" . Dumper(\%rules)); if($required_regex) { $required_regex =~ s/|$//; $rules{required_regexp} = qr/^$required_regex$/; } if($optional_regex) { $optional_regex =~ s/|$//; $rules{optional_regexp} = qr/^$optional_regex$/; } $rules{debug} = 0; $settings{rulesloaded} = 1; } sub _constraint { my $constraint = shift; if($constraint eq 'imagefile') { my %hash = ( constraint_method => 'file_format', params => [mime_types => [qw!image/jpe image/jpg image/jpeg image/gif image/png!]], ); return \%hash; } else { my %hash = ( constraint_method => $constraint, ); return \%hash; } return $constraint; } =head2 Script Name =over 4 =item ScriptPath() =item ScriptFile() =back =cut sub ScriptPath { return $settings{cgipath} if($settings{cgipath} =~ m!^http!); # we're assuming only http/https return $settings{cgiroot}; } sub ScriptFile { my %hash = @_; my $path = ScriptPath() || ''; my $file = $hash{file} || $settings{script}; my $query = $hash{query} ? '?' . $hash{query} : ''; return "$path/$file$query"; } =head2 Database Handling =over 4 =item DBConnect() The method to initiate the Database access object. The method passes the values held within the internal settings (set LoadSettings()), to the DB access object constructor. It returns and stores internally the object reference, which can be accessed across the system via the $dbi scalar. =item dbh Returns the reference to the DB access object, as created by the DBConnect() method, or calls DBConnect() if not previously invoked. =back =cut sub DBConnect { return $dbi if $dbi; # object already exists # use settings or defaults my $logfile = $settings{logfile}; my $phrasebook = $settings{phrasebook}; my $dictionary = $settings{dictionary}; $dbi = Labyrinth::DBUtils->new({ driver => $settings{driver}, database => $settings{database}, dbfile => $settings{dbfile}, dbhost => $settings{dbhost}, dbport => $settings{dbport}, dbuser => $settings{dbuser}, dbpass => $settings{dbpass}, autocommit => $settings{autocommit}, logfile => $logfile, phrasebook => $phrasebook, dictionary => $dictionary, }); LogDebug("DBConnect DONE"); $dbi; } # used by the DB access object sub _errors { my $err = shift; my $sql = shift; my $message = ''; $message = "$err
" if($err); $message .= "
SQL=$sql
" if($sql); $message .= "ARGS=[".join(",",@_)."]" if(@_); $tvars{failures} = [ { code => 'DB', message => $message } ]; PublishCode('MESSAGE'); exit; } sub dbh { $dbi || DBConnect; } =head2 Interface Parameter Handling =over 4 =item ParseParams($rules) ParseParams() reads and validates the interface (CGI) parameters that are sent via a HTTP request, before storing them in the %cgiparams hash. Each parameter must have a rule for it to be accepted. The rules file (default is parserules.ini) is automatically loaded and stored. All valid input parameter values (scalars only) are also automatically stored in the template variable hash, %tvars. This is to enable templates to be reparsed in the event of an error, and retain the user's valid entries. =back =cut sub ParseParams { LoadRules($_[0]) unless($settings{rulesloaded}); my $results; # LogDebug("rules=".Dumper(\%rules)); if(!defined $ENV{'SERVER_SOFTWARE'}) { # commandline testing my $file = "$settings{'config'}/cgiparams.nfo"; if(-r $file) { my $fh = IO::File->new($file, 'r') or return; my (%params,$params); { local $/ = undef; $params = <$fh>; } $fh->close; foreach my $param (split(/[\r\n]+/,$params)) { my ($name,$value) = $param =~ /(\w+)=(.*)/; next unless($name); if($value =~ /\[([^\]]+)\]/) { @{$params{$name}} = split(",",$1); } else { $params{$name} = $value; } } LogDebug("params=".Dumper(\%params)); $results = Data::FormValidator->check(\%params, \%rules); $settings{testing} = 1; } } else { use Data::FormValidator; my %fdat = $cgi->Vars; LogDebug("fdat=".Dumper(\%fdat)); my %fields = map {$_ => 1} @{$rules{required}}, @{$rules{optional}}; # for (keys %fdat) { # LogDebug("NO RULE: $_") # unless( $fields{$_} || # ($rules{required_regexp} && $_ =~ $rules{required_regexp}) || # ($rules{optional_regexp} && $_ =~ $rules{optional_regexp}) # ); # } $results = Data::FormValidator->check($cgi, \%rules); } if($results) { my $values = $results->valid; %cgiparams = %$values; $values = $results->msgs; foreach my $key (keys %$values) { $tvars{$key} = $values->{$key} if($key =~ /^err_/); } } else { LogDebug("NO Data::FormValidator RESULTS!"); my( $valids, $missings, $invalids, $unknowns ) = Data::FormValidator->validate($cgi, \%rules); LogDebug("NO RULE: valids=".Dumper($valids)); LogDebug("NO RULE: invalids=".Dumper($invalids)); # LogDebug("NO RULE: missings=".Dumper($missings)); # LogDebug("NO RULE: unknowns=".Dumper($unknowns)); %cgiparams = %$valids; $cgiparams{'err_'.$_} = 'Invalid' for(@$invalids); } LogDebug("cgiparams=".Dumper(\%cgiparams)); LogInfo("ParseParams DONE"); } 1; __END__ =head1 SEE ALSO IO::File, Cwd, File::Path, File::Basename, File::Spec::Functions, Data::FormValidator, Data::FormValidator::Constraints::Upload Labyrinth =head1 AUTHOR Barbie, for Miss Barbell Productions, L =head1 COPYRIGHT & LICENSE Copyright (C) 2002-2012 Barbie for Miss Barbell Productions All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the Artistic License 2.0. =cut