register
[username]

XSLHandler.php

This file (via modrewrite or a custom 404) handles all incoming requests to this website.
It finds an XSL file under the /easyethical folder that can satisfy the request and matches it up with a database stored procedure of the same name that returns the required XML.


<?
/*
Architecturally this file mimicks WebServer functionality
modrewrite based PostGRES -> XML -> XSL loader system
the request_uri is encoded and translated into a DB call for XML data and an XSL file to translate it
every XSL file has a ppp_* SPROC with the same name (including directories) which returns the required XML
for example: a request URI of /example/search?term=harrods translates to
   get the XML data from ppp_example_search($sessionid, $schema, $version, $term, ...)
   get the XSL translation stylesheet from /basesite/example/search.xsl
$sessionid is the PHP session id (sessions are created, stored and deleted by the database)
$schema    is the type of XML schema required (can be controlled with ?schema=<schema name>)
$version   is always 1

.htaccess using Apache mod_rewrite (required):
RewriteEngine on
# if the request is for a file or directory that exists already on the server, index.php isn't served.
# equivalent of using the 404
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# [L] = last, do not apply any more Rewrite rules
RewriteRule . /XSLHandler.php [L]

Failover for opening the XSL transform file
/companies/harrods?schema=minimal
   try to open /basesite/companies/harrods as a directory (fail)
   try to open /basesite/companies/harrods/index.xsl if was a directory
   try to open /basesite/companies/harrods.xsl (fail)
   try to open /basesite/companies/all.xsl?term=harrods&schema=minimal

System can return special XML indicators to the XSL:
    <root><noproc></root> means that the ppp_* DB procedure was not found (non dynamic pages)
    <root><noxml></root>  means that the ppp_* DB procedure returned null or zero string (can happen with invalid ids for objects in the DB)
*/

//system wide implementation specific variables like development mode
ini_set('include_path', ini_get('include_path') . ':' . dirname(__FILE__) . '/includes/pear' . ':' . dirname(__FILE__) . '/includes');
require("define.php");
date_default_timezone_set('UTC');

//these are external required dependencies
//part of the functionality of this loader system
require_once('includes/phpsniff/phpSniff.class.php');
require_once('includes/email.php');
require_once('includes/recaptchalib.php'); //http://recaptcha.net/plugins/php/ (anewholm/fryace4)
require_once('includes/flickr/functions.php');

//these XML namespaces are built into and required by the system
define('NAMESPACE_DIRECTIVES', 'http://www.xsearchservices.com/namespaces/directives');
define('NAMESPACE_SENDMAIL',   'http://www.xsearchservices.com/namespaces/sendmail');
define('NAMESPACE_RSS',        'http://www.xsearchservices.com/namespaces/rss');
define('NAMESPACE_KML',        'http://earth.google.com/kml/2.0');
define('XML_DECL', "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");

//base header and session setup
header("Pragma: no-cache", true);
header('Content-type: text/html; charset=utf-8'); //default content-type (changed below sometimes)

//------------------------------------------- inputs ---------------------------------------------------
//GoogleBot useragents
// * Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
// * Googlebot/2.1 (+http://www.googlebot.com/bot.html)
// * Googlebot/2.1 (+http://www.google.com/bot.html)
$client        = & new phpSniff();
$is_googlebot  = preg_match('/googlebot/i', $client->property('ua'));
$uri_request   = utf8_encode(urldecode($_SERVER['REQUEST_URI'])); //can include utf-8 characters (e.g. Nestlé)
$remote_addr   = $_SERVER["REMOTE_ADDR"];
$user_agent    = $_SERVER['HTTP_USER_AGENT'];
$referer       = $_SERVER['HTTP_REFERER'];
$domain        = $_SERVER['HTTP_HOST'];
$domainstem    = domainstem($domain); //e.g. carrotmobuk instead of www.carrotmobuk.org
$thisdir       = dirname(__FILE__);
$version       = 1;
$schema        = 'standard';
$clientXSLT    = false;
$debug         = '';
session_start();
$session       = session_id();

//work on a directory array so that we can pop and shift easily
$uri_parts   = explode('?', $uri_request);
$uri_path    = str_replace('//', '/', strtolower($uri_parts[0]));
$uri_query   = $uri_parts[1];
$args        = array();
$argso       = array();

//------------------------------------------- virtual hosts ---------------------------------------------------
//virtual hosts setup the recaptcha keys, site root directory and any site dependent variables
//beyond this system
$virtualhostfile = "$thisdir/virtualhosts/$domainstem.php";
if (is_file($virtualhostfile)) include($virtualhostfile);
if (!$siteroot  ) $siteroot   = $domainstem;
if (!$procprefix) $procprefix = $siteroot.'_'; //network/site/mySite -> ppp_network_site_mySite_*

