<?php
define('idDiGIR_DiGIR','$Id: DiGIR.php,v 1.14 2004/05/18 17:51:28 rdg Exp $');
//Script will exit immediately if idDIGIR_VERSION_REQUEST is defined, which means
//that the script has been loaded only to extract version information that is
//defined by the idDIGIR_xxx defines at the top of each file.
if (defined('idDIGIR_VERSION_REQUEST'))
    return;
/**
$Id: DiGIR.php,v 1.14 2004/05/18 17:51:28 rdg Exp $

DiGIR.php is the primary mechanism for invoking the DiGIR provider

== License ==
Copyright (c) 2002 The University of Kansas Natural History Museum and
Biodiversity Research Center.  All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
with the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

*  Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimers.

* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.

* Neither the names of The University of Kansas Natural History Museum and
Biodiversity Research Center, The University of Kansas at Lawrence, nor the
names of its contributors may be used to endorse or promote products derived
from this Software without specific prior written permission.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

///////////////////////////////////////////////////////////////////////////
/**
* Initialize the parameter array.  The parameter array is used to control the 
* operation and response of the DiGIR provider.  The array is populated by 
* loading key+value pairs from the URL, from POSTed values, or by interpreting
* the DiGIR request document.
* @param $params array Reference to operation parameters to initialize
*/
function intializeParams(&$params)
{
    $params['operation'] = 'metadata';
    $params['filter'] = '';
    $params['resource'] = '';
    $params['startrec'] = 0;
    //$params['maxrecs'] = DIGIR_DEFAULT_PAGESIZE;
    //this value is set within get content to the default for the resource
    //if it is less than zero (ie, unchanged by the request)
    $params['maxrecs'] = -1;
    $params['clientkey'] = '';
    $params['recordstruct'] = '';;
    $params['sortstruct'] = '';
    $params['countrecs'] = false;
    $params['getrecords'] = true;	
    $params['sort'] = array(NULL,'ASC');
    $params['clientid'] = $_SERVER['REMOTE_ADDR'];
    $params['requestnamespaces'] = array();
    $params['cache'] = DIGIR_USE_CACHE;
    $params['resourceconfigfile'] = null;
}

/**
* Loads the parameter values from key+value pairs provided in the URL or
* POSTed data.  POST values take precedence.
* @param $params array Reference to a parameter array to load.
*/
function loadParamsNVPairs(&$params)
{
	$params["operation"] = getVar("operation",$params["operation"]);
	
	//scan is now called "inventory"
	if (strcasecmp($params['operation'],'scan') == 0)
	   $params['operation'] = 'inventory';
	
	$params["filter"] = stripcslashes(getVar("filter",$params["filter"]));
	$params["resource"] = getVar("resource",$params["resource"]);
	$params["startrec"] = getVar("startrec",$params["startrec"]);
	$params["maxrecs"] = getVar("maxrecs",$params["maxrecs"]);
	$params["clientkey"] = getVar("key",$params["clientkey"]);
	$params["recordstruct"] = stripcslashes(getVar("format",$params["recordstruct"]));
	$params["sortstruct"] = stripcslashes(getVar("sort",$params["sortstruct"]));
	$params["countrecs"]= getVar("countRecs",$params["countrecs"]);
}

