includes/wikiformat.php
author Dan
Sat, 17 Jan 2009 15:16:36 -0500
changeset 817 880c4b7eb658
parent 801 eb8b23f11744
child 1027 98c052fc3337
permissions -rw-r--r--
SECURITY: Fix XSS under IE in closing tags (shared sanitizer)

<?php

/*
 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
 * Version 1.1.6 (Caoineag beta 1)
 * Copyright (C) 2006-2008 Dan Fuhry
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 */

/**
 * Parse structured wiki text and render into arbitrary formats such as XHTML.
 *
 * PHP versions 4 and 5
 *
 * @category   Text
 * @package    Text_Wiki
 * @author     Paul M. Jones <pmjones@php.net>
 * @license    http://www.gnu.org/licenses/lgpl.html
 * @version    CVS: $Id: Wiki.php,v 1.44 2006/03/02 04:04:59 justinpatrin Exp $
 * @link       http://wiki.ciaweb.net/yawiki/index.php?area=Text_Wiki
 *
 * This code was modified for use in Enano. The Text_Wiki engine is licensed
 * under the GNU Lesser General Public License; see
 * http://www.gnu.org/licenses/lgpl.html for details.
 *
 */

require_once ENANO_ROOT.'/includes/wikiengine/Parse.php';
require_once ENANO_ROOT.'/includes/wikiengine/Render.php';

class Text_Wiki {

  var $rules = array(
        'Prefilter',
        'Delimiter',
        'Code',
        'Function',
        'Html',
        'Raw',
        'Include',
        'Embed',
        'Anchor',
        'Heading',
        'Toc',
        'Horiz',
        'Break',
        'Blockquote',
        'List',
        'Deflist',
        'Table',
        'Image',
        'Phplookup',
        'Center',
        'Newline',
        'Paragraph',
        'Url',
        'Freelink',
        'Interwiki',
        'Wikilink',
        'Colortext',
        'Strong',
        'Bold',
        'Emphasis',
        'Italic',
        'Underline',
        'Tt',
        'Superscript',
        'Subscript',
        'Revise',
        'Tighten'
    );

    var $disable = array(
        'Html',
        'Include',
        'Embed',
        'Tighten',
        'Image',
        'Wikilink'
    );

    var $parseConf = array();

    var $renderConf = array(
        'Docbook' => array(),
        'Latex' => array(),
        'Pdf' => array(),
        'Plain' => array(),
        'Rtf' => array(),
        'Xhtml' => array()
    );

    var $formatConf = array(
        'Docbook' => array(),
        'Latex' => array(),
        'Pdf' => array(),
        'Plain' => array(),
        'Rtf' => array(),
        'Xhtml' => array()
    );
    var $delim = "\xFF";
    var $tokens = array();
    var $_countRulesTokens = array();
    var $source = '';
    var $parseObj = array();
    var $renderObj = array();
    var $formatObj = array();
    var $path = array(
        'parse' => array(),
        'render' => array()
    );
    var $_dirSep = DIRECTORY_SEPARATOR;
    function Text_Wiki($rules = null)
    {
        if (is_array($rules)) {
            $this->rules = $rules;
        }
        
        global $plugins;
        // This code can be run from the installer, so in some cases $plugins
        // isn't initted. (Bug in 1.1.2, fixed for 1.1.3)
        if ( is_object($plugins) )
        {
          $code = $plugins->setHook('text_wiki_construct');
          foreach ( $code as $cmd )
          {
              eval($cmd);
          }
        }

        $this->addPath(
            'parse',
            $this->fixPath(ENANO_ROOT) . 'includes/wikiengine/Parse/Default/'
        );
        $this->addPath(
            'render',
            $this->fixPath(ENANO_ROOT) . 'includes/wikiengine/Render/'
        );

    }

    public static function singleton($parser = 'Default', $rules = null)
    {
        static $only = array();
        if (!isset($only[$parser])) {
            $ret = Text_Wiki::factory($parser, $rules);
            if (!$ret) {
                return $ret;
            }
            $only[$parser] =& $ret;
        }
        return $only[$parser];
    }

    public static function factory($parser = 'Default', $rules = null)
    {
        $d=getcwd();
        chdir(ENANO_ROOT);
        
        $class = 'Text_Wiki_' . $parser;
        $c2 = $parser;
        $file = ENANO_ROOT . '/includes/wikiengine/' . str_replace('_', '/', $c2).'.php';
        if (!class_exists($class)) {
            $fp = @fopen($file, 'r', true);
            if ($fp === false) {
                die_semicritical('Wiki formatting engine error', '<p>Could not find file '.$file.' in include_path</p>');
            }
            fclose($fp);
            include_once($file);
            if (!class_exists($class)) {
                die_semicritical('Wiki formatting engine error', '<p>Class '.$class.' does not exist after including '.$file.'</p>');
            }
        }
        
        chdir($d);

        $obj = new $class($rules);
        return $obj;
    }