//------------------------------------------- db connection ---------------------------------------------------
//can be defined in the virtual host file
if (defined('LINK_BASE'))     $link_base     = LINK_BASE;
if (defined('RESOURCE_BASE')) $resource_base = RESOURCE_BASE;
if (defined('DBHOST'))        $dbhost        = DBHOST;
if (defined('DBNAME'))        $dbname        = DBNAME;
if (defined('DBUSER'))        $dbuser        = DBUSER;
if (defined('DBPWD'))         $password      = DBPWD;
//query string overrides
if ($_GET['DBHOST'])          $dbhost        = $_GET['DBHOST'];
if ($_GET['DBNAME'])          $dbname        = $_GET['DBNAME'];
if ($_GET['DBUSER'])          $dbuser        = $_GET['DBUSER'];
if ($_GET['DBPWD'])           $password      = $_GET['DBPWD'];

//------------------------------------------- administrator monitoring ---------------------------------------------------
if (GOOGLED_EMAILS && $is_googlebot && $uri_path == '/') sendEmail('annesley.newholm@gmail.com', 'admin@xsearchservices.com', 'Googled:'.$domainstem, $user_agent, $user_agent);
if (LOG404) $log = fopen('404log.txt', 'a');
log404("-------------------------------- $uri_path\n");

//debug
if (DEBUG) {
    $debug .= "################## defines\n";
    $debug .= "DEVSERVER: ".DEVSERVER."\n";
    $debug .= "DEBUG: ".DEBUG."\n";
    $debug .= "LOG404: ".LOG404."\n";
    $debug .= "FORCE_SERVER_XSLT: ".FORCE_SERVER_XSLT."\n";
    $debug .= "DBHOST: $dbhost\n";
    $debug .= "DBNAME: $dbname\n";
    $debug .= "DBUSER: $dbuser\n";

    $debug .= "################## php.ini\n";
    $debug .= "magic_quotes_gpc: ".ini_get('magic_quotes_gpc')."\n"; //handled manually by the DB engine

    $debug .= "################## inputs\n";
    if (is_file("$siteroot/define.php")) $debug .= "(using site specific define)\n";
    foreach($HTTP_POST_VARS as $k => $v) $debug .= "POST: $k=$v\n";
    $debug .= "uri_request: $uri_request\n";
    $debug .= "user_agent: $user_agent\n";
    $debug .= "domainstem: $domainstem\n";
    $debug .= "thisdir: $thisdir\n";
    $debug .= "virtualhostfile: $virtualhostfile\n";
    $debug .= "siteroot: $siteroot (".is_dir($siteroot).")\n";
    $debug .= "procprefix: $procprefix\n";
    $debug .= "################## path\n";
    $debug .= "uri_path: $uri_path\n";
    $debug .= "uri_query: $uri_query\n";
    $debug .= "link_base: $link_base\n";
    $debug .= "resource_base: $resource_base\n";
}

//------------------------------------------- find directives in URI ---------------------------------------------------
$dirs        = explode('/', $uri_path);
array_shift($dirs);                                  //remove initial /
if ($dirs[count($dirs) - 1] == '') array_pop($dirs); //remove trailing / if there

//format requests (.json)
//must always be on the last part of the url
$dir_last    = array_pop($dirs);
$file_parts  = explode('.', $dir_last);
if (count($file_parts) > 1) $extension = array_pop($file_parts);
array_push($dirs, implode($file_parts));

//directives
$dir_first   = $dirs[0];
$dir_second  = $dirs[1];
$api_mode    = ($dir_first == 'api');
$has_directive = ($api_mode);           //for more directives
if ($has_directive) array_shift($dirs); //remove initial element

//------------------------------------------- find files or directories ---------------------------------------------------
//we do not care if uri looks like a file or a directory
//just check to see which it is
$localpath   = "$thisdir/$siteroot/".implode('/', $dirs);
$localxsl    = "$localpath.xsl";
$havefile    = is_file($localxsl);

//look for index files and all files
if ($havefile) {
    $filename  = array_pop($dirs);
    $localpath = "$thisdir/$siteroot/".implode('/', $dirs).'/';
} else {
    $is_dir  = is_dir($localpath);
    if ($is_dir) {
        //uri points directly to an existing directory
        //may need to redirect to place a / on the end if there is not one
        //otherwise relative links will not work
        //not using relative links in this site
        /*
        if ($uri_path... == '') {
            header("Location: $localpath/", true);
            header("HTTP/1.1 302 Moved", true, 302);
            exit();
        };
        */
        $localpath      = "$localpath/";
        $localxsl       = $localpath.'index.xsl';
        $filename       = 'index';
        $havefile       = is_file($localxsl);
    } else {
        //not a valid xsl file or directory
        //pop the last part and send it as a parameter
        $directive_last = array_pop($dirs);
        array_unshift($args, $directive_last);
        $localpath      = "$thisdir/$siteroot/".implode('/', $dirs).'/';
        $localxsl       = $localpath.'all.xsl';
        $filename       = 'all';
        $havefile       = is_file($localxsl);
    }
}