/**
* Loads parameters by parsing a DiGIR request document.  The document can be
* a reference to a DiGIR request document (i.e. a URL), or can be the actual
* request document.
* @param $request string The request document body or a URL that references the
* document.
* @param $params array A reference to an array that will hold the operation values
* extracted from the request document. 
*/
function loadParamsDiGIRRequest($request,&$params)
{
	//extract the operating parameters from the payload
	//load the xmlpath library

	$xparser = new XPath();
	$xparser->setVerbose(0);
	$xparser->setXmlOption(XML_OPTION_CASE_FOLDING,false);
	$xparser->setXmlOption(XML_OPTION_SKIP_WHITE,true);

	if (isURL($request))
	{
		DebugMsg("load from file");		
		$xparser->importFromFile($request);
	}
	else
	{
		DebugMsg("load from string");		
		$xparser->importFromString($request);
	}
	//load the namespaces from the request document
	$params['requestnamespaces'] = getNamespaces($xparser);
	
	$fnode = $xparser->match("//request/header/type");
	if (count($fnode) > 0)
	{
		$params["operation"] = strtolower($xparser->getData($fnode[0]));
		if (strcasecmp($params['operation'],'getcontent') == 0)
		  $params['operation'] = 'search';
	}
	
	//get the clientKey if it was set in the header.
	$fnode = $xparser->match("//request/header");
	if (count($fnode) > 0)
	{
	    $ckey = $xparser->getAttributes($fnode[0],"clientKey");
	    if ((isset($ckey)) && (strlen($ckey) > 0))
	    {
	        $params['clientkey'] = $ckey;
        }
	}
	//resource is located as an attribute of the destination	
	$fnode = $xparser->getAttributes("//request/header/destination");
	if (count($fnode) > 0)
	{
		$params["resource"] = $fnode["resource"];	
	}
	
	/////////////////////
	//Extract the parameters for a search operation
	if (strcasecmp($params["operation"],'search') == 0)
	{	
            $fnode = $xparser->match("//".$params['operation']."/filter");
            if (count($fnode) > 0)
                $params["filter"] = $xparser->exportAsXml($fnode[0]);
                    
            //old style check for resource name
            if ($params["resource"] == "")
            {		
                $fnode = $xparser->match("//".$params['operation']."/resource");
                if (count($fnode) > 0)
                    $params["resource"] = $xparser->getData($fnode[0]);
            }			
            
            $tmpnode = $xparser->match("//".$params['operation']."/records");
            if (count($tmpnode) > 0)
            {
                $fnode = $xparser->getAttributes($tmpnode[0]);
                if (count($fnode) > 0)
                {
                    if (isset($fnode["start"]))
                        $params["startrec"] = $fnode["start"];
                    if (isset($fnode["limit"]))
                        $params["maxrecs"] = $fnode["limit"];
                    $params["getrecords"] = true;	
                }
            }
            else
            {
                $params['getrecords'] = false;	
            }
            
            //retrieve the record structure definition
            //check to see if we are given a structure or a reference to a structure
            $fnode = $xparser->match("//".$params['operation']."/records/structure");
            if (count($fnode) > 0)
            {
                $params['recordstruct'] = $xparser->getAttributes($fnode[0],"schemaLocation");
                if ($params['recordstruct'] == '')
                {
            //retrieve the XML structure from the request document
                    $params["recordstruct"] =
                    $xparser->exportAsXml("/request[1]/".
                    $params['operation'].
                    "[1]/records[1]/structure[1]");
                }
            }
            else
            {
                //retrieve the URL for the record structure
                $params["recordstruct"] =
                    trim($xparser->wholeText("/request[1]/".
                    $params['operation'].
                    "[1]/records[1]"));
            }

        //check to see if we need to return a record count
            $fnode = $xparser->match("//".$params['operation']."/count");
            if (count($fnode) > 0)
            {
                $tmp = $xparser->getData($fnode[0]);
                if ((strcasecmp($tmp,"true") == 0) || ((int)$tmp== 1))
                    $params["countrecs"]=true;
            }
            else
            {
                $params["countrecs"]=false;	
            }
	}
        ////////////
	//Extract the parameters for an inventory operation
	if ((strcasecmp($params["operation"],'inventory') == 0) ||
            (strcasecmp($params["operation"],'scan') == 0))
	{	
	    $fnode = $xparser->getAttributes("//".$params['operation']);
	    if (count($fnode) > 0)
	    {
		if (isset($fnode["start"]))
		    $params["startrec"] = $fnode["start"];
		if (isset($fnode["limit"]))
		    $params["maxrecs"] = $fnode["limit"];
	    }


            //get a list of all immediate child nodes of the <inventory>
            $fnodes = $xparser->match("//".$params['operation']."/*");
            
            //the element name of each of these nodes can be:
            //filter, count, or some item being inventoried
            
/*
The concept to be scanned is defined by the content of the <inventory> element:
<inventory>
  [<filter>...</filter>]
  [<count>true | false</count>]
  <ns:inv_me />
</inventory>

changed to:
<inventory start="" limit="">
...
*/
            $params['countrecs'] = false;
            foreach ($fnodes as $v)
            {
                $node = $xparser->getNode($v);
                if ($node['name'] == 'filter')
                {
                    //there is a filter associated with the inventory
                    $params["filter"] = $xparser->exportAsXml($v);
                }
                elseif ($node['name'] == 'count')
                {
                    //count has been specified
                    $tmp = $xparser->getData($v);
                    if ((strcasecmp($tmp,"true") == 0) || ((int)$tmp== 1))
                        $params["countrecs"]=true;
                }
                else
                {
                    //anything else must be the element being inventor
                    //this is kind of a kludge, since there could be more than
                    //one
                    $params['recordstruct'] = $node['name'];
                }
            }
		
/*            //retrieve the record structure definition
            //check to see if we are given a structure or a reference to a structure
            $fnode = $xparser->match("//".$params['operation']."/records/structure");
            if (count($fnode) > 0)
	    {
                $params['recordstruct'] = $xparser->getAttributes($fnode[0],"schemaLocation");
                if ($params['recordstruct'] == '')
		{
                    //retrieve the XML structure from the request document
                    $params["recordstruct"] =
                    $xparser->exportAsXml("/request[1]/".
                        $params['operation'].
                        "[1]/records[1]/structure[1]");
                }
	    }
            else
            {
                //retrieve the URL for the record structure
                $params["recordstruct"] =
                    trim($xparser->wholeText("/request[1]/".
                    $params['operation'].
                    "[1]/records[1]"));
            }
*/
            // convert old school 
            if (strcasecmp($params['operation'],'scan') == 0)
                $params['operation'] = 'inventory';
	      
	}
        //Metadata parameters - there are none!
	elseif ((strcasecmp($params["operation"],"metadata") == 0) ||
		(strcasecmp($params["operation"],"getCapabilities") == 0))
	{
        //Nothing to do really.  This is just a place holder
        //where we could set the parameters for metadata requests
	}
}

