<?php if (!defined('PmWiki')) exit();
/*
 * IncludeXML - Include processed XML into PmWiki 2.x pages
 * Copyright 2005-2007 by D.Faure (dfaure@cpan.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in 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:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 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
 * AUTHORS 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.
 *
 * See http://www.pmwiki.org/wiki/Cookbook/IncludeXML for info.
 */
$RecipeInfo['IncludeXML']['Version'] = '20070621';

include_once("$FarmD/cookbook/extlinkedres.php");
if($RecipeInfo['LinkedResourceExtras']['Version'] < '20070215')
  Abort('?IncludeXML requires an updated LinkedResourceExtras');

Markup('includexml', 'directives',
  "/\\(:includexml(\\s+.*)?\\s*:\\)/e",
  "IncludeXMLTransform(\$pagename,'',PSS('$1'),'')");

Markup('xsl', '>[=',
  "/\\(:xsl(\\s+.*)?\\s*:\\)\\s*$KeepToken(\\d.*?)$KeepToken/e",
  "IncludeXMLTransform(\$pagename,'xsl',PSS('$1'),\$GLOBALS['KPV']['$2'])");

Markup('xslend', '>xsl',
  "/\\(:xsl(\\s+.*)?\\s*:\\)\n(?s:(.*?))\\(:xslend:\\)/e",
  "IncludeXMLTransform(\$pagename,'xsl',PSS('$1'),PSS('$2'))");

Markup('xml', '>[=',
  "/\\(:xml(\\s+.*)?\\s*:\\)\\s*$KeepToken(\\d.*?)$KeepToken/e",
  "IncludeXMLTransform(\$pagename,'xml',PSS('$1'),\$GLOBALS['KPV']['$2'])");

Markup('xmlend', '>xml',
  "/\\(:xml(\\s+.*)?\\s*:\\)\n(?s:(.*?))\\(:xmlend:\\)/e",
  "IncludeXMLTransform(\$pagename,'xml',PSS('$1'),PSS('$2'))");

SDVA($IncludeXMLMsgFmt, array(
'denied' => "<h3 class='wikimessage'>$[Unable to bind to denied resource.]</h3>",
'notfound' => "<h3 class='wikimessage'>$[XML data not found]</h3>",
));
SDVA($IncludeXMLOptions, array(
'xml' => array('inc' => false, 'ctx' => null),
'xsl' => array('inc' => false, 'ctx' => null),
'markup' => true,
));
SDV($IncludeXMLDecodeFunc, create_function('$data', 'return $data;'));

function IncludeXMLTransform($pagename, $type, $args, $data = '') {
  global $IncludeXMLMsgFmt, $IncludeXMLDecodeFunc, $IncludeXMLOptions;
  $opt = ParseArgs($args);
  $xslt_args = array();
  if($type) {
    $o = array_flip(array('xml', 'xsl')); 
    unset($o[$type]);

    $opt[$type] = '-';
    $xslt_args["/_$type"] =
    # undo PmWiki's htmlspecialchars conversion and trim
      str_replace(array('<:vspace>', '&lt;', '&gt;', '&amp;'),
                  array('', '<', '>', '&'), trim($data));
    $rsc = key($o);
    if(IncludeXMLReadData($pagename, $data2, $opt[$rsc],
                          $IncludeXMLOptions[$rsc]['inc'],
                          $IncludeXMLOptions[$rsc]['ctx']))
      $xslt_args["/_$rsc"] = $data2;
    else return $data2;
  }
  else {
    if(!isset($opt['xml'])) { $opt['xml'] = $opt['src']; unset($opt['src']); }

    if(IncludeXMLReadData($pagename, $data, $opt['xml'],
                          $IncludeXMLOptions['xml']['inc'],
                          $IncludeXMLOptions['xml']['ctx']))
      $xslt_args['/_xml'] = $data;
    else return $data;
    if(IncludeXMLReadData($pagename, $data, $opt['xsl'],
                          $IncludeXMLOptions['xsl']['inc'],
                          $IncludeXMLOptions['xsl']['ctx']))
      $xslt_args['/_xsl'] = $data;
    else return $data;
  }
  if(!$xslt_args['/_xml']) return FmtPageName($IncludeXMLMsgFmt['notfound'], $pagename);
  if(!$xslt_args['/_xsl']) return Keep("<code><pre>"
                                     . htmlentities($xslt_args['/_xml'])
                                     . "</pre></code>");
  $redoMarkup = ! @in_array('markup', $opt['-']) && $IncludeXMLOptions['markup'];
  $debug = @in_array('debug', $opt['+']);
  unset($opt['#'], $opt['+'], $opt['-']);

  $xh = xslt_create();
  $result = $IncludeXMLDecodeFunc(xslt_process($xh, 'arg:/_xml', 'arg:/_xsl',
                                               NULL, $xslt_args, $opt));
  xslt_free($xh);

  if($debug) return Keep("<code><pre>" . htmlentities($result) . "</pre></code>");
  if($redoMarkup) return PRR(PVSE($result));
  return Keep($result);
}