//debug
if (DEBUG) {
    $debug .= "################## inputs\n";
    $debug .= "api_mode: $api_mode\n";
    $debug .= "localpath: $localpath\n";
    $debug .= "filename: $filename\n";
    $debug .= "localxsl: $localxsl\n";
    $debug .= "localphp: $localphp\n";
    $debug .= "extension: $extension\n";
    $debug .= 'havefile: '.($havefile?'true':'false')."\n";
    $debug .= "dir_first: $dir_first\n";
    $debug .= "dir_second: $dir_second\n";
    $debug .= "directive_last: $directive_last\n";
    $debug .= "uri_xslpath: $uri_xslpath\n";
}

//------------------------------------------- xsl file directives ---------------------------------------------------
//our xsl files can contain more language constructs that control the server
//load the found xsl file here because it may contain special directives
//these directives should all be in the http://www.xsearchservices.com/namespaces/directives namespace
//MIMEType:       set an alternate MIMEType for this standard transform
//transform:      server or auto
//proc:           procedure name (ppp_*), none or auto
//schema:         comma delimited controls for turning off parts of the XML to wrap around the proc response: 
//                   noparams, noclient, noextinfo, nopathinfo, baresession, barexml, etc.
//sendmail:       parse - instruction to parse the resultant XML for diretives to send an email
//porder:         explicitly state the order of the parameters
//jsdocwrite:     page uses document.write during parse (not compatible with FF client XSLT)
//setcookies:     which cookies to query and provide as parameters and in the XML
//getcookies:     which cookies to set
//referal:        do not include this page in the last referer list (take the one from the session)
//link_base:      the base domain for all a hrefs
//resource_base:  the base for all resources (using a base tag)
//compatability:  uses different file according to certain named condition sets like 'notIE6'
//redirect:       immediate redirect to another page (no show)
//execute:        execute another xsl file instead (server.execute)

$xslDoc = new DOMDocument('1.0', 'utf-8'); //some of the plugins check the XSL file for directives
if ($havefile) {
    $xslDoc->load($localxsl);
    $xsldir_startpoint = $xslDoc;
    $xsldir_block      = $xslDoc->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'block');
    if ($xsldir_block->length) $xsldir_startpoint = $xsldir_block->item(0);
    if (!($xsldir_mimetype      = $_GET['dir:MIMEType']))      $xsldir_mimetype      = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'MIMEType'  ));
    if (!($xsldir_transform     = $_GET['dir:transform']))     $xsldir_transform     = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'transform' ));
    if (!($xsldir_proc          = $_GET['dir:proc']))          $xsldir_proc          = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'proc'      ));
    if (!($xsldir_schema        = $_GET['dir:schema']))        $xsldir_schema        = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'schema'    ));
    if (!($xsldir_sendmail      = $_GET['dir:sendmail']))      $xsldir_sendmail      = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'sendmail'  ));
    if (!($xsldir_porder        = $_GET['dir:porder']))        $xsldir_porder        = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'porder'    ));
    if (!($xsldir_jsdocwrite    = $_GET['dir:jsdocwrite']))    $xsldir_jsdocwrite    = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'jsdocwrite'));
    if (!($xsldir_getcookies    = $_GET['dir:getcookies']))    $xsldir_getcookies    = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'getcookies'));
    if (!($xsldir_setcookies    = $_GET['dir:setcookies']))    $xsldir_setcookies    = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'setcookies'));
    if (!($xsldir_referal       = $_GET['dir:referal']))       $xsldir_referal       = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'referal'));
    if (!($xsldir_link_base     = $_GET['dir:link_base']))     $xsldir_link_base     = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'link_base'));
    if ($xsldir_link_base)      $link_base = $xsldir_link_base;
    if (!($xsldir_resource_base = $_GET['dir:resource_base'])) $xsldir_resource_base = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'resource_base'));
    if ($xsldir_resource_base)  $resource_base = $xsldir_resource_base;
    if (!($xsldir_compatability = $_GET['dir:compatability'])) $xsldir_compatability = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'compatability'));
    if (!($xsldir_redirect      = $_GET['dir:redirect']))      $xsldir_redirect      = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'redirect'));
    if (!($xsldir_execute       = $_GET['dir:execute']))       $xsldir_execute       = firstitemtext($xsldir_startpoint->getElementsByTagNameNS(NAMESPACE_DIRECTIVES, 'execute'));

    if (DEBUG) {
        $debug .= "################## directives\n";
        if ($xsldir_block->length) $debug .= "(USING dir:block)\n";
        $debug .= "forceMIMEType: $forceMIMEType\n";
        $debug .= "xsldir_mimetype: $xsldir_mimetype\n";
        $debug .= "xsldir_transform: $xsldir_transform\n";
        $debug .= "xsldir_proc: $xsldir_proc\n";
        $debug .= "xsldir_schema: $xsldir_schema\n";
        $debug .= "xsldir_sendmail: $xsldir_sendmail\n";
        $debug .= "xsldir_porder: $xsldir_porder\n";
        $debug .= "xsldir_jsdocwrite: $xsldir_jsdocwrite\n";
        $debug .= "xsldir_getcookies: $xsldir_getcookies\n";
        $debug .= "xsldir_setcookies: $xsldir_setcookies\n";
        $debug .= "xsldir_referal: $xsldir_referal\n";
        $debug .= "xsldir_link_base: $xsldir_link_base\n";
        $debug .= "xsldir_resource_base: $xsldir_resource_base\n";
        $debug .= "xsldir_compatability: $xsldir_compatability\n";
        $debug .= "xsldir_redirect: $xsldir_redirect\n";
        $debug .= "xsldir_execute: $xsldir_execute\n";
    }
}

