/add-ons/modsp/sp.php
PHP | 691 lines | 599 code | 44 blank | 48 comment | 27 complexity | 8d441d83e1727e21b4e66d99ed9c00c9 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, GPL-3.0, Apache-2.0, BSD-3-Clause
- <?php
- //error_reporting(E_ALL);
- /*
- * Copyright (C) 2004 BYS Promotion, France
- *
- * Web: http://www.bys-promotion.com/
- * http://sourceforge.net/projects/comarquage/
- * http://comarquage.sourceforge.net/
- *
- * Coordinator: Daniel BODEA (daniel.bodea@bys-promotion.com)
- *
- * This software package is released under the terms of the Q Public License
- * version 1.0. The full text of the license is available in the LICENSE file
- * that is bundled with this distribution.
- *
- * The code, or any part thereof, can only be redistributed if the statements
- * above, including this one, are included unchanged in all redistributed
- * materials.
- *
- */
- /* This class is built so that it can be subclassed for advanced logic
- modifications and improvements.
- */
- class ServicePublic_V2
- {
- /* Object configuration variables, updated from configuration files and
- inside the constructor. Dynamic values are computed in the constructor. */
- var $aParams = array(
- 'xml_server' => 'http://lecomarquage.service-public.fr/xml2v2',
- 'default_xml_file' => 'Themes.xml',
- 'default_xsl_file' => 'spThemes.xsl',
- 'local_timeout' => 259200, /* 60*60*24*3 */
- 'root_path' => NULL,
- 'images_path' => NULL,
- 'error_email' => NULL,
- 'handle_not_contained' => TRUE,
- 'html_error_template' => array(
- '<div align="center"><div style="border-left: 3px solid red; ',
- 'padding-left:5px;text-align:left;" nowrap>%s</div></div>',
- ),
- );
- /* Constructor. Parameter is an array of configuration variables, a folder
- containing the default configuration file or the full path of a custom
- configuration file.
- */
- function __construct($vConfig = NULL)
- {
- /* TODO: Merge from a global variable too. */
- $bMergeConfig = (boolean)(
- $this->_mergeConfigFrom(dirname(__FILE__)) &&
- (
- getcwd() == dirname(__FILE__) ||
- $this->_mergeConfigFrom($this->_sConfigFile)
- ) && (
- is_null($vConfig) ||
- $this->_mergeConfigFrom($vConfig)
- ) && (
- !isset($this->_aParams) ||
- $this->_mergeConfigFrom($this->_aParams)
- )
- );
- /* TODO: Test and continue if no show stoppers. */
- if (!$bMergeConfig) {
- $this->_error("impossible de créer un objet <b>".get_class($this)."</b> ".
- "car une erreur est survenue lors de sa configuration initiale",
- E_USER_ERROR);
- return;
- }
- if (!defined(TZR_COMARQUAGE_CACHE_DIR))
- define('TZR_COMARQUAGE_CACHE_DIR',TZR_VAR2_DIR.'modsp/');
- if (isset($this->_aParams)) unset($this->_aParams);
- is_null($this->aParams['root_path']) and
- $this->aParams['root_path'] = dirname(__FILE__);
- }
- /* Make sure the specified file is on disk, with or without its related
- ressources.
- */
- function prepareXMLFile($aFileParams, $bRelated = TRUE)
- {
- if (($bExists = file_exists($aFileParams['xml_full_path'])) &&
- time() - filemtime($aFileParams['xml_full_path']) <
- $this->aParams['local_timeout']) {
- return TRUE;
- }
- if (!$vFetch = $this->fetchXMLFile($aFileParams)) {
- $this->_error("erreur de téléchargement du fichier <b>".
- "$aFileParams[xml]</b>; ".(
- $bExists ? "l'action continue avec le fichier présent dans le cache ".
- "mais la connexion au serveur externe doit ętre rétablie" :
- "l'action ne peut pas ętre poursuivie car le fichier n'existe pas ".
- "dans le cache"), E_USER_NOTICE);
- return $bExists;
- }
- if ($bRelated && $vFetch == 20 && $aFileParams['xml']{0} != 'M') {
- $aRes = $this->extractAssociatedRessources($aFileParams['xml_full_path']);
- /* TODO: The full path of a local XML file should be calculated centrally
- so that special cases can be handled once only. */
- if ($aRes !== FALSE) foreach ($aRes as $v) {
- if (!$this->prepareXMLFile(array_merge($aFileParams, array(
- 'xml' => $v,
- 'xml_full_path'=>TZR_COMARQUAGE_CACHE_DIR.'xml/'.$v)),FALSE)) {
- $aRes = FALSE; break;
- }
- }
- /* TODO: Unlink the main XML file ? Continue the action ? The main file
- should compile even without its ressources while generating an
- XSL error.
- Ressources may be recursive. Flag files as incomplete for
- further downloading at a later time. Watch out for XSL "missing
- related files" errors indicating this state. */
- if ($aRes === FALSE) {
- $this->_error("impossible de récupérer les ressources associées au ".
- "fichier <b>$aFileParams[xml_full_path]</b>");
- return FALSE;
- }
- }
- return TRUE;
- }
- /* Download a remote XML file. Return FALSE on error, 10 if not modified and
- 20 if the file was downloaded OK.
- */
- function fetchXMLFile($aFileParams)
- {
- /* TODO: This is an extremely light weight HTTP client tailored especially for
- the requests of this module. It doesn't understand anything more than
- the very basic response codes so it only works in the most basic
- environment.
- A later version of this function should use a higher level HTTP
- handler like the context support and the HTTP built-in wrapper in
- PHP 5 or the HTTP libraries in PEAR. */
- $sModSince = file_exists($aFileParams['xml_full_path']) ? 'If-Modified-Since: '.
- gmdate('r', filemtime($aFileParams['xml_full_path']))."\r\n" : '';
- $aURL = parse_url($this->aParams['xml_server']);
- if (empty($aURL['host'])) {
- $this->_error("l'adresse du serveur XML semble ne pas ętre correcte ('<b>".
- $this->aParams['xml_server']."</b>'); vérifiez qu'elle soit au format ".
- "HTTP standard complet");
- return FALSE;
- }
- /* HTTP request headers sent to the remote XML server. */
- $uu=$aURL['host'].$aURL['path']."/".$aFileParams['xml'];
- $hplus="";
- if(!empty($sModSince)) $hplus="-H '$sModSince'";
- exec(TZR_CURL_PATH." $hplus -I --interface ".TZR_COMARQUAGE_IP.' '.$uu, $aHeaders);
- exec(TZR_CURL_PATH." $hplus --interface ".TZR_COMARQUAGE_IP.' '.$uu, $s1);
- $sBody=implode("\n",$s1);
- if (!ereg('^HTTP[^ ]+ ([[:digit:]]+) (.*)$', $aHeaders[0], $aRegs)) {
- $this->_error("erreur d'interprétation de l'en-tęte HTTP ".
- "'<b>$aHeaders[0]</b>'");
- return FALSE;
- }
- $sRCode = $aRegs[1]; $sRMessage = $aRegs[2];
- unset($aRegs);
- /* TODO: Use Apache's error database to consume the return code. */
- switch ($sRCode) {
- /* Return 10 to signal that the file was not modified. */
- case '304': return 10;
- case '200':
- if (!$fp = fopen($aFileParams['xml_full_path'], 'wb')) {
- $this->_error("erreur d'ouverture en écriture du fichier ".
- "'<b>$aFileParams[xml_full_path]</b>'");
- return FALSE;
- }
- /* Return 20 if the file has been downloaded OK. */
- fwrite($fp, $sBody);
- fclose($fp); return 20;
- default:
- $this->_error("code de réponse inconnu ('<b>$sRCode $sRMessage</b>') ".
- "en téléchargeant le fichier '<b>$aFileParams[xml]</b>' du serveur ".
- "<b>$aURL[host]</b>");
- return FALSE;
- }
- /* Execution shouldn't reach this point. */
- return FALSE;
- }
- /* Return an array containing the ressource files related to the given
- XML file.
- */
- function extractAssociatedRessources($sXMLFile)
- {
- $sRessourcesXSL = dirname(__FILE__).'/ressources.xsl';
- $oXSL =& new _ServicePublic_XSL_Engine($this, $bRet);
- if (!$bRet) return FALSE;
- if (($aRessources =& $oXSL->transformXMLFile(
- $sXMLFile, $sRessourcesXSL)) === FALSE)
- return FALSE;
- $aRet = array(); foreach ($aRessources as $v)
- if ($sRessource = trim($v)) $aRet[] = $sRessource.'.xml';
- return $aRet;
- }
- /* Get the contents of one compiled XML file.
- */
- function & compileOnePage($aCallParams)
- {
- $this->_secureCallParams($aCallParams);
- if (!file_exists($aCallParams['xsl_full_path'])) {
- $this->_error("la feuille de style XSL ".
- "'<b>$aCallParams[xsl]</b>' n'existe pas");
- return -10;
- }
- if (!$this->prepareXMLFile($aCallParams))
- return -20;
- $this->_prepareCacheParams($aCallParams);
- if ($sOutput =& $this->_getCache($aCallParams))
- return $sOutput;
- /* TODO: Use a stack based error reporting system. */
- $oXSL =& new _ServicePublic_XSL_Engine($this, $bRet);
- if (!$bRet) return -30;
- $aXSLParams = array();
- /* TODO: The image path should be calculated differently according to the
- location of the XML content and according to the XSL sheets. */
- $aXSLParams['REFERER']= TZR_COMARQUAGE_PAGE.'?';
- $aXSLParams['PICTOS']= is_null($this->aParams['pictos_path']) ?
- dirname($_SERVER['PHP_SELF']).'/pictos/' : $this->aParams['pictos_path'];
- $aXSLParams['SITEURL']= $GLOBALS['HOME_ROOT_URL'];
- $aXSLParams['IMAGES']= is_null($this->aParams['images_path']) ?
- dirname($_SERVER['PHP_SELF']).'/images/' : $this->aParams['images_path'];
- $aXSLParams['PIVOTS']= '';
- $aXSLParams['XMLURL']= 'http://lecomarquage.service-public.fr/xml2v2/';
- $aXSLParams['CATEGORIE']= is_null($this->aParams['pictos_path']) ?
- 'particuliers' : $this->aParams['categorie'];
- if (($sOutput =& $oXSL->transformXMLFile($aCallParams['xml_full_path'],
- $aCallParams['xsl_full_path'], $aXSLParams)) === FALSE) {
- $this->_error("le processeur XSLT a retourné une erreur fatale; ".
- "l'action ne peut pas continuer");
- return -40;
- }
- $sOutput = implode("\n", $sOutput)."\n";
- $this->_setCache($aCallParams, $sOutput);
- return $sOutput;
- }
- function isContained()
- {
- return TRUE;
- /* TODO: The referer header is easy to fake so a stronger one-time-key
- authorization scheme needs to be brought in. */
- $sRef = (empty($_SERVER['HTTPS']) ? 'http' : 'https').'://'.
- $_SERVER['HTTP_HOST'].'/';
- if (empty($_SERVER['HTTP_REFERER']) ||
- $sRef != substr($_SERVER['HTTP_REFERER'], 0, strlen($sRef))) {
- $this->_error("l'appel n'est pas autorisé pour REFERER = ".(
- empty($_SERVER['HTTP_REFERER']) ?
- '(vide)' : '<b>'.$_SERVER['HTTP_REFERER'].'</b>'));
- return FALSE;
- }
- return TRUE;
- }
- /* The JavaScript code is first of all entirely dependent on the XSL files and
- the ressources should be calculated on the server.
- TODO: The JS support in this module should be replaced by independent XSL
- files usage and a server-side ressources extraction.
- */
- function writeJSComplement()
- {
- $txt = <<<EOT
- var _SP_actionMap = [];
- function SP_actionMap(vMap)
- {
- var v = null; for (v in vMap) {
- if (!_SP_actionMap[v]) continue;
- var oLink = document.getElementById(_SP_actionMap[v] + '_LINK');
- var oText = document.getElementById(_SP_actionMap[v] + '_TEXT');
- oLink.href = vMap[v]['href'];
- oLink.target = vMap[v]['target'] ?
- vMap[v]['target'] : '';
- oText.innerHTML = vMap[v]['text'] ?
- vMap[v]['text'] : 'Ressource locale.';
- delete _SP_actionMap[v];
- }
- }
- function _SP_requestRessourceAvailability(sRessource, sId)
- {
- if (!sRessource || !sId) return;
- if (typeof(SP_actionMapURL) == 'undefined') return;
- _SP_actionMap[sRessource] = sId;
- document.write('<scr' + 'ipt src="' +
- SP_actionMapURL.replace("SP_RESSOURCE", sRessource) + '"></scr' + 'ipt>');
- }
- EOT;
- echo $txt;
- }
- function writeJSOutput(& $sOutput, $aExtraParams = NULL)
- {
- $sExtraParams = '?';
- if (!is_null($aExtraParams)) foreach ($aExtraParams as $k => $v)
- $sExtraParams .= "&$k=".urlencode($v);
- //$stmp=addcslashes($sOutput, "\"");
- $stmp=addcslashes($sOutput, "\n\r\"");
- $stmp1=substr($sExtraParams, 1);
- $uri=eregi_replace('(xml=[a-z0-9\.]+)','',TZR_COMARQUAGE_PAGE);
- $uri=eregi_replace('(xsl=[a-z0-9\.]+)','',$uri);
- $stmp=str_replace("%%REFERER%%",$uri.$sExtraParams,$stmp);
- //$stmp=str_replace("?xml=","&xml=",$stmp);
- // $stmp=str_replace("?&","?",$stmp);
- // $stmp=str_replace("??","?",$stmp);
- echo "document.write(\"$stmp\");";
- }
- /* Get call parameters from the current URL and output the compiled XML file
- as JavaScript code.
- */
- function runJavaScript($bContain = TRUE)
- {
- if ($bContain && !$this->isContained()) {
- $this->aParams['handle_not_contained'] and
- $this->_handleNotContained();
- return FALSE;
- }
- $aCallParams =& $this->splitCallParams($_GET);
- $sOutput =& $this->compileOnePage($aCallParams['call']);
- if (!is_string($sOutput)) {
- if ($sOutput == -20) {
- $sError = "<b>La page ne peut ętre affichée car le contenu XML n'est ".
- "pas disponible.<br>Le serveur XML du Service Public pourrait ętre ".
- "momentanément hors ligne.</b><br><br>Veuillez réessayer ".
- "ultérieurement.";
- } else {
- $sError = "<b>Une erreur interne s'est produite lors de la génération ".
- "de cette page.<br>Les administrateurs ont été notifiés et le ".
- "problčme sera résolu sous peu.</b><br><br>Veuillez réessayer ".
- "ultérieurement.";
- }
- $sError = sprintf(implode('',
- (array)$this->aParams['html_error_template']), $sError);
- $this->writeJSOutput($sError);
- return FALSE;
- }
- $this->writeJSComplement(); echo "\n";
- $this->writeJSOutput($sOutput, $aCallParams['rest']);
- return TRUE;
- }
- function & splitCallParams($aValues)
- {
- $aSplit = array(
- 'call' => array(),
- 'rest' => array()
- );
- foreach ($this->_aCallKeys as $k) {
- if (!array_key_exists($k, $aValues)) continue;
- $aSplit['call'][$k] = $aValues[$k];
- unset($aValues[$k]);
- }
- $aSplit['rest'] = $aValues;
- return $aSplit;
- }
- function dirname($sPath, $iDepth = 1)
- {
- while ($iDepth--) $sPath = dirname($sPath);
- return $sPath == '/' ? '' : $sPath;
- }
- /* Private.
- */
- var $_sConfigFile = 'sp.config',
- $_aCallKeys = array('xml', 'xsl', 'lettre', 'motcle');
- function _error($sError, $iType = E_USER_WARNING)
- {
- /* TODO: Append errors to an error stack that can be used to simulate
- exceptions using some other helper methods.
- The email address is considered secure by default. Perform some
- security checks first. */
- if (!is_null($this->aParams['error_email'])) {
- ob_start(); echo "\$_SERVER: "; print_r($_SERVER);
- echo "\n\$this: "; print_r($this); $sDump =& ob_get_clean();
- $aErrorTypes = array(
- E_USER_NOTICE => 'User Notice',
- E_USER_WARNING => 'User Warning',
- E_USER_ERROR => 'User Error'
- );
- $sMessage = $aErrorTypes[$iType].': '.$sError."\n\n".$sDump;
- mail($this->aParams['error_email'], '[SP] ERROR Notification',
- $sMessage, 'From: "'.$_SERVER['HTTP_HOST'].'" <'.
- $this->aParams['error_email'].'>', '-f'.
- $this->aParams['error_email']);
- }
- /* TODO: Use a backtrace to gather more info on the error and search for a
- possibly derived class when reporting the environement.
- */
- trigger_error('[ServicePublic] '.$sError, $iType);
- }
- function _mergeConfigFrom($vConfig)
- {
- /* A directory or a file name. */
- if (is_string($vConfig)) {
- if (is_dir($vConfig))
- $vConfig .= (substr($vConfig, -1) == '/' ? '' : '/').
- $this->_sConfigFile;
- if (!file_exists($vConfig)) return TRUE;
- $aConfig = parse_ini_file($vConfig);
- if (!is_array($aConfig)) {
- $this->_error("une erreur s'est produite lors de la lecture du ".
- "fichier de configuration <b>$vConfig</b>");
- return FALSE;
- }
- /* Fall through as an array. */
- $vConfig =& $aConfig;
- }
- if (is_array($vConfig)) {
- $aKeys = array_intersect(
- array_keys($this->aParams), array_keys($vConfig));
- if (count($aKeys) != count($vConfig)) {
- $this->_error("les options de configuration suivantes n'ont pas été ".
- "reconnues : <b>".implode(', ', array_diff(array_keys($vConfig), $aKeys)).
- "</b>; ceci n'est pas une erreur fatale mais la configuration de ce ".
- "module devrait ętre corrigée", E_USER_NOTICE);
- return FALSE;
- }
- foreach ($aKeys as $k)
- $this->aParams[$k] = $vConfig[$k];
- }
- return TRUE;
- }
- function _handleNotContained()
- {
- $txt = <<<EOT
- alert("Le module Service Public que vous ętes en train d'appeler\nest protégé \
- et ne peut ętre utilisé qu'ŕ l'intérieur d'une application\nqui se trouve sur \
- le męme serveur que la page qui l'intčgre.\n\nSi vous pensez qu'il s'agit \
- d'une erreur, veuillez signaler\nce problčme aux administrateurs du portail \
- que vous ętes\nen train de visiter ou directement aux développeurs ŕ l'adresse \
- :\n\nhttp://sourceforge.net/projects/comarquage/.");
- EOT;
- echo $txt;
- }
- /* Perform some security checks on the raw call parameters and prepare the
- call array for secure use inside the object.
- */
- function _secureCallParams(& $aCallParams)
- {
- foreach (array('xml') as $k) {
- if (empty($aCallParams[$k]) ||
- (($aCallParams[$k] = trim($aCallParams[$k])) &&
- !fnmatch('*.'.$k, $aCallParams[$k], FNM_PATHNAME | FNM_PERIOD))) {
- $aCallParams[$k] =
- $this->aParams['default_'.$k.'_file'];
- }
- @mkdir(TZR_COMARQUAGE_CACHE_DIR.$k,0777,true);
- $aCallParams[$k.'_full_path'] = TZR_COMARQUAGE_CACHE_DIR.$k.'/'.$aCallParams[$k];
- $aCallParams['xsl_full_path'] = $this->aParams['root_path'].'/xsl/'.getXSLFile($aCallParams[$k]);
- }
- }
- /* Calculate the cache parameters based on a secure call array.
- */
- function _prepareCacheParams(& $aCallParams)
- {
- $sCacheId = '';
- !empty($aCallParams['lettre']) and $sCacheId .= 'l'.$aCallParams['lettre'];
- !empty($aCallParams['motcle']) and $sCacheId .= 'm'.md5($aCallParams['motcle']);
- $aCallParams['cache_full_path'] = TZR_COMARQUAGE_CACHE_DIR.basename($aCallParams['xml'], '.xml').
- ($sCacheId ? '.'.$sCacheId : '').'.cache';
- }
- /*function & _getCache($aCallParams)
- {
- $sCF = $aCallParams['cache_full_path'];
- if (file_exists($sCF) &&
- filemtime($sCF) > filemtime($aCallParams['xml_full_path']) &&
- filemtime($sCF) > filemtime(dirname($aCallParams['xsl_full_path']))) {
- return implode('', file($sCF));
- }
- return FALSE;
- }*/
- function & _getCache($aCallParams)
- {
- $sCF = $aCallParams['cache_full_path'];
- if (file_exists($sCF)) {
- // Scanne les dates de modification de tous les XSL
- if ($handle = opendir(dirname($aCallParams['xsl_full_path']))) {
- while (false !== ($file = readdir($handle))) {
- if (filemtime($sCF) < filemtime(dirname($aCallParams['xsl_full_path']).'/'.$file)) {
- closedir($handle);
- return false;
- }
- }
- closedir($handle);
- }
- if (filemtime($sCF) > filemtime($aCallParams['xml_full_path']) &&
- filemtime($sCF) > filemtime(dirname($aCallParams['xsl_full_path']))) {
- return implode('', file($sCF));
- }
- }
- return FALSE;
- }
- function _setCache($aCallParams, & $sOutput)
- {
- if (!$fp = fopen($aCallParams['cache_full_path'], 'wb')) {
- $this->_error("impossible d'écrire dans le fichier ".
- "<b>$aCallParams[cache_full_path]</b>");
- return FALSE;
- }
- fwrite($fp, $sOutput);
- fclose($fp);
- return TRUE;
- }
- /* Static.
- */
- static function isMain($sSource = __FILE__)
- {
- return true;
- $fname=str_replace('/restricted-ssh','',$_SERVER['SCRIPT_FILENAME']);
- $sSource=eregi_replace('([a-z]+.php)','modsp.php',$sSource);
- $res= !empty($fname) && ($fname == $sSource);
- return $res;
- }
- }
- class _ServicePublic_XSL_Engine
- {
- var $oSP = NULL;
- function __construct(& $oSP, & $bRet)
- {
- $this->oSP =& $oSP;
- /* TODO: Don't call this more than once per page request. */
- $bRet = $this->_isEngineAvailable();
- }
- /* xsltproc specific.
- */
- function & transformXMLFile($sXMLFile, $sXSLFile = NULL, $aParams = NULL)
- {
- $sParams = ' ';
- if (!is_null($aParams)) foreach ($aParams as $k => $v) {
- if($k != 'PIVOTS') $sParams .= '--stringparam '.escapeshellarg($k).' '.escapeshellarg($v).' ';
- }
- $sErrorFile = tempnam('/tmp', 'xsltprocErrors_');
- $sCommand = $this->_executable.$sParams.($sXSLFile ? $sXSLFile.' ' : '').
- "$sXMLFile 2> $sErrorFile";
- exec($sCommand, $aRet, $iRet);
- if (/*true || */filesize($sErrorFile)) {
- $this->oSP->_error("la commande <b>$sCommand</b> a retourné ".
- "(<b>$iRet</b>) : ".implode('', file($sErrorFile)));
- }
- //unlink($sErrorFile);
- return $iRet ? FALSE : $aRet;
- }
- var $_executable = 'xsltproc';
- function _isEngineAvailable()
- {
- foreach (explode(':', getenv('PATH')) as $sPath) {
- if (is_executable($sPath.'/'.$this->_executable)) return TRUE;
- }
- $this->oSP->_error("l'exécutable <b>$this->_executable</b> n'a pas été ".
- "trouvé dans le PATH ('<b>".getenv('PATH')."</b>')");
- return FALSE;
- }
- }
- function getXSLFile($sXMLFile)
- {
- $aSheets = array('F' => 'spFichePrincipale.xsl', 'M' => 'spMotsCles.xsl',
- 'N' => 'spNoeud.xsl', 'R' => 'spRessource.xsl', 'T' => 'spThemes.xsl');
- $sXMLFile=(basename($sXMLFile));
- if (!($sXMLFile = trim($sXMLFile))) return FALSE;
- if (empty($aSheets[$sXMLFile{0}])) return FALSE;
- return $aSheets[$sXMLFile{0}];
- }
- /* This module can be called directly in which case it uses the local config
- file and performs the default JS actions, suited for a basic and rapid
- installation.
- */
- if (ServicePublic_V2::isMain()) {
- $oSP =& new ServicePublic_V2();
- $oSP->runJavaScript();
- }
- ?>