view InputParser.inc @ 130:ee5f98a0bc93

Retrieve documents from DOCUMENT_ROOT, not SCRIPT_ROOT
author Tom Fredrik Blenning <bfg@bfgconsult.no>
date Fri, 13 Jan 2023 18:08:45 +0100
parents 7858ceb293d1
children
line wrap: on
line source

<?php
/// @cond
$baseDir = dirname(__FILE__);
$cache = ScriptIncludeCache::instance(__FILE__);
$cache->includeOnce('Page.inc', dirname(__FILE__));
/// @endcond

/**
 * Functionality for translating an XML configuration document into a webpage
 */
class InputParser extends Page
{
  private $options;
  private $master;

  /**
   * Constructs a new InputParser object
   *
   * @param $name name of file to read configuration from
   *
   * @param $masterCache CacheTimeCheck cache object to use as parent
   * for this inputParsercache
   */
  function __construct($name, $masterCache) {
    $this->master = new DOMDocument();
    $cache = new CacheTimeCheck($name);
    $cache->addParent($masterCache);
    parent::setCache($cache);
    $this->master->load($name);

    $this->options = new Options($this->master);
    $this->options->setCache($cache);
    $base=basePath();
    $base=$_SERVER['DOCUMENT_ROOT'];
    $this->options->setBasePath($base);
    $this->options->setCacheable(true);

    $this->options->setUrlParams(array('name', 'lang'));

    if(array_key_exists('lang', $_GET) && $_GET['lang']) {
      $this->options->setLang($_GET['lang']);
    }
    else {
      if (count($this->options->getAcceptedLanguages()) > 1)
	$this->addVariant('Accept-Language');
      $this->options->setLang($this->options->getDefaultLang());
    }

    if(array_key_exists('name', $_GET) && $_GET['name']) {
      $this->options->setName($_GET['name']);
    }
    else {
      $pathToAnalyze=$_SERVER['PATH_TRANSLATED'];
      $prefix=$_SERVER['SCRIPT_FILENAME'];
      if (substr($pathToAnalyze, 0, strlen($prefix)) == $prefix) {
	$pathToAnalyze = substr($pathToAnalyze, strlen($prefix));
      }
      preg_match('/\/([^\/]*)$/', $pathToAnalyze, $groups);
      if ($groups[1]) {
	$this->options->setName($groups[1]);
      }
    }


    $params = $this->master->getElementsByTagName("param");

    foreach ($params as $param) {
      if ($param->getAttribute("type") == "input") {
	$doc = self::getInput($this->master, $param, $this->options);

	$parent = $param->parentNode;
	foreach ($doc->firstChild->childNodes as $child) {
	  $clonedChild = $this->master->importNode($child, true);
	  $parent->insertBefore($clonedChild, $param);
	}
	$parent->removeChild($param);
      }
    }
    $this->master = self::getFiles($this->master, $this->options);
    self::removeCommentsFromDOM($this->master);
    $cache->setMaxAge(0);
  }

  /**
   * Removes all comments from the DOM
   *
   * @param $dom A DOMDocument for processing
   */
  static function removeCommentsFromDOM($dom)
  {
    $xpath = new DOMXPath($dom);
    foreach ($xpath->query('//comment()') as $comment) {
      $comment->parentNode->removeChild($comment);
    }
  }

  function cacheCheck()
  {
    return $this->options->getCacheable();
  }

  /**
   * Generate an appropriate response for this page, eg. 302 NOT
   * MODIFIED or the actual page
   */
  function generateContent()
  {
    //We may need to set and check the contenttype and replace
    //saveHTML with saveXML
    $retVal = new PageContent(options::replacePlaceholders($this->master->saveXML()));
    $retVal->setHeader('Content-Language', $this->options->getLang());
    return $retVal;
  }

  /**
   * Extracts data from a @<param@> tag.
   *
   * @param $param the param tag
   *
   * @return if the type is array, return an array, otherwise return a
   * scalar
   */
  static function getParam($param)
  {
    $param_type = $param->getAttribute("type");
    $param_value;
    if (! $param_type)
      $param_type = "scalar";

    if($param_type == "scalar") {
      $param_subst = $param->getAttribute("subst");
      $param_value = $param->getAttribute("value");
      if ($param_subst) {
	/*
	  $param_value=preg_replace("/name/", $name, $param_subst);
	  $param_value=preg_replace('/lang/', $lang, $param_value);
	*/
      }
    }
    elseif($param_type == "array") {
      $params = $param->getElementsByTagName("param");
      $param_value = array();
      foreach ($param->childNodes as $param) {
	if ($param->nodeType == XML_ELEMENT_NODE) {
	  array_push($param_value, self::getParam($param));
	}
      }
    }
    else {
      throw new UnexpectedValueException("Unknown parameter type '$param_type'");
    }
    return $param_value;
  }