//------------------------------------------- execute ---------------------------------------------------
//load other file
if ($xsldir_execute) {
    $dirs           = explode('/', $xsldir_execute);
    $filename       = array_pop($dirs);
    $localpath      = "$thisdir/$siteroot/".implode('/', $dirs).'/';
    $localxsl       = "$localpath$filename.xsl";
    $havefile       = is_file($localxsl);
    if ($havefile) $xslDoc->load($localxsl);

    $debug .= "################ xsldir_execute:\n";
    $debug .= "filename: $filename\n";
    $debug .= "localpath: $localpath\n";
    $debug .= "localxsl: $localxsl\n";
    $debug .= 'havefile: '.($havefile?'true':'false')."\n";
    $debug .= "\n";
}

//------------------------------------------- redirect ---------------------------------------------------
//directly return a 302 - resource moved
if ($xsldir_redirect) {
    header("HTTP/1.1 302 Moved", true, 302);
    header("Location: $xsldir_redirect", true);
    exit();
}

//------------------------------------------- referer re-process ---------------------------------------------------
$referer = $_SESSION['referer'];
if ($xsldir_referal != 'exclude') $_SESSION['referer'] = $uri_request;

//------------------------------------------- browser compatability ---------------------------------------------------
if ($xsldir_compatability == 'notIE6' && $client->property('browser') == 'ie' and $client->property('maj_ver') == 6) {
    $filename       = $xsldir_compatability;
    $localpath      = "$thisdir/$siteroot/".implode('/', $dirs).'/';
    $localxsl       = "$localpath$xsldir_compatability.xsl";
    $havefile       = is_file($localxsl);
    if ($havefile) $xslDoc->load($localxsl);
}

//------------------------------------------- query args ---------------------------------------------------
//done here because the ordering may be affected by the directives
//system uses POST *or* GET. There are extra GET options to control the system
//the order of arguments is important for the procedure
//note that $_GET is lost because the origonal query is held in $_SERVER[REQUEST_URI]
//we need to ignore the PHPSESSID that can be added automatically to pass session in some PHP setups
$queryargs   = explode('&', $uri_query);
if ($xsldir_porder) {
    //a specific ordering and/or selection has been requested
    //comma delimited form names
    $ordering = explode(',', $xsldir_porder);
    foreach($ordering as $key) {
        $key   = trim($key);
        $value = $_POST[$key];
        if (is_array($value)) $value = implode(',', $value);
        array_push($args, $value);
        $argso[$key] = $value;
    }
} else {
    foreach($_POST as $key => $value) {
        if (is_array($value)) $value = implode(',', $value); //PHP auto-creates arrays for field[] names
        if ($key != 'PHPSESSID') {
            array_push($args, $value);
            $argso[$key] = $value;
        }
    }
    foreach($queryargs as $nv) {
        $nva = explode('=', $nv);
        if (count($nva) == 2) {
            $key   = $nva[0];
            $value = $nva[1];
            if ($key != 'PHPSESSID') {
                array_push($args, $value);
                $argso[$key] = $value;
            }
        }
    }
}
$submission  = $argso['submit'];
$customxml   = '';
$headxml     = '';
$cookiexml   = '';
$xml         = '<root/>';
$statuses    = '';