    function setParseConf($rule, $arg1, $arg2 = null)
    {
        $rule = ucwords(strtolower($rule));

        if (! isset($this->parseConf[$rule])) {
            $this->parseConf[$rule] = array();
        }

                                if (is_array($arg1)) {
            $this->parseConf[$rule] = $arg1;
        } else {
            $this->parseConf[$rule][$arg1] = $arg2;
        }
    }

    function getParseConf($rule, $key = null)
    {
        $rule = ucwords(strtolower($rule));

                if (! isset($this->parseConf[$rule])) {
            return null;
        }

                if (is_null($key)) {
            return $this->parseConf[$rule];
        }

                if (isset($this->parseConf[$rule][$key])) {
                        return $this->parseConf[$rule][$key];
        } else {
                        return null;
        }
    }

    function setRenderConf($format, $rule, $arg1, $arg2 = null)
    {
        $format = ucwords(strtolower($format));
        $rule = ucwords(strtolower($rule));

        if (! isset($this->renderConf[$format])) {
            $this->renderConf[$format] = array();
        }

        if (! isset($this->renderConf[$format][$rule])) {
            $this->renderConf[$format][$rule] = array();
        }

                                if (is_array($arg1)) {
            $this->renderConf[$format][$rule] = $arg1;
        } else {
            $this->renderConf[$format][$rule][$arg1] = $arg2;
        }
    }

    function getRenderConf($format, $rule, $key = null)
    {
        $format = ucwords(strtolower($format));
        $rule = ucwords(strtolower($rule));

        if (! isset($this->renderConf[$format]) ||
            ! isset($this->renderConf[$format][$rule])) {
          return null;
        }

        if (is_null($key)) {
          return $this->renderConf[$format][$rule];
        }

        if (isset($this->renderConf[$format][$rule][$key])) {
          return $this->renderConf[$format][$rule][$key];
        } else {
          return null;
        }

    }

    function setFormatConf($format, $arg1, $arg2 = null)
    {
      if (! is_array($this->formatConf[$format])) {
        $this->formatConf[$format] = array();
      }

      if (is_array($arg1)) {
        $this->formatConf[$format] = $arg1;
      } else {
        $this->formatConf[$format][$arg1] = $arg2;
      }
    }

    function getFormatConf($format, $key = null)
    {
      if (! isset($this->formatConf[$format])) {
        return null;
      }

      if (is_null($key)) {
        return $this->formatConf[$format];
      }

      if (isset($this->formatConf[$format][$key])) {
        return $this->formatConf[$format][$key];
      } else {
        return null;
      }
    }

    function insertRule($name, $tgt = null)
    {
      $name = ucwords(strtolower($name));
      if (! is_null($tgt)) {
        $tgt = ucwords(strtolower($tgt));
      }
      if (in_array($name, $this->rules)) {
        return null;
      }

      if (! is_null($tgt) && $tgt != '' &&
        ! in_array($tgt, $this->rules)) {
        return false;
      }

      if (is_null($tgt)) {
        $this->rules[] = $name;
        return true;
      }

      if ($tgt == '') {
        array_unshift($this->rules, $name);
        return true;
      }

      $tmp = $this->rules;
      $this->rules = array();

      foreach ($tmp as $val) {
        $this->rules[] = $val;
        if ($val == $tgt) {
          $this->rules[] = $name;
        }
      }

      return true;
    }

    function deleteRule($name)
    {
      $name = ucwords(strtolower($name));
      $key = array_search($name, $this->rules);
      if ($key !== false) {
        unset($this->rules[$key]);
      }
    }

    function changeRule($old, $new)
    {
      $old = ucwords(strtolower($old));
      $new = ucwords(strtolower($new));
      $key = array_search($old, $this->rules);
      if ($key !== false) {
        $this->deleteRule($new);
        $this->rules[$key] = $new;
      }
    }

    function enableRule($name)
    {
      $name = ucwords(strtolower($name));
      $key = array_search($name, $this->disable);
      if ($key !== false) {
        unset($this->disable[$key]);
      }
    }

    function disableRule($name)
    {
      $name = ucwords(strtolower($name));
      $key = array_search($name, $this->disable);
      if ($key === false) {
        $this->disable[] = $name;
      }
    }

    function transform($text, $format = 'Xhtml')
    {
      $this->parse($text);
      return $this->render($format);
    }

    function parse($text)
    {
      $this->source = $text;

      $this->tokens = array();
      $this->_countRulesTokens = array();

      foreach ($this->rules as $name) {
        if (! in_array($name, $this->disable)) {
          $this->loadParseObj($name);

          if (is_object($this->parseObj[$name])) {
            $this->parseObj[$name]->parse();
          }
          // For debugging
          // echo('<p>' . $name . ':</p><pre>'.htmlspecialchars($this->source).'</pre>');
        }
      }
    }