/**
* Loads the operational parameters by examining the request URL and POSTed values.
* @param $params array Reference to an array of operation parameters.
*/
function loadParams(&$params)
{
    global $HTTP_GET_VARS,
           $HTTP_POST_VARS,
           $QUERY_STRING;

    //The request can be an XML block (i.e. a DiGIR Request Structure) or
    //a bunch of name + value pairs.  In either case, the data may be sent by
    //POST or GET, and may be identified by the name "doc" or "request", or may
    //simple form the entire body of the submitted data (i.e. the default
    //parameter).

    $request = "NULL";
    //Check to see if request is the "default" parameter for GET or POST
    //if so then it will be a url encoded xml request document
    $tmp = getDefaultParameter();
    if ($tmp != null)
        $request = $tmp;

    //maybe the request is called "request"?
    if (strcasecmp($request,"NULL") == 0)
	   $request = stripcslashes(getVar("request","NULL"));

    //could be called "doc"?
    if (strcasecmp($request,"NULL") == 0)
	   $request = stripcslashes(getVar("doc","NULL"));

    //strip any whitespace from the request
    $request=trim($request);

    //The cache parameter will always be outside of the request body
    //but may be present in POST or GET forms of submission.
    $params['cache']=(boolean)getVar('cache',$params['cache']);

//DEBUG
//    $request='http://velo.dyndns.info/phpdev/digir/req/requestExample.xml';
//    $params['cache'] = 0;

    if (strcasecmp($request,"NULL") == 0)
    {
        loadParamsNVPairs($params);
    }
    else
    {
        loadParamsDiGIRRequest($request,$params);
    }
    if ($params['maxrecs'] > DIGIR_ABS_RECORD_LIMIT)
        $params['maxrecs'] = DIGIR_ABS_RECORD_LIMIT;
        
    //reduce ambiguities in operation names.
    if (strcasecmp($params['operation'],'getcontent') == 0)
    {
        $params['operation'] = 'search';
    }
    elseif (strcasecmp($params['operation'],'getcapabilities') == 0)
    {
        $params['operation'] = 'metadata';
    }

    //get the name of the resource configuration file
    if ((strcasecmp($params['operation'],'inventory') == 0) ||
        (strcasecmp($params['operation'],'search') == 0))
    {
        $configFile = getResourceConfigFile($params['resource']);
        if ($configFile)
              $params['resourceconfigfile'] = realpath(DIGIR_CONFIG_DIR.'/'.$configFile);
    }
}