//------------------------------------------- cookies (XSL directives) ---------------------------------------------------
if ($xsldir_getcookies) {
    $cookiexml  = '<cookies>';
    foreach(explode(',', $xsldir_getcookies) as $name) {
        $value      = $_COOKIE[$name];
        $name_esc   = escapeForElementName($name);
        $cookiexml .= "<$name_esc><![CDATA[".escapeForCDATA($value)."]]></$name_esc>";
        array_push($args, $value);
        $argso[$name] = $value;
        $debug .= "got [$name] cookie = [$value]\n";
    }
    $cookiexml .= '</cookies>';
}
if ($xsldir_setcookies) {
    foreach(explode(',', $xsldir_setcookies) as $key) {
        $namevalue  = explode('=', $key);
        $name       = $namevalue[0];
        $value      = $namevalue[1];
        $debug .= "setting [$name] cookie to [$value]\n";
        if (!setcookie($name, $value)) $debug .= '*** FAILED ***';
    }
}

//------------------------------------------- plugins pre ---------------------------------------------------
//all connections are the same: use one persistent connection
if ($dbhost && $dbname)
    $conn = pg_connect("host=$dbhost dbname=$dbname user=$dbuser password=$password");
require_once('includes/404plugins/404plugins_pre.php');

//------------------------------------------- 404 handling ---------------------------------------------------
//note that the plugins may change $havefile and $proc in order to use a special XSL and PROC, e.g. CMS
if (!$havefile) {
    if (is_dir($siteroot)) {
        //look for 404 handlers
        header("HTTP/1.1 404 Not Found", true, 404);
        if (is_file("$siteroot/404.xsl")) {
            $havefile = true;
            $filename = '404';
            $dirs     = array();
            $xslDoc->load("$siteroot/404.xsl");
        } else {
            if (is_file('404.html')) print(file_get_contents('404.html'));
            else print('Page Not Found (404)');
        }
    } else {
        //the actual site root is not found, thus is an invalid sub-domain
        header("HTTP/1.1 404 Not Found", true, 404);
        if (is_file("nosite.html")) {
            print(file_get_contents('nosite.html'));
        } else print('Website Not Found (404)');
    }
}