function IncludeXMLReadData($pagename, &$data, $tgt, $inc, $ctx) {
  global $EnableExternalResource, $IncludeXMLMsgFmt,
         $IncludeXMLUseIncludePath;
  
  if(!ResolveLinkResource($pagename, $tgt, $url, $txt, $upname, $filepath, $size, $mime)) {
    $data = isset($filepath) ? Keep($url) : '';
    return false;
  }
  if(!$filepath) {
    if(!IsEnabled($EnableExternalResource, true)) {
      $data = FmtPageName($IncludeXMLMsgFmt['denied'], $pagename);
      return false;
    }
    $filepath = $url;
  }
  $data = IncludeXML_file_get_contents($filepath, $inc, $ctx);
  return true;
}

// Emulate potentially missing functions and old xslt library functions
//
// xslt_* functions from jw@jwscripts.com's php manual notes.
//
// file_get_contents: PHP 4 >= 4.3.0, PHP 5 - file_put_contents: PHP 5
// functions adapted from http://pear.php.net/package/PHP_Compat code. See there
// for original credits.
//
if(PHP_VERSION >= 5) {
  function xslt_create() { return new XsltProcessor(); }
  function xslt_free($xsltproc) { unset($xsltproc); }
  function xslt_process($xsltproc, $xml_arg, $xsl_arg, $xslcontainer = null,
                        $args = null, $params = null) {
    // Start with preparing the arguments
    $xml_arg = str_replace('arg:', '', $xml_arg);
    $xsl_arg = str_replace('arg:', '', $xsl_arg);

    // Load the xml document and the xsl template
    // creating instances of the DomDocument class
    $xml = DOMDocument::loadXML($args[$xml_arg]);
    $xsl = DOMDocument::loadXML($args[$xsl_arg]);

    // Load the xsl template
    $xsltproc->importStyleSheet($xsl);

    // Set parameters when defined
    if($params)
      foreach($params as $param => $value)
        $xsltproc->setParameter("", $param, $value);

    // Start the transformation
    $processed = $xsltproc->transformToXML($xml);

    // Put the result in a file when specified
    if($xslcontainer)
      return IncludeXML_file_put_contents($xslcontainer, $processed);
    else
      return $processed;
  }

  function IncludeXML_file_get_contents($filename, $incpath = false, $resource_context = null) {
    return file_get_contents($filename, $incpath, $resource_context);
  }

  function IncludeXML_file_put_contents($filename, $content, $flags = null, $resource_context = null) {
    return file_put_contents($filename, $content, $flags, $resource_context);
  }
} else {
  define('INCLUDEXML_FILE_GET_CONTENTS_MAX_REDIRECTS', 5);

  function IncludeXML_file_get_contents($filename, $incpath = false, $resource_context = null) {
    $opts = is_resource($resource_context) &&
            function_exists('stream_context_get_options') ?
      stream_context_get_options($resource_context) : null;
    
    $colon_pos = strpos($filename, '://');
    $wrapper = $colon_pos === false ? 'file' : substr($filename, 0, $colon_pos);
    $opts = (empty($opts) || empty($opts[$wrapper])) ? array() : $opts[$wrapper];

    switch($wrapper) {
    case 'http':
      $max_redirects = (isset($opts[$wrapper]['max_redirects'])
        ? $opts[$proto]['max_redirects']
        : INCLUDEXML_FILE_GET_CONTENTS_MAX_REDIRECTS);
      for($i = 0; $i < $max_redirects; $i++) {
        $contents = IncludeXML_file_get_contents_http_get_contents_helper($filename, $opts);
        if(is_array($contents)) {
          // redirected
          $filename = rtrim($contents[1]);
          $contents = '';
          continue;
        }
        return $contents;
      }
      user_error('redirect limit exceeded', E_USER_WARNING);
      return;
    case 'ftp':
    case 'https':
    case 'ftps':
    case 'socket':
        // tbc               
    }

    if(false === $fh = fopen($filename, 'rb', $incpath)) {
      user_error('failed to open stream: No such file or directory', E_USER_WARNING);
      return false;
    }

    clearstatcache();
    if($fsize = @filesize($filename))
      $data = fread($fh, $fsize);
    else {
      $data = '';
      while (!feof($fh))
        $data .= fread($fh, 8192);
    }

    fclose($fh);
    return $data;
  }

  function IncludeXML_file_get_contents_http_get_contents_helper($filename, $opts) {
    $path = parse_url($filename);
    if(!isset($path['host'])) return '';
    $fp = fsockopen($path['host'], 80, $errno, $errstr, 4);
    if(!$fp) return '';
    if(!isset($path['path'])) $path['path'] = '/';
    
    $headers = array('Host'      => $path['host'],
                     'Conection' => 'close');

    // enforce some options (proxy isn't supported) 
    $opts_defaults = array('method'          => 'GET',
                           'header'          => null,
                           'user_agent'      => ini_get('user_agent'),
                           'content'         => null,
                           'request_fulluri' => false);

    foreach($opts_defaults as $key => $value)
      if(!isset($opts[$key])) $opts[$key] = $value;
    $opts['path'] = $opts['request_fulluri'] ? $filename : $path['path'];

    // build request
    $request = $opts['method'] . ' ' . $opts['path'] . " HTTP/1.0\r\n";
  
    // build headers
    if(isset($opts['header'])) {
      $optheaders = explode("\r\n", $opts['header']);
      for($i = count($optheaders); $i--;) {
        $sep_pos = strpos($optheaders[$i], ': ');
        $headers[substr($optheaders[$i], 0, $sep_pos)] = substr($optheaders[$i], $sep_pos + 2);
      }
    }
    foreach($headers as $key => $value)
      $request .= "$key: $value\r\n";
    $request .= "\r\n" . $opts['content'];
      
    // make request
    fputs($fp, $request);
    $response = '';
    while(!feof($fp))
      $response .= fgets($fp, 8192);
    fclose($fp);    
    $content_pos = strpos($response, "\r\n\r\n");

    // recurse for redirects
    if(preg_match('/^Location: (.*)$/mi', $response, $matches)) return $matches;
    return ($content_pos != -1 ?  substr($response, $content_pos + 4) : $response);
  }

  if(!defined('FILE_USE_INCLUDE_PATH')) define('FILE_USE_INCLUDE_PATH', 1);
  if(!defined('LOCK_EX'))               define('LOCK_EX', 2);
  if(!defined('FILE_APPEND'))           define('FILE_APPEND', 8);

  function IncludeXML_file_put_contents($filename, $content, $flags = null, $resource_context = null) {
    if(!is_scalar($content)) {
      user_error('file_put_contents() The 2nd parameter should be either a string or an array',
                 E_USER_WARNING);
      return false;
    }

    $fh = @fopen($filename,
                 ($flags & FILE_APPEND) ? 'a' : 'wb',
                 ($flags & FILE_USE_INCLUDE_PATH) ? true : false,
                 $resource_context);
    if(!$fh) {
      user_error('file_put_contents() failed to open stream: Permission denied',
                 E_USER_WARNING);
      return false;
    }

    // Attempt to get an exclusive lock
    if(($flags & LOCK_EX) && !flock($fh, LOCK_EX)) return false;

    // Write to the file
    if(is_array($content)) $content = implode('', $content);
    $length = strlen($content);
    if(($bytes = @fwrite($fh, $content)) === false) {
      user_error(sprintf('file_put_contents() Failed to write %d bytes to %s',
                         $length, $filename), E_USER_WARNING);
      return false;
    }

    @fclose($fh);

    // Check all the data was written
    if($bytes != $length) {
      user_error(sprintf('file_put_contents() Only %d of %d bytes written, possibly out of free disk space.',
                         $bytes, $length), E_USER_WARNING);
      return false;
    }
    return $bytes;
  }
}