///////////////////////////////////////////////////////////////////////////
//Start of the main routine
if ((isset($_SERVER['SCRIPT_NAME']) && preg_match("/.\/DiGIR.php/i",$_SERVER['SCRIPT_NAME'])) ||
    (isset($_SERVER['argv'][0]) && strcasecmp($_SERVER['argv'][0],'DiGIR.php') === 0))
{
    define('DiGIRVERSION','$Revision: 1.14 $');
    define('DiGIRBuild','104');
    
    //set the level PHP of error reporting
    error_reporting(E_ALL
                    & ~E_NOTICE
                    & ~E_WARNING
                    & ~E_CORE_WARNING
                    & ~E_COMPILE_WARNING);
    
    define('PHP_SELF',$_SERVER['PHP_SELF']);
    
    require_once('DiGIR_globals.php');
    require_once(DIGIR_XPATH_LIBRARY);
    require_once('DiGIR_Log.php');
    
    //install a new error handler
    $old_error_handler = set_error_handler("DiGIRErrorHandler");
    
    //check PHP version
    if (version_compare(phpversion(), DIGIR_MIN_PHP_VERSION, '<') > 0)
    {
        appendError(DC_PHPVERSION,
        'PHP Version '.DIGIR_MIN_PHP_VERSION.' or later required. '.
        'Some features may not be available. Detected version '.phpversion(),
        DIAG_WARN);
    }
    
    //set the maximum script run time
    //Note: if php set safe mode is on then this can't be set from here, and will need to be set in php.ini file
    set_time_limit(DIGIR_MAX_RUNTIME);
    
    // set _DEBUG to false to disable debug statements
    // Security issue:
    // Since both the value of debug and information provided to ulink
    // and fwrite can be provided by an untrusted user, _DEBUG should be
    // forced to false except when needed for debuging, otherwise 
    // the potential exists for an attacker to write arbitrary files to 
    // the file system with the priviliges of the php process. 
    // PJM 2003 Mar 19
    // So set it in DiGIR_globals.php not here.  DV April 2003
    //
    // loads the value from the request variable called "debug"
    if (DIGIR_ALLOWDEBUG)
        define("_DEBUG",(bool) getVar("debug",false));
    else
        define("_DEBUG",false);
    
    if (strcasecmp(getVar('operation',''),'test') == 0)
    {
        //Invoke test routines
        include_once(realpath(DIGIR_ROOT_DIR.'/../test').'/test_request.php');
        die();
    }
    
    //if operation=help, then dump some help information
    if (strcasecmp(getVar("operation",""),"help") == 0)
    {
        include_once("DiGIR_help.php");
        die();
    }
    
    //Send the XML content type header
    header ("Content-type: text/xml");
    
    //This is used for debugging purposes - just stores the request to disk
    $g_stashRequest = false;
    if (_DEBUG)
            $g_stashRequest = true;
    
    //Create the log singleton
    //log entries have ID = client (portal) IP address
    $g_LOG = &Log::singleton(DIGIRLOG_TYPE,
                             realpath(DIGIRLOG_PATH.'/'.DIGIRLOG_NAME),
                             $_SERVER['REMOTE_ADDR'],
                             explode(",",DIGIRLOG_OPTIONS),
                             DIGIRLOG_LEVEL);
    
    if (PEAR::isError($g_LOG)) 
    {
            appendError(DC_LOG_ERROR,
                    "The log could not be opened. Check configuration.");
    }
    
    //Start the response
    echo "<?xml version='1.0' encoding='utf-8' ?".">\n";
    
    //set the default namespace of the response document
    echo "<response xmlns='".DiGIRNameSpace."'>\n";
    
    //Get the operation parameters
    //standard DiGIR call?
    $params = array();
    intializeParams($params);
    
    //load parameters from the request.
    loadParams($params);
    
    /////////////////////////////
    //DEBUG - set the request
    //$params['operation'] = 'search';
    //$params['cache'] = FALSE;
    //$params['resource'] = 'unicode';
    //$params['countrecs'] = TRUE;
    //$params['filter'] = '@attr 1=12 @attr 2=4 "99995"';
    //$params['resourceconfigfile'] = getResourceConfigFile($params['resource']);
    /////////////////////////////
    
    $g_LOG->log("request_params=".str_replace("\n"," ",serialize($params)),PEAR_LOG_DEBUG);
    
    //==========
    //stash the request xml to a file
    if ($g_stashRequest)
    {
            $fnam = DIGIR_DEBUG_DIR;
            $fnam = tempnam($fnam,"req");
            unlink($fnam);
            // sanitize remote address before using in fopen
            $sanitized_remote_address = str_replace(".","_",$_SERVER['REMOTE_ADDR']);
            $sanitized_remote_address = preg_replace("[^A-Za-z0-9_]","_",$sanitized_remote_address);
            $fnam .= "_".$sanitized_remote_address.".txt";
            $fp = fopen($fnam,"w");
            if ($fp)
            {
               fwrite($fp,sprintf("AGENT: %s\n",$_SERVER['HTTP_USER_AGENT']));
               if (isset($_SERVER['QUERY_STRING']))
               {
                   fwrite($fp,sprintf("GET: %s\n",$_SERVER['QUERY_STRING']));
                   fwrite($fp,sprintf("GET: %s\n",urldecode($_SERVER['QUERY_STRING'])));
            }
               fwrite($fp,sprintf("POST: %s\n",dumpArray($_POST)));
               fclose($fp);
        }
    }
    //===========
    
    include_once('DiGIR_status.php');
    $statusopcode = 0;
    if (strcasecmp($params["operation"],"metadata") == 0)
        $statusopcode = 1;
    elseif (strcasecmp($params["operation"],"search") == 0)
        $statusopcode = 2;
    elseif (strcasecmp($params["operation"],"inventory") == 0)
        $statusopcode = 3;
    $digirstatus = getStatus(DIGIR_STATUSINTERVAL,DIGIR_STATUSPICKLE,$statusopcode);

    AppendError(DC_STATUS_INTERVAL,DIGIR_STATUSINTERVAL,DIAG_INFO);
    AppendError(DC_STATUS_DATA,
                sprintf("%d,%d,%d",$digirstatus[1],$digirstatus[2],$digirstatus[3]),
                DIAG_INFO);

    //Send the response Header
    echo DiGIRHeader($statusopcode,
                     $params["clientid"],
                     $params["resource"],
                     $params['clientkey']);
    
    //invoke the code that generates a response
    // do we have valid parameters?
    // no -> generate a simple response with doc link
    // yes -> go for it
    if (strcasecmp($params["operation"],"search") == 0)
    {
        //Load the resource list from an XML file
            require_once("DiGIR_getContent.php");
            if ($params['resourceconfigfile'] !== null)
            {
                    //Retrieve the records
                    //getContent($params);
                    if ($params['cache'])
                    {
                require_once 'Cache/Function.php';
                $cacheParams = array('cache_dir'=>DIGIR_CACHE_DIRECTORY,
                                     'filename_prefix'=>'req_');
                $cache = new Cache_Function('file',
                                     $cacheParams,
                                     DIGIR_RESULTSET_CACHE_LIFE_SECS);
                $cache->call('getContent',$params);
            }
            else
                getContent($params);
    
            //Note: DiGIRErrors() is now called by getContent so that the output is
            //caught by the cache operation.
            echo '</response>';
            die();
            }
            else
            {
                    appendError(DC_RESOURCENOTFOUND,
                sprintf("The requested resource (%s) is not
                         available on this provider.",
                $params["resource"]),DIAG_ERROR);
            }
    }
    elseif (strcasecmp($params['operation'],'inventory') == 0)
    {
        //scan a column.  The filter contains the column name
        //Load the resource list from an XML file
        require_once("DiGIR_scan.php");
        
        if ($params['resourceconfigfile'] !== null)
        {
            //Retrieve the records
            //getContent($params);
            if ($params['cache'])
            {
                require_once 'Cache/Function.php';
                $cacheParams = array('cache_dir'=>DIGIR_CACHE_DIRECTORY,
                                     'filename_prefix'=>'req_');
                $cache = new Cache_Function('file',
                                            $cacheParams,
                                            DIGIR_INVENTORY_CACHE_LIFE_SECS);
                $cache->call('scanColumn',$params);
            }
            else
                scanColumn($params);
    
            echo '</response>';
            die();
        }
        else
        {
            appendError(DC_RESOURCENOTFOUND,
            sprintf("The requested resource (%s) is not
                     available on this provider.",
                     $params["resource"]),DIAG_ERROR);
        }
    }
    elseif (strcasecmp($params["operation"],"metadata") == 0)
    {
        require_once("DiGIR_getCaps.php");
        $logentry = new DiGIRLog_metadata();
        $logentry->writeLog($g_LOG);
        if ($params['cache'])
        {
            require_once('Cache/Function.php');
            $cacheParams = array('cache_dir'=>DIGIR_CACHE_DIRECTORY,
                                 'filename_prefix'=>'cap_');
            $cache = new Cache_Function('file',
                                        $cacheParams,
                                        DIGIR_METADATA_CACHE_LIFE_SECS);
            $cache->call('getCapabilities2',$params);
        }
        else
            getCapabilities2($params);

        echo '</response>';
        die();
    }
    else
    {
        //Huh? I don't know what you want
        appendError(DC_INVALIDREQUEST,
            "Sorry, I can not understand what you want (request = \"".
            $params['operation']."\"). Try reading http://digir.sourceforge.net",
            DIAG_ERROR);
        $g_LOG->log("Invalid request",PEAR_LOG_WARNING);
    }
    
    echo DiGIRErrors($params['clientkey']);
    echo "\n</response>";
    exit;
    // that's all folks...
}
?>