//------------------------------------------- data retrieval ---------------------------------------------------
if ($havefile && $conn) {
    //------------------------------------------- call proc in DB ---------------------------------------------------
    //construct proc name, connect and query
    $user_agent_esc = pg_escape_string($user_agent);
    $session_esc    = pg_escape_string($session);
	if ($xsldir_proc == 'none') {
        $statuses .= '<noproc/>';
        $xml = '<root />';
	} else {
	    if ($xsldir_proc == '' || $xsldir_proc == 'auto') {
	        $proc = "ppp_$procprefix".implode('_', $dirs);
	        if (count($dirs) && $dirs[0] != '') $proc .= '_'; //need to account for a blank root directory call
	        $proc .= $filename;
	    } else $proc = $xsldir_proc;
        //get the argument types for the call
	    $sql  = "select proargtypes from pg_proc where proname = '$proc'";
	    $rset = pg_query($conn, $sql);
	    $sargs = pg_fetch_result($rset, 'proargtypes');
        if ($sargs == null) {
            $statuses .= '<noproc/>';
        } else {
            $arg_types = explode(' ', $sargs);
	        pg_free_result($rset);
	        //assemble DB call
	        $sql  = "select $proc('$session_esc'::text, '$schema'::text, $version";
	        $count_args = count($arg_types);
	        for($i = 3; $i < $count_args; $i++) {
	            $arg = $args[$i-3];
	            switch ($arg_types[$i]) {
	                case 16: { //bool
	                    $sql .= ', '.($arg == 'on' || $arg === true || $arg == 'true'? 'true' : 'false');
	                    break;
	                }
	                case 23: { //number
	                    $arg = (int)$arg;
	                    $sql .= ", $arg::integer";
	                    break;
	                }
	                case 25:   //text: note that magic_quotes should be Off because we bring in items from custom PHP files
	                default: {
	                    $sql .= ", '".pg_escape_string($arg)."'::text";
	                    break;
	                }
	            }
	        }
	        //make the call
            $sql .= ') as xml';
	        $rset = pg_query($conn, $sql);
            $xml  = pg_fetch_result($rset, 'xml');
            if ($xml == null || $xml == '') $statuses .= '<noxml/>';
	        pg_free_result($rset);
        }
        if (DEBUG) {
            $debug .= "################## sql\n";
            $debug .= "submission: $submission (".($submission ? 'true' : 'false').")\n";
            $debug .= "proc: $proc\n";
            $debug .= "$count_args proc arguments (including session \$1, schema \$2, version \$3 -> \$$count_args)\n";
            if ($submission) {
                if ($submission == $args[$count_args-4]) $debug .= "successful submit [submit] == [last]\n";
                else                                     $debug .= "******************************* failed attempted submission [submit] != [last]\n";
            }
            $debug .= "argso (pre-plugins bare form inputs):\n";
            foreach($argso as $k => $v) $debug .= "  $k= $v\n";
            $debug .= "args (post-plugins with geo, recaptcha etc.):\n";
            $debug .= "  \$1 session, \$2 schema, \$3 version\n";
            foreach($args as $k => $v) $debug .= "  \$".($k+4)."($k)= $v\n";
            if ($k+4 < $count_args) $debug .= "  (arguments unaccounted for up to \$$count_args)\n";
            $debug .= "sql: $sql\n";
        }
    }
    //$xml  = utf8_encode($xml); //DB outputs utf-8

    //------------------------------------------- session ---------------------------------------------------
    //done after proc in case it affects the session information
    //includes any session information like login status into the response xml
    $session_schema = '';
    $sql  = "select proargtypes from pg_proc where proname = 'ppp_$procprefix"."session'";
    $rset = pg_query($conn, $sql);
    $sargs = pg_fetch_result($rset, 'proargtypes');
    if ($sargs == null) {
    	  $sessionxml = '<nosession />';
    } else {
	     pg_free_result($rset);
	     if (stripos($xsldir_schema, 'baresession') !== FALSE) $session_schema = 'bare';
	     $rset           = pg_query($conn, "select ppp_$procprefix"."session('$session_esc', '$session_schema', 1, '$siteroot', '$user_agent_esc')");
	     $sessionxml     = pg_fetch_result($rset, 0);
	     pg_free_result($rset);
	 }

    //------------------------------------------- plugins post ---------------------------------------------------
    require_once('includes/404plugins/404plugins_post.php');

    //------------------------------------------- base xml details? ---------------------------------------------------
    //this is extra server information passed to the HTML webpage via the XML
    //for example:
    // * availability of an RSS transform for this file
    // * time and date
    // * parameters passed in
    //it is done here rather than being passed through to the SPROC and out again because there may not be a proc
    //the API transforms will usually remove this and return only the <procxml> node
    //note that $procxml will have a single root element so that it is also standalone
    $relativedirectory = implode('/', $dirs);
    $relativepath      = "$relativedirectory/$filename";
    $relativequery     = '';
    $responsexml = '<response transform=""'; //note that the transform attribute is filled out later
        $responsexml .= ' xmlns:dir="'.NAMESPACE_DIRECTIVES.'"';
        $responsexml .= ' xmlns:sendmail="'.NAMESPACE_SENDMAIL.'"';
        $responsexml .= ' xmlns:rss="'.NAMESPACE_RSS.'"';
        $responsexml .= '>';
        $responsexml .= '<request>';
            if (stripos($xsldir_schema, 'noclient') === FALSE) {
                $responsexml .= '<client>';
                    if ($is_googlebot) $browser = 'googlebot';
                    else               $browser = $client->property('browser');
                    $responsexml .= '<browser><![CDATA['.escapeForCDATA($browser).']]></browser>';
                    $responsexml .= '<version>
                            <major><![CDATA['.escapeForCDATA($client->property('maj_ver')).']]></major>
                            <minor><![CDATA['.escapeForCDATA($client->property('min_ver')).']]></minor>
                        </version>';
                    $responsexml .= '<platform><![CDATA['.escapeForCDATA($client->property('platform')).']]></platform>';
                    $responsexml .= '<os><![CDATA['.escapeForCDATA($client->property('os')).']]></os>';
                $responsexml .= '</client>';
            }
            $responsexml .= $recaptchaxml;
            $responsexml .= $geoxml;
            if (stripos($xsldir_schema, 'noparams') === FALSE) {
                $responsexml .= '<params>';
                    if (count($args)) {
                        foreach ($args as $name => $value) {
                            $relativequery .= "$name=$value&amp;";
                            $responsexml .= "<p index=\"$name\"><![CDATA[$value]]></p>";
                        }
                        $relativequery = '?'.substr($relativequery, 0, -5);
                    }
                $responsexml .= '</params>';
            }
            $responsexml .= $cookiexml;
            if ($submission) $responsexml .= "<submit>$submission</submit>"; //indicates a form submission (always name submit buttons submit!)
            $responsexml .= '<server>';
                if ($link_base || $resource_base) {
                    $responsexml .= '<distributed>';
                        if ($link_base)     $responsexml .= '<link_base><![CDATA['.escapeForCDATA($link_base).']]></link_base>';
                        if ($resource_base) $responsexml .= '<resource_base><![CDATA['.escapeForCDATA($resource_base).']]></resource_base>';
                    $responsexml .= '</distributed>';
                }
                if (defined('GOOGLEAPIKEY')) $responsexml .= '<google_api><![CDATA['.GOOGLEAPIKEY.']]></google_api>';
                if ($email_result) $responsexml .= '<sendmail:result><![CDATA['.escapeForCDATA($email_result).']]></sendmail:result>';
                $responsexml .= $sessionxml;
                if (stripos($xsldir_schema, 'noextinfo') === FALSE) {
                    $responsexml .= "<referer><![CDATA[$referer]]></referer>";
                    $responsexml .= datexml(time());
                    $responsexml .= "<domain><![CDATA[$domain]]></domain>";
                    $responsexml .= '<stage>'.(DEVSERVER?'dev':'live').'</stage>';
                    $responsexml .= "<siteroot><![CDATA[$siteroot]]></siteroot>";
                    $responsexml .= "<domainstem><![CDATA[$domainstem]]></domainstem>";
                }
                if (stripos($xsldir_schema, 'nopathinfo') === FALSE) {
                    $responsexml .= '<pathinfo>';
                        $responsexml .= '<relativepath><![CDATA['.escapeForCDATA($relativepath).']]></relativepath>';
                        $responsexml .= '<relativedirectory><![CDATA['.escapeForCDATA($relativedirectory).']]></relativedirectory>';
                        $responsexml .= '<relativequery><![CDATA['.escapeForCDATA($relativequery).']]></relativequery>';
                    $responsexml .= '</pathinfo>';
                }
            $responsexml .= '</server>';
        $responsexml .= '</request>';
        if ($headxml)   $responsexml .= "<head>$headxml</head>";
        if ($customxml) $responsexml .= "<custom>$customxml</custom>";
        if ($statuses)  $responsexml .= "<status>$statuses</status>";
        $responsexml .= "<procxml>$xml</procxml>";
    $responsexml .= '</response>';

	if ($api_mode && $extension != 'html') {
        //------------------------------------------- api mode ---------------------------------------------------
        //this is always a server transform
        //formatting (based on extension) only allowed in api mode (otherwise ignored)
        //thus, the main xsl file is now ignored and an xsl file capable of transforming the xml
        //into the required format is looked for
        //essentially the main xsl file was really a placeholder to say that the function (and ppp_*) is available
        if (!$extension) $extension = 'xml';
        $responsexml = str_replace(' transform=""', ' transform="apimode"', $responsexml);
        $xmlDoc = new DOMDocument('1.0', 'utf-8');
        $xmlDoc->loadXML(&$responsexml);
        $xslDoc = new DOMDocument('1.0', 'utf-8');
        $transform_local    = "$localpath$filename"."_$extension.xsl"; //e.g. all_json.xsl custom transform
        $transform_standard = "$thisdir/formats/$extension.xsl";
        $loaded = false;
        if (is_file($transform_local)) $loaded = $xslDoc->load($transform_local);
        else if (is_file($transform_standard)) $loaded = $xslDoc->load($transform_standard);

        if ($loaded) {
            //work out the relevant content-type for the result
            //the format XSL file can contain the MIMEType that it creates:
            $mimetype = firstitemtext($xslDoc->getelementsByTagNameNS(NAMESPACE_DIRECTIVES, 'MIMEType'));
            if (!$mimetype) {
                switch ($extension) {
                    //standard types:
                    case 'xml':  {$mimetype = 'text/xml'; break;} //just the <procxml> element
                    case 'fxml': {$mimetype = 'text/xml'; break;} //full response
                    //case 'rss':  {$mimetype = 'text/rss+xml'; break;}
                    case 'rss':  {$mimetype = 'application/xhtml+xml'; break;}
                    case 'json': {$mimetype = 'application/json'; break;}
                    case 'js':   {$mimetype = 'application/x-javascript'; break;}
                    default:     {$mimetype = 'text/html'; break;}
  	            }
  	        }
            header("Content-Type: $mimetype; charset=utf-8", true);

            $oXSLT  = new XSLTProcessor();
            $oXSLT->importStyleSheet($xslDoc);
            $output = $oXSLT->transformToXML($xmlDoc);
        } else {
            //show error if can not find handler
  	        header("Content-Type: text/html; charset=utf-8", true);
	        $output = "cannot find a handler for [$extension]";
        }
        if (DEBUG) {
            $debug .= "################## api\n";
            $debug .= "transform_local: $transform_local\n";
            $debug .= "transform_standard: $transform_standard\n";
            $debug .= "loaded: $loaded\n";
        }
	} else {
        //------------------------------------------- normal HTML conditional output ---------------------------------------------------
        if (stripos($xsldir_schema, 'barexml') !== FALSE) $responsexml = $xml;
	    //client-server switch
	    if ($client->has_feature('xml')             //browser sniff: can process XSLT
	        && $xsldir_transform != 'server'        //explicit server transform request by the XSL DIR
	        && !$is_googlebot                       //googlebot masquerades as Firefox
	        && !$xsldir_jsdocwrite                  //page uses document.write during parse (not compatible with FF client XSLT)
	        && !FORCE_SERVER_XSLT                   //define.php
	        && $extension != 'html'                 //special request for a server side transform
	    ) {
		    //CLIENT transform - indicate MIME type and disable cacheing
		    $clientXSLT   = true;
		    $responsexml = str_replace(' transform=""', ' transform="client"', $responsexml);
		    header("Content-Type: application/xml; charset=utf-8", true);
		    $uri_xslpath  = "$resource_base/$siteroot/".implode('/', $dirs);
	        if (count($dirs) && $dirs[0] != '') $uri_xslpath .= '/'; //need to account for a blank root directory call
	        $uri_xslpath .= "$filename.xsl";
		    $xml_ss       = "<?xml-stylesheet charset=\"utf-8\" type=\"text/xsl\" href=\"$uri_xslpath\"?>\n";
		    //the xml decleration overrides the MIMEType in Firefox
		    //so when we are trying to force the MIMEType, remove the xml decl
		    if ($xsldir_mimetype) $output = $responsexml;
		    else $output  = XML_DECL.$xml_ss.$responsexml;
            if (DEBUG) {
                $debug   .= "################## output (client side transform)\n";
                $debug   .= "uri_xslpath: $uri_xslpath\n";
            }
	    } else {
	        //SERVER transform (can be requested by using a .html extension without the api)
	        //the xsl file is loaded already because the directives needed to be read
		    $responsexml = str_replace(' transform=""', ' transform="server"', $responsexml);
		    header("Content-Type: text/html; charset=utf-8", true);
	        $xmlDoc = new DOMDocument('1.0', 'utf-8');
	        $xmlDoc->loadXML(&$responsexml);
            $oxslt  = new XSLTProcessor();
            $oxslt->importStyleSheet($xslDoc);
	        $output = $oxslt->transformToXML($xmlDoc);
            if (DEBUG) {
                $debug .= "################## output (server side transform)\n";
            }
	    }
	}

	//output
	header("HTTP/1.1 200 OK", true, 200);
	if ($xsldir_mimetype) header("Content-Type: $xsldir_mimetype; charset=utf-8", true);
	$forceMIMEType = $argso['forceMIMEType'];
	if ($forceMIMEType) header("Content-Type: $forceMIMEType; charset=utf-8", true);
	print($output);
}

