Source for file pnAPI.php
Documentation is available at pnAPI.php
* Zikula Application Framework
* @copyright (c) 2001, Zikula Development Team
* @link http://www.zikula.org
* @version $Id: pnAPI.php 24457 2008-07-10 07:08:27Z markwest $
* @license GNU/GPL - http://www.gnu.org/copyleft/gpl.html
define('PNMODULE_STATE_UNINITIALISED', 1);
define('PNMODULE_STATE_INACTIVE', 2);
define('PNMODULE_STATE_ACTIVE', 3);
define('PNMODULE_STATE_MISSING', 4);
define('PNMODULE_STATE_UPGRADED', 5);
define('PNMODULE_STATE_INVALID', - 1);
* Module dependency states
define('PNMODULE_DEPENDENCY_REQUIRED', 1);
define('PNMODULE_DEPENDENCY_RECOMMENDED', 2);
define('PNMODULE_DEPENDENCY_CONFLICTS', 3);
* 'All' and 'unregistered' for user and group permissions
define('PNPERMS_UNREGISTERED', '0');
* Core version informations - should be upgraded on each release for
* better control on config settings
define('PN_VERSION_NUM', '1.0.1');
define('PN_VERSION_ID', 'Zikula');
define('PN_VERSION_SUB', 'adam_baum');
* Fake module for config vars
define('PN_CONFIG_MODULE', '/PNConfig');
* Core initialisation stages
define('PN_CORE_OBJECTLAYER', 8);
define('PN_CORE_SESSIONS', 32);
define('PN_CORE_DECODEURLS', 1024);
define('PN_CORE_THEME', 2048);
* get a configuration variable
* @param name $ the name of the variable
* @param default the default value to return if the requested param is not set
* @return mixed value of the variable, or false on failure
if (isset ($GLOBALS['PNConfig']['System'][$name])) {
$mod_var = $GLOBALS['PNConfig']['System'][$name];
$GLOBALS['PNConfig']['System'][$name] = $mod_var;
* set a configuration variable
* @param name $ the name of the variable
* @param value $ the value of the variable
* @return bool true on success, false on failure
$name = isset ($name) ? (string) $name : '';
// The database parameter are not allowed to change
if (empty($name) || $name == 'dbtype' || $name == 'dbhost' || $name == 'dbuname' || $name == 'dbpass' ||
$name == 'dbname' || $name == 'system' || $name == 'prefix' || $name == 'encoded') {
$GLOBALS['PNConfig']['System'][$name] = $value;
* delete a configuration variable
* @param name $ the name of the variable
* @returns mixed value of deleted config var or false on failure
// The database parameter are not allowed to be deleted
if (empty($name) || $name == 'dbtype' || $name == 'dbhost' || $name == 'dbuname' || $name == 'dbpass' ||
$name == 'dbname' || $name == 'system' || $name == 'prefix' || $name == 'encoded') {
if (isset ($GLOBALS['PNConfig']['System'][$name])) {
$val = $GLOBALS['PNConfig']['System'][$name];
unset ($GLOBALS['PNConfig']['System'][$name]);
* Carries out a number of initialisation tasks to get Zikula up and
* @returns bool true initialisation successful false otherwise
function pnInit($stages = PN_CORE_ALL)
static $globalscleansed = false;
// force register_globals = off
if ($globalscleansed == false && ini_get('register_globals')) {
foreach ($GLOBALS as $s_variable_name => $m_variable_value) {
if (!in_array($s_variable_name, array('GLOBALS', 'argv', 'argc', '_FILES', '_COOKIE', '_POST', '_GET', '_SERVER', '_ENV', '_SESSION', '_REQUEST', 's_variable_name', 'm_variable_value', '_PNSession'))) {
unset ($GLOBALS[$s_variable_name]);
unset ($GLOBALS['s_variable_name']);
unset ($GLOBALS['m_variable_value']);
// Neither Smarty nor Zikula itself works with magic_quotes_runtime (not to be confused with magic_quotes_gpc!)
die("Sorry, but Zikula does not support PHP magic_quotes_runtime - please disable this feature in your php.ini file.");
// initialise environment
$GLOBALS['PNConfig'] = array();
$GLOBALS['PNRuntime'] = array();
// add some useful runtime vars
// store the load stages in a global so other API's can check whats loaded
$GLOBALS['loadstages'] = $stages;
// Hack for some weird PHP systems that should have the
// LC_* constants defined, but don't
// quick hack until we remove this define
// we need this loaded before anything else to access the
// faster require/include_once functions
class_exists('Loader') || require 'includes/pnobjlib/Loader.class.php';
// the next include independent from the rest of the pnobjlib
// include old pnTheme.php for backwards compatibility
// To Do: Decide when to remove this
// Initialise and load configuration
$GLOBALS['PNConfig']['System']['PN_CONFIG_USE_OBJECT_ATTRIBUTION'] = false;
$GLOBALS['PNConfig']['System']['PN_CONFIG_USE_OBJECT_LOGGING'] = false;
$GLOBALS['PNConfig']['System']['PN_CONFIG_USE_OBJECT_META'] = false;
// Initialize the (ugly) additional header array
$GLOBALS['additional_header'] = array();
* schemas - holds all component/instance schemas
* Should wrap this in a static one day, but the information
* isn't critical so we'll do it later
$GLOBALS['schemas'] = array();
// Check that Zikula is installed before continuing
header('HTTP/1.1 503 Service Unavailable');
if (file_exists('config/templates/notinstalled.htm')) {
header('HTTP/1.1 503 Service Unavailable');
if (file_exists('config/templates/dbconnectionerror.htm')) {
// Initialise and load pntables
// ensure that the base modules info is available
// if we've got this far an error handler can come into play
// (except in the installer)
// ensure that the sesssions table info is available
if ($anonymoussessions== '1' || !empty($_COOKIE[SessionUtil::getCookieName()])) {
// we need to create a session for guests as configured or
// a cookie exists which means we have been here before
// Auto-login via HTTP(S) REMOTE_USER property
// Load our language files
// Set compression on if desired
// New pnAnticracker code needs to be after pnMod as it's now a module - markwest
// Cross-Site Scripting attack defense - Sent by larsneo
// some syntax checking against injected javascript
// TODO - move to banners module
// Call Stats module counter code if installed
// Call Referers module counter code if installed
// register default page vars
// check the users status, if not 1 then log him out
// remove log files being too old
* Initialise DB connection
* @return bool true if successful, false otherwise
* get a list of database connections
* @copyright Copyright (c) 2003 Envolution; Eric Barr. All rights reserved.
* @param bool $pass_by_reference default = false
* @param string $fetchmode set ADODB fetchmode ADODB_FETCH_NUM, ADODB_FETCH_ASSOC, ADODB_FETCH_DEFAULT, ADODB_FETCH_BOTH
* @return array array of database connections
function pnDBGetConn($pass_by_reference = false, $fetchmode = ADODB_FETCH_NUM)
// If $pass_by_reference is true, return a reference to the dbconn object
if ($pass_by_reference == true) {
* get a list of database tables
* @copyright Copyright (c) 2003 Envolution; Eric Barr. All rights reserved.
* @return array array of database tables
return $GLOBALS['pntables'];
* get a list of dbms specific table options
* For use by ADODB's data dictionary
* Additional database specific settings can be defined here
* See ADODB's data dictionary docs for full details
* @param $tablename (optional) if tablename and there is a set of options configured
* for this table via pntable.php then this we're returning these options, the default options are returned otherwise
if(isset ($pntables[$tablename. '_def'])){
return $pntables[$tablename. '_def'];
if (!isset ($tableoptions)) {
// for mysql we need to get the the version to act on
// and the table type (myisam/innodb)
if (!isset ($type) || empty($type)) $type = 'MyISAM';
if (substr($serverinfo['version'], 0, 3) == '3.2') {
$tableoptions = array('mysql' => 'type = ' . $type);
$tableoptions = array('mysql' => 'engine = ' . $type);
* Set Database Table Listing
* @desc Creates the database table listing if it hasn't been created yet
* and merges new table listings into the master list.
* @copyright Copyright (c) 2003 Envolution; Eric Barr. All rights reserved.
* @param array $newtables
// Create a static var to hold the database table listing
// If the table listing doesn't exist create it with the input array
// if a multisite has its own pntables.
require_once WHERE_IS_PERSO . 'pntables.php';
// Set the pntables in the global listing for pnDBGetTables to have access to
$GLOBALS['pntables'] = $pntable;
* get's the database prefix for the current site
* In a non multisite scenario this will be the 'prefix' config var
* from config/config.php. For a multisite configuration the multistes
* module will manage the prefixes for a given table
* The table name parameter is the table name to get the prefix for
* minus the prefix and seperating _
* e.g. pnDBGetPrefix returns pn_modules for pnDBGetPrefix('modules');
* @param table - table name
$prefix = pnModAPIFunc('Multisites', 'user', 'getprefix', array('table' => $table));
* Gets a global variable, cleaning it up to try to ensure that
* hack attacks don't work
* @see FormUtil::getPassedValues
* @param var $ name of variable to get
* @return mixed prepared variable if only one variable passed
* in, otherwise an array of prepared variables
LogUtil::log('Function pnVarCleanFromInput() is deprecated. Please use FormUtil::getPassedValue() instead.', 'STRICT');
foreach ($vars as $var) {
* stripslashes on multidimensional arrays.
* Used in conjunction with pnVarCleanFromInput
* @param any $ variables or arrays to be stripslashed
function pnStripslashes (&$value)
* Gets a variable, cleaning it up such that the text is
* shown exactly as expected
* @see DataUtil::formatForDisplay
* @param var $ variable to prepare
* @return mixed prepared variable if only one variable passed
* in, otherwise an array of prepared variables
LogUtil::log('Function pnVarPrepForDisplay() is deprecated. Please use DataUtil::formatForDisplay() instead.', 'STRICT');
foreach ($ourvars as $ourvar) {
* Gets a variable, cleaning it up such that the text is
* shown exactly as expected, except for allowed HTML tags which
* @author Xaraya development team
* @see DataUtil::formatForDisplayHTML
* @param var variable to prepare
* @return string/array prepared variable if only one variable passed
* in, otherwise an array of prepared variables
LogUtil::log('Function pnVarPrepHTMLDisplay() is deprecated. Please use DataUtil::formatForDisplayHTML() instead.', 'STRICT');
foreach ($ourvars as $ourvar) {
* Gets a variable, cleaning it up such that the text is
* stored in a database exactly as expected
* @see DataUtil::formatForStore()
* @param var $ variable to prepare
* @return mixed prepared variable if only one variable passed
* in, otherwise an array of prepared variables
LogUtil::log('Function pnVarPrepForStore() is deprecated. Please use DataUtil::formatForStore() instead.', 'STRICT');
foreach ($ourvars as $ourvar) {
* ready operating system output
* Gets a variable, cleaning it up such that any attempts
* to access files outside of the scope of the Zikula
* @see DataUtil::formatForOS()
* @param var $ variable to prepare
* @return mixed prepared variable if only one variable passed
* in, otherwise an array of prepared variables
LogUtil::log('Function pnVarPrepForOS() is deprecated. Please use DataUtil::formatForOS() instead.', 'STRICT');
foreach ($ourvars as $ourvar) {
foreach ($ourvars as $ourvar) {
* validate a zikula variable
* @author Damien Bonvillain
* @author Gregor J. Rothfuss
* @since 1.23 - 2002/02/01
* @param $var the variable to validate
* @param $type the type of the validation to perform (email, url etc.)
* @param $args optional array with validation-specific settings (never used...)
* @return bool true if the validation was successful, false otherwise
if (!isset ($var) || !isset ($type)) {
// typecasting (might be useless in this function)
static $maxlength = array('modvar' => 64,
static $minlength = array('mod' => 1,
// commented out some regexps until some useful and working ones are found
static $regexp = array( // 'mod' => '/^[^\\\/\?\*\"\'\>\<\:\|]*$/',
// 'func' => '/[^0-9a-zA-Z_]/',
// 'api' => '/[^0-9a-zA-Z_]/',
// 'theme' => '/^[^\\\/\?\*\"\'\>\<\:\|]*$/',
'email' => '/^(?:[^\s\000-\037\177\(\)<>@,;:\\"\[\]]\.?)+@(?:[^\s\000-\037\177\(\)<>@,;:\\\"\[\]]\.?)+\.[a-z]{2,6}$/Ui',
'url' => '/^([!#\$\046-\073=\077-\132_\141-\172~]|(?:%[a-f0-9]{2}))+$/i');
if ($type == 'mod' && $var == '/PNConfig') {
if ($type == 'config' && ($var == 'dbtype') ||
// The database parameter are not allowed to change
if ($type == 'email' || $type == 'url') {
// CSRF protection for email and url
$var = str_replace(array('\r', '\n', '%0d', '%0a'), '', $var);
// transfer between the encoded (Punycode) notation and the decoded (8bit) notation.
// all characters must be 7 bit ascii
if (!empty($url_array) && empty($url_array['scheme'])) {
// check for invalid characters
// variable passed special checks. We now to generic checkings.
// check for maximal length
if (isset ($maxlength[$type]) && strlen($var) > $maxlength[$type]) {
// check for minimal length
if (isset ($minlength[$type]) && strlen($var) < $minlength[$type]) {
// check for regular expression
if (isset ($regexp[$type]) && !preg_match($regexp[$type], $var)) {
// all tests for illegal entries failed, so we assume the var is ok ;-)
* get status message from previous operation
* Obtains any status message, and also destroys
* it from the session to prevent duplication
* @return string the status message
// Error message overrides status message
* get base URI for Zikula
* @author Martin Andersen
* @return string base URI for Zikula
if ($path == '/') $path = '';
// remove our entry point if found
if (empty($entrypoint)) {
$entrypoint = 'index.php';
$strip = stristr($path, '/'. $entrypoint);
* get base URL for Zikula
* @return string base URL for Zikula
return "$proto$server$path/";
* @param string $redirecturl URL to redirect to
* @param array $addtionalheaders array of header strings to send with redirect
* @returns bool true if redirect successful, false otherwise
function pnRedirect($redirecturl, $additionalheaders = array())
// very basic input validation against HTTP response splitting
$redirecturl = str_replace(array('\r', '\n', '%0d', '%0a'), '', $redirecturl);
// check if the headers have already been sent
// Always close session before redirect
// add any additional headers supplied
if (!empty($additionalheaders)) {
foreach ($additionalheaders as $additionalheader) {
if (preg_match('!^(?:http|https|ftp|ftps):\/\/!', $redirecturl)) {
// Absolute URL - simple redirect
} elseif (substr($redirecturl, 0, 1) == '/') {
// Removing leading slashes from redirect url
// Get base URL and append it to our redirect url
$redirecturl = $baseurl. $redirecturl;
header("Location: $redirecturl");
* check to see if this is a local referral
* @param bool strict - strict checking ensures that a referer must be set as well as local
* @return bool true if locally referred, false if not
// an empty referer returns true unless strict checking is enabled
if (!$strict && empty($referer)) {
// check the http referer
if (preg_match("!^https?://$server/!", $referer)) {
// Hack - we need this for themes, but will get rid of it soon
* get a Time String in the right format
* @param time $ - prefix string
* @return mixed string if successfull, false if not
* e-mail messages should now be send with a pnModAPIFunc call to the mailer module
* @param to $ - recipient of the email
* @param subject $ - title of the email
* @param message $ - body of the email
* @param headers $ - extra headers for the email
* @param html $ - message is html formatted
* @param debug $ - if 1, echo mail content
* @return bool true if the email was sent, false if not
function pnMail($to, $subject, $message= '', $headers = '', $html = 0, $debug = 0)
if (empty($to) || !isset ($subject)) {
// set initial return value until we know we have a valid return
// check if the mailer module is availble and if so call the API
$return = pnModAPIFunc('Mailer', 'user', 'sendmessage', array('toaddress' => $to,
* Function that compares the current php version on the
* system with the target one
* Deprecate function reverting to php detecion function
if ($curver >= $minver) {
$ADODB_CACHE_DIR = realpath($GLOBALS['PNConfig']['System']['temp'] . '/adodb');
if ($GLOBALS['PNConfig']['Debug']['sql_adodb']) {
// decode encoded new DB parameters
if ($GLOBALS['PNConfig']['DBInfo'][$key]['encoded']) {
$GLOBALS['PNConfig']['DBInfo'][$key]['dbuname'] = base64_decode($GLOBALS['PNConfig']['DBInfo'][$key]['dbuname']);
$GLOBALS['PNConfig']['DBInfo'][$key]['dbpass'] = base64_decode($GLOBALS['PNConfig']['DBInfo'][$key]['dbpass']);
$GLOBALS['PNConfig']['DBInfo'][$key]['encoded'] = 0;
if ($GLOBALS['PNConfig']['Debug']['debug']) {
$GLOBALS['PNRuntime']['dbg'] = new LensDebug();
$GLOBALS['PNRuntime']['debug_sqlcalls'] = 0;
// initialise time to render
if ($GLOBALS['PNConfig']['Debug']['pagerendertime']) {
$GLOBALS['PNRuntime']['dbg_starttime'] = $mtime[1] + $mtime[0];
* Returns the value of $name from $_SERVER array.
* Accepted values for $name are exactly the ones described by the
* {@link http://www.php.net/manual/en/reserved.variables.html#reserved.variables.server PHP manual}.
* If the server variable doesn't exist void is returned.
* @author Marco Canini <marco@xaraya.com>, Michel Dalle
* @param name string the name of the variable
* @param default the default value to return if the requested param is not set
* @return mixed value of the variable
// Check the relevant superglobals
if (!empty($name) && isset ($_SERVER[$name])) {
return $default; // nothing found -> return default
* Returns the server host name fetched from HTTP headers when possible.
* The host name is in the canonical form (host + : + port) when the port is different than 80.
* @author Marco Canini <marco@xaraya.com>
* @return string HTTP host name
// HTTP_HOST is reliable only for HTTP 1.1
if ($port != '80') $server .= ":$port";
* Get current URI (and optionally add/replace some parameters)
* @param args array additional parameters to be added to/replaced in the URI (e.g. theme, ...)
* @return string current URI
// adapted patch from Chris van de Steeg for IIS
// TODO: please test this :)
if ($pathinfo == $scriptname) {
if (!empty($scriptname)) {
$request = $scriptname . $pathinfo;
if (!empty($querystring)) $request .= '?'. $querystring;
// add optional parameters
if (strpos($request,'?') === false) $request .= '?';
foreach ($args as $k=> $v) {
// TODO: replace in-line here too ?
if (!empty($w)) $request .= $k . "[$l]=$w&";
// if this parameter is already in the query string...
if (preg_match("/(&|\?)($k=[^&]*)/",$request,$matches)) {
// ... replace it in-line if it's not empty
$request = preg_replace("/(&|\?)$find/","$1$k=$v",$request);
// ... or remove it otherwise
} elseif ($matches[1] == '?') {
$request = substr($request, 0, - 1);
* Gets the current protocol
* Returns the HTTP protocol used by current connection, it could be 'http' or 'https'.
* @author Marco Canini <marco@xaraya.com>
* @return string current HTTP protocol
// IIS seems to set HTTPS = off for some reason
return (!empty($HTTPS) && $HTTPS != 'off') ? 'https' : 'http';
* @param args array additional parameters to be added to/replaced in the URL (e.g. theme, ...)
* @return string current URL
* @todo cfr. BaseURI() for other possible ways, or try PHP_SELF
$baseurl = "$protocol://$server";
// adapted patch from Chris van de Steeg for IIS
// TODO: please test this :)
if ($pathinfo == $scriptname) {
if (!empty($scriptname)) {
$request = $scriptname . $pathinfo;
if (!empty($querystring)) $request .= '?'. $querystring;
return $baseurl . $request;
* Decode the path string into a set of variable/value pairs
* available via pnVarCleanFromInput or the object libary
* This API works in conjunction with the new short urls
* system to extract a path based variable set into the Get, Post
* and request superglobals.
* A sample path is /modname/function/var1:value1
* @author Martin Andersen
function pnQueryStringDecode()
// get our base parameters to work out if we need to decode the url
// check if we need to decode the url
(empty($module) && empty($type) && empty($func))) {
// define our site entry points
$root = empty($customentrypoint) ? 'index.php' : $customentrypoint;
$tobestripped = array("/$root", '/admin.php', '/user.php', '/modules.php', '/backend.php', '/print.php',
// get base path to work out our current url
// strip any unwanted content from the provided URL
$path = str_replace($tobestripped, '', $parsedURL['path']);
// split the path into a set of argument strings
// ensure that each argument is properly decoded
foreach ($args as $k => $v) {
// the module is the first argument string
if (isset ($args[1]) && !empty($args[1])) {
// first try the first argument as a module
// if that fails it's a theme
// now shift the vars and continue as before
// even though it's not really a module let the standard module code handle the problem
$modinfo['name'] = $args[1];
if ($modinfo['type'] == 1) {
// the function name is the second argument string
// check if there is a custom url handler for this module
// if not decode the url using the default handler
if (isset ($modinfo) && $modinfo['type'] != 0 && !pnModAPIFunc($modname, 'user', 'decodeurl', array('vars' => $args))) {
// any remaining arguments are specific to the module
$argscount = count($args);
for ($i = 3; $i < $argscount; $i = $i + 2) {
* add a variable/value pair into the query string
* (really the _GET superglobal
* This API also adds the variable to the _REQUEST superglobal for consistentcy
* @return bool true if successful, false otherwise
// add the variable into the get superglobal
$_REQUEST[$name] = $_GET[$name] = $value;
function pnErrorHandler($errno, $errstr, $errfile, $errline, $errcontext)
// check for an @ suppression or E_STRICT errors (we're not php 5 only yet!)
static $errorlog, $errorlogtype, $errordisplay, $pntemp;
if (!isset ($errorlogtype)) {
// What do we want to log?
// 1 - Log real errors only. 2 - Log everything
if ($errorlog == 2 || ($errorlog == 1 &&
($errno != E_WARNING || $errno != E_NOTICE || $errno != E_USER_WARNING || $errno != E_USER_NOTICE))) {
$msg = "Zikula Error: $errstr";
$msg .= " in $errfile on line $errline for page $request";
// log to the system log (default php handling....)
$body = pnModFunc('Errors', 'user', 'system',
array('type' => $errno, 'message' => $errstr, 'file' => $errfile, 'line' => $errline));
array('toaddress' => $toaddress, 'toname' => $toaddress,
// log a module specific log (based on top level module)
error_log($msg. "\r\n", 3, $pntemp . '/error_logs/' . $modname . '.log');
// log to global error log
error_log($msg. "\r\n", 3, $pntemp . '/error_logs/error.log');
// should we display the error to the user
if ($errordisplay == 0) {
// check if we want to flag up warnings and notices
if ($errordisplay == 1 &&
($errno == E_WARNING || $errno == E_NOTICE || $errno == E_USER_WARNING || $errno == E_USER_NOTICE)) {
// clear the output buffer
// display the new output and halt the script
array('type' => $errno, 'message' => $errstr, 'file' => $errfile, 'line' => $errline));
* Gracefully shut down the framework (traps all exit and die calls)
* @param $exit_param params to pass to the exit function
* @return none - function halts execution
// we must deliberately cause the session to close down because if we
// rely on PHP to do so after an exit is called, the framework gets shutdown
// by PHP and no longer functions correctly.
if (empty($exit_param)) {
|