    function render($format = 'Xhtml')
    {
      $format = ucwords(strtolower($format));

      $output = '';

      $in_delim = false;

      $key = '';

      $result = $this->loadFormatObj($format);
      if ($this->isError($result)) {
        return $result;
      }
      
      if (is_object($this->formatObj[$format])) {
        $output .= $this->formatObj[$format]->pre();
      }

      foreach (array_keys($this->_countRulesTokens) as $rule) {
        $this->loadRenderObj($format, $rule);
      }
      
      $k = strlen($this->source);
      for ($i = 0; $i < $k; $i++) {

        $char = $this->source{$i};

        if ($in_delim) {

          if ($char == $this->delim) {

            $key = (int)$key;
            $rule = $this->tokens[$key][0];
            $opts = $this->tokens[$key][1];
            $output .= $this->renderObj[$rule]->token($opts);
            $in_delim = false;

          } else {

            $key .= $char;

          }

        } else {

          if ($char == $this->delim) {
            $key = '';
            $in_delim = true;
          } else {
            $output .= $char;
          }
        }
      }

      if (is_object($this->formatObj[$format])) {
        $output .= $this->formatObj[$format]->post();
      }

      return $output;
    }

    function getSource()
    {
      return $this->source;
    }

    function getTokens($rules = null)
    {
        if (is_null($rules)) {
            return $this->tokens;
        } else {
            settype($rules, 'array');
            $result = array();
            foreach ($this->tokens as $key => $val) {
                if (in_array($val[0], $rules)) {
                    $result[$key] = $val;
                }
            }
            return $result;
        }
    }

    function addToken($rule, $options = array(), $id_only = false)
    {
                                static $id;
        if (! isset($id)) {
            $id = 0;
        } else {
            $id ++;
        }

                settype($options, 'array');

                $this->tokens[$id] = array(
            0 => $rule,
            1 => $options
        );
        if (!isset($this->_countRulesTokens[$rule])) {
            $this->_countRulesTokens[$rule] = 1;
        } else {
            ++$this->_countRulesTokens[$rule];
        }

                if ($id_only) {
                        return $id;
        } else {
                        return $this->delim . $id . $this->delim;
        }
    }

    function setToken($id, $rule, $options = array())
    {
        $oldRule = $this->tokens[$id][0];
                $this->tokens[$id] = array(
            0 => $rule,
            1 => $options
        );
        if ($rule != $oldRule) {
            if (!($this->_countRulesTokens[$oldRule]--)) {
                unset($this->_countRulesTokens[$oldRule]);
            }
            if (!isset($this->_countRulesTokens[$rule])) {
                $this->_countRulesTokens[$rule] = 1;
            } else {
                ++$this->_countRulesTokens[$rule];
            }
        }
    }

    function loadParseObj($rule)
    {
        $rule = ucwords(strtolower($rule));
        $file = $rule . '.php';
        $class = "Text_Wiki_Parse_$rule";

        if (! class_exists($class)) {
            $loc = $this->findFile('parse', $file);
            if ($loc) {
                                include_once $loc;
            } else {
                                $this->parseObj[$rule] = null;
                                return $this->error(
                    "Parse rule '$rule' not found"
                );
            }
        }

        $this->parseObj[$rule] = new $class($this);

    }

    function loadRenderObj($format, $rule)
    {
        $format = ucwords(strtolower($format));
        $rule = ucwords(strtolower($rule));
        $file = "$format/$rule.php";
        $class = "Text_Wiki_Render_$format" . "_$rule";

        if (! class_exists($class)) {
                        $loc = $this->findFile('render', $file);
            if ($loc) {
                                include_once $loc;
            } else {
                return $this->error(
                    "Render rule '$rule' in format '$format' not found"
                );
            }
        }

        $this->renderObj[$rule] = new $class($this);
    }

    function loadFormatObj($format)
    {
        $format = ucwords(strtolower($format));
        $file = $format . '.php';
        $class = "Text_Wiki_Render_$format";

        if (! class_exists($class)) {
            $loc = $this->findFile('render', $file);
            if ($loc) {
                include_once $loc;
            } else {
                return $this->error(
                    "Rendering format class '$class' not found"
                );
            }
        }

        $this->formatObj[$format] = new $class($this);
    }

    function addPath($type, $dir)
    {
        $dir = $this->fixPath($dir);
        if (! isset($this->path[$type])) {
            $this->path[$type] = array($dir);
        } else {
            array_unshift($this->path[$type], $dir);
        }
    }

    function getPath($type = null)
    {
        if (is_null($type)) {
            return $this->path;
        } elseif (! isset($this->path[$type])) {
            return array();
        } else {
            return $this->path[$type];
        }
    }

    function findFile($type, $file)
    {
      $set = $this->getPath($type);

      foreach ($set as $path) {
            $fullname = $path . $file;
            if (file_exists($fullname) && is_readable($fullname)) {
                return $fullname;
            }
        }

      return false;
    }

    function fixPath($path)
    {
        $len = strlen($this->_dirSep);

        if (! empty($path) &&
            substr($path, -1 * $len, $len) != $this->_dirSep)    {
            return $path . $this->_dirSep;
        } else {
            return $path;
        }
    }

    function &error($message)
    {
        die($message);
    }

    function isError(&$obj)
    {
        return ( @get_class($obj) == 'PEAR_Error' );
    }
}

?>