  /**
   * This is the last processing stage for generating a file.
   *
   * @param $doc the document to be worked upon
   * @param $options an Options object for this file.
   *
   * @return This is the same as the input document, fully processed
   */
  static function getFiles($doc, $options) {
    $lang = $options->getLang();
    $conf = $options->getName();

    $toRemove = array();

    $topLevelTags = $doc->getElementsByTagName("toplevel");
    foreach ($topLevelTags as $topLevel) {
      $topLevel->parentNode->removeChild($topLevel);
    }

    $valueDict = array();
    $fragments = array();
    $setters = $doc->getElementsByTagName("set");
    foreach ($setters as $setTag) {
      $key = $setTag->getAttribute("id");
      $type = $setTag->getAttribute("type");
      if ($type == "fragment") {
	$fragments[$key] = $setTag;
      }
      else {
	$value = $setTag->getAttribute("value");
	if ($key && $value) {
	  $valueDict[$key] = $value;
	}
      }
      //We need to iterate in the opposite direction when removing,
      //so best shifting.
      array_unshift($toRemove, $setTag);
    }

    $params = $doc->getElementsByTagName("param");
    foreach ($params as $param) {
      if ($param->getAttribute("type")=="input_config") {
	$id = $param->getAttribute("id");
	if (array_key_exists($id, $valueDict)) {
	  $value = $valueDict[$id];
	  $tmp = new DOMDocument();

	  $tmp->loadXml("<xml>${value}</xml>");
	  $parent = $param->parentNode;
	  $parent->insertBefore(new DOMText($tmp->textContent), $param);
	  //We need to iterate in the opposite direction when removing,
	  //so best shifting.
	  array_unshift($toRemove, $param);
	}
	elseif (array_key_exists($id, $fragments)) {
	  $fragment = $fragments[$id];

	  $cloneFragment = $fragment->cloneNode(true);
	  $insNode = $param;
	  for ($i = $cloneFragment->childNodes->length - 1; $i >= 0; $i--) {
	    $child = $cloneFragment->childNodes->item($i);
	    $child = $child->parentNode->removeChild($child);
	    $insNode = $insNode->parentNode->insertBefore($child, $insNode);
	  }

	  //We need to iterate in the opposite direction when removing,
	  //so best shifting.
	  array_unshift($toRemove, $param);
	}
      }
    }

    foreach($toRemove as $param) {
      $parent = $param->parentNode;
      $parent->removeChild($param);
    }

    $body = getElementByTagName($doc,"html");
    $files = $body->getElementsByTagName("file");

    $toRemove = array();

    foreach ($files as $file) {
      $script = $file->getAttribute("script");
      if ($script) {
	$options->setCacheable(false);
	$src="";
	$cwd = getcwd();

	$matches=array();
	preg_match('/(.*\/)/', $script, $matches);
	$dirname=$matches[0];
	preg_match('/([^\/]*)$/', $script, $matches);
	$filename=$matches[0];
	chdir("${lang}/${dirname}");
	$pipe=popen("php ${filename}","r");
	$file_content = stream_get_contents($pipe);
	chdir("${cwd}");
      }
      else {
	$src = $file->getAttribute("src");
	$fname = $options->getBasePath() . "/${lang}/${src}";
	if (!file_exists($fname) ) {
	  $fname = $options->getBasePath() . "/common/${src}";
	}
	$file_content = $options->getCache()->loadFile($fname);
      }
      if(floatval($file_content)<0) {
	errorPage("Resource not found '${lang}/${src}'");
      }

      $filters = $file->getElementsByTagName("filter");
      foreach($filters as $filter) {
	$func = $filter->getAttribute("function");
	$params = $filter->getElementsByTagName("param");
	$callString = "\$file_content = ${func}(\$file_content, \$options";
	$param_values = array();
	$i = 0;
	foreach ($filter->childNodes as $param) {
	  if ($param->nodeType == XML_ELEMENT_NODE)
	    {
	      $param_value[$i] = self::getParam($param);
	      $callString .= ",\$param_value[$i]";
	      $i++;
	    }
	}
	$callString .= ");";
	eval($callString);
      }
      $ndoc = $doc->createDocumentFragment();

      $parent = $file->parentNode;

      $ndoc->appendXML($file_content);
      $parent->insertBefore($ndoc, $file);

      //We need to iterate in the opposite direction when removing,
      //so best shifting.
      array_unshift($toRemove, $file);
    }
    foreach($toRemove as $param) {
      $parent = $param->parentNode;
      $parent->removeChild($param);
    }

    return $doc;
  }

  /**
   * Follows all include directives recursively for the specified
   * $param an generates an xml file.
   *
   * This function may be used to generate a file which has all the
   * necessary information to determine wether or not we may cache.
   *
   * @param $master The master document to be processed
   * @param $param the input tag to resolve
   * @param $options the options object for this file
   */
  function getInput($master, $param, $options)
  {
    $lang = $options->getLang();
    $name = $param->getAttribute("id");
    $conf = $options->getName();
    if (!$conf)
      $conf = $param->getAttribute("default");

    $confFile = $options->getBasePath() . "/${lang}/${conf}.xml";
    if (! file_exists($confFile) ) {
      $confFile = $options->getBasePath() . "/common/${conf}.xml";
    }

    $options->getCache()->cache_time($confFile);
    $doc = new DOMDocument();
    $doc->load($confFile);

    $toplevel = $doc->getElementsByTagName("toplevel");

    if(! $toplevel->length) {
      errorPage("Resource '${conf}' is not available", 500);
    }

    $includes = $doc->getElementsByTagName("include");
    $recurse = 0;

    while($includes->length > 0) {
      if(++$recurse > MAX_RECURSE) {
	errorPage('Recursion limit exceeded', 500);
      }
      foreach ($includes as $include) {
	$src = $include->getAttribute("src");
	$subdoc = new DOMDocument();
	$subfile = $options->getBasePath() . "/${lang}/${src}";
	if (! file_exists($subfile) ) {
	  $subfile = $options->getBasePath() . "/common/${src}";
	}
	$subdoc->load("$subfile");
	$options->getCache()->cache_time($subfile);
	$parent = $include->parentNode;
	$xml = getElementByTagName($subdoc,"xml");
	foreach($xml->childNodes as $child) {
	  $text = $subdoc->saveHTML($child);
	  $clonedChild = $doc->importNode($child,true);
	  $parent->insertBefore($clonedChild,$include);
	}
	$parent->removeChild($include);
      }
      $includes = $doc->getElementsByTagName("include");
    }

    return $doc;
  }
}
?>