//------------------------------------------- debug ---------------------------------------------------
if (DEBUG && (!$extension || $extension == 'php')) {
    print("\n<!-- \n");
    print($debug);
    print(' -->');
}
log404($debug);
log404("\n\n");
if (LOG404) fclose($log);



//------------------------------------------- functions ---------------------------------------------------
function log404($string) {
    global $log;
    if (LOG404) fwrite($log, $string);
}

function escapeForCDATA($in) {
    return preg_replace('/&(?!amp;)/', '&amp;', str_replace(']]>', ' ', $in));
}
function escapeForElementName($in) {
    return preg_replace('/[^a-zA-Z_]/i', '', $in);
}
function escapeForAttributeValue($in) {
    return preg_replace('/&(?!amp;)/', '&amp;', $in);
}

function domainstem($domain) {
    return preg_replace('/^www\.|\.org$|\.co\.uk$|\.com$|\.dev$|\.net$|\.org\.uk|\.dev2|\.eval$/i', '', $domain);
}

function firstitemtext($nodelist) {
    $text = NULL;
    if (is_object($nodelist) && $nodelist->length > 0) {
        $firstnode = $nodelist->item(0);
        $text      = $firstnode->textContent;
    }
    return $text;
}

function datexml($d) {
    return  '<date RFC2822="'.date('r', $d).'" ISO8601="'.date('c', $d).'" year="'.date('o', $d).'" month="'.date('m', $d).'" monthname="'.date('F', $d).'" date="'.date('d', $d).'" day="'.date('l', $d).'" hour="'.date('H', $d).'" minute="'.date('i', $d).'" second="'.date('s', $d).'" tz="'.date('O', $d).'"><![CDATA['.date('c', $d).']]></date>';
}

//This function exploits preg_match behaviour (since PHP 4.3.5) when used
//with the u modifier, as a fast way to find invalid UTF-8. When the matched
//string contains an invalid byte sequence, it will fail silently.
function validate_utf8($text) {
  return (strlen($text) == 0 ? true : (preg_match('/^./us', $text) == 1) );
}
?>