Файл: upload/include/library/phpfox/parse/wiki.class.php
Строк: 426
<?php
/**
 * [PHPFOX_HEADER]
 */
defined('PHPFOX') or exit('NO DICE!');
/**
 * WIKI Parser
 * Clones how a common WIKI works and allows the ability for users
 * to use WIKI code to create pages.
 * 
 * @copyright        [PHPFOX_COPYRIGHT]
 * @author            Raymond Benc
 * @package         Phpfox
 * @version         $Id: wiki.class.php 1668 2010-07-12 08:54:32Z Raymond_Benc $
 */
class Phpfox_Parse_Wiki 
{    
    /**
     * Common regex for HTML.
     *
     * @var array
     */
    private $_sLineRegexes = array(
        'preformat' => '^s(.*?)$',
        'definitionlist' => '^([;:])s*(.*?)$',
        'newline' => '^$',
        'list' => '^([*#]+)(.*?)$',
        'sections' => '^(={1,6})(.*?)(={1,6})$',
        'horizontalrule' => '^----$'
    );
        
    /**
     * Common character regex.
     *
     * @var array
     */
    private    $_sCharRegexes = array(
        'internallink' => '([[(([^]]*?):)?([^]]*?)(|([^]]*?))?]]([a-z]+)?)',
        'externallink' => '([([^]]*?)(s+[^]]*?)?])',
        'emphasize' => '('{2,5})',
        'eliminate' => '(__TOC__|__NOTOC__|__NOEDITSECTION__)',
        'variable' => '({{([^}]*?)}})'
    );    
        
    /**
     * Reference within links.
     *
     * @var string
     */
    private $_sReferenceWiki;
    
    /**
     * Image URI.
     *
     * @var string
     */
    private $_sImageUri;
    
    /**
     * BOOL to ignore images or not.
     *
     * @var bool
     */
    private $_bIgnoreImages = true;
    
    /**
     * Redirect links.
     *
     * @var bool
     */
    private $_bRedirect = false;
    
    /**
     * Format <pre>.
     *
     * @var bool
     */
    private $_bPreformat = false;
    
    /**
     * ARRAY of emphasis tags.
     *
     * @var array
     */
    private $_aEmphasis = array();
    
    /**
     * ARRAY of support nowiki tags.
     *
     * @var array
     */
    private $_aNoWikis = array();
    
    /**
     * <ul> list level types.
     *
     * @var array
     */
    private $_aListLevelTypes = array();
    
    /**
     * Define how many levels a list has.
     *
     * @var int
     */
    private $_iListLevel = 0;
    
    /**
     * Support for <dl>.
     *
     * @var bool
     */
    private $_bDefList = false;
    
    /**
     * Link number.
     *
     * @var int
     */
    private $_iLinkNumber = 0;
    
    /**
     * Supress link breaks.
     *
     * @var bool
     */
    private $_bSuppressLinebreaks = false;
    
    /**
     * Create a link break.
     *
     * @var bool
     */
    private $_bStop = false;
    
    /**
     * Class constructor.
     *
     */
    public function __construct()
    {
    }
    
    /**
     * Parse string and converts WIKI code into HTML.
     *
     * @param string $sText Text to parse.
     * @param string $sTitle Title of this page.
     * @return string Converted text from WIKI to HTML.
     */
    public function parse($sText, $sTitle = '') 
    {        
        $sText = preg_replace_callback('/<nowiki>([sS]*)</nowiki>/i', array(&$this, "_handleSaveNoWiki"), $sText);
        $aLines = explode("n", $sText);
        
        if (preg_match('/^#REDIRECTs+[[(.*?)]]$/', trim($aLines[0]), $aMatches)) 
        {
            $this->_bRedirect = $aMatches[1];
        }
        
        $sOutput = '';
        foreach ($aLines as $iKey => $sLine) 
        {
            $sOutput .= $this->_parseLine($sLine);
        }
        $sOutput = preg_replace_callback('/<nowiki></nowiki>/i', array(&$this, "_handleRestoreNoWiki"), $sOutput);
        return $sOutput;
    }    
    
    /**
     * Parses each line of the string and creates handles.
     *
     * @param string $sLine Line of the string we are parsing.
     * @return string Returns the parsed line.
     */
    private function _parseLine($sLine) 
    {                
        $this->_bStop = false;
        $this->_bStopAll = false;
        $aCalled = array();
        
        $sLine = rtrim($sLine);
        
        foreach ($this->_sLineRegexes as $sFunction => $sRegex) 
        {
            if (preg_match("/$sRegex/i", $sLine, $aMatches)) 
            {
                $aCalled[$sFunction] = true;
                $sFunction = '_handle' . $sFunction;
                $sLine = $this->$sFunction($aMatches);
                if ($this->_bStop || $this->_bStopAll)
                {
                    break;
                }
            }
        }
        
        if (!$this->_bStopAll) 
        {
            $this->_bStop = false;
            foreach ($this->_sCharRegexes as $sFunction => $sRegex) 
            {
                $sLine = preg_replace_callback("/$sRegex/i", array(&$this, "_handle" . $sFunction), $sLine);
                if ($this->_bStop)
                {
                    break;
                }
            }
        }
        
        $isline = (strlen(trim($sLine)) > 0);
        
        if (($this->_iListLevel > 0) && (!isset($aCalled['list'])))
        {
            $sLine = $this->_handleList(false, true) . $sLine;
        }
        
        if ($this->_bDefList && (!isset($aCalled['definitionlist'])))
        {
            $sLine = $this->_handleDefinitionList(false, true) . $sLine;
        }
        
        if ($this->_bPreformat && (!isset($aCalled['preformat'])))
        {
            $sLine = $this->_handlePreFormat(false, true) . $sLine;
        }
        
        if ($isline)
        {
            $this->_bSuppressLinebreaks = ((isset($aCalled['newline']) && $aCalled['newline']) || (isset($aCalled['sections']) && $aCalled['sections']));
        }
        
        return $sLine;
    }    
    
    /**
     * Handles sections (h1, h2, h3 etc...)
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML line.
     */
    private function _handleSections($aMatches) 
    {                
        $iLevel = strlen($aMatches[1]);
        $sContent = $aMatches[2];
        $sContent = trim($sContent);
        $this->_bStop = true;
        
        return $this->emphasize_off() . "nn<p><a name="" . $this->_cleanTitle($sContent) . "" id="" . $this->_cleanTitle($sContent) . ""></a></p>n<h{$iLevel}>{$sContent}</h{$iLevel}>nn";
    }
    
    /**
     * Cleans the title of an item.
     *
     * @param string $sTxt Title of the item.
     * @return string Title all clean.
     */
    private function _cleanTitle($sTxt)
    {
        $sTxt = preg_replace( '/ +/', '_',preg_replace('/[^0-9a-zA-Z ]+/', '', $sTxt));
        
        return $sTxt;
    }
    
    /**
     * Handler for new lines (<br />).
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */
    private function _handleNewline($aMatches) 
    {
        if ($this->_bSuppressLinebreaks)
        {
            return $this->emphasize_off();
        }
        
        $this->_bStop = true;
        return $this->emphasize_off() . "<br /><br />";
    }
    
    /**
     * Handles lists (<ul><li>)
     *
     * @param array $aMatches ARRAY matches from regex.
     * @param bool $bClose TRUE to close the <ul>. FALSE to leave it open.
     * @return string Converted HTML.
     */
    private function _handleList($aMatches, $bClose = false) 
    {
        $aListTypes = array(
            '*' => 'ul',
            '#' => 'ol',
        );        
        
        $iNewLevel = (($bClose) ? 0 : strlen($aMatches[1]));
        
        $sOutput = '';
        while ($this->_iListLevel != $iNewLevel) 
        {
            $sListChar = substr($aMatches[1], -1);                        
            
            $sListType = (isset($aListTypes[$sListChar]) ? $aListTypes[$sListChar] : '');
            
            if ($this->_iListLevel > $iNewLevel) 
            {
                $sListType = '/' . array_pop($this->_aListLevelTypes);
                $this->_iListLevel--;
            } 
            else 
            {
                $this->_iListLevel++;
                array_push($this->_aListLevelTypes, $sListType);
            }
            $sOutput .= "n<{$sListType}>n";
        }
        
        if ($bClose)
        {
            return $sOutput;
        }
        
        $sOutput .= "<li>".$aMatches[2]."</li>n";
        
        return $sOutput;
    }
    
    /**
     * Handle <dl>.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @param bool $bClose TRUE to close the <dl>. FALSE to leave it open.
     * @return string Converted HTML.
     */
    private function _handleDefinitionList($aMatches, $bClose = false) 
    {        
        if ($bClose) 
        {
            $this->_bDefList = false;
            return "</dl>n";
        }        
        
        $sOutput = "";
        if (!$this->_bDefList)
        {
            $sOutput .= "<dl>n";
        }
        $this->_bDefList = true;
        switch($aMatches[1]) 
        {
            case ';':
                $sTerm = $aMatches[2];
                if (strpos($sTerm, ' :') !== false) 
                {
                    list($sTerm, $definition) = explode(':', $sTerm);
                    $sOutput .= "<dt>{$sTerm}</dt><dd>{$definition}</dd>";
                } 
                else 
                {
                    $sOutput .= "<dt>{$sTerm}</dt>";
                }
                break;
            case ':':
                $definition = $aMatches[2];
                $sOutput .= "<dd>{$definition}</dd>n";
                break;
        }
        
        return $sOutput;
    }
    
    /**
     * Handle <pre>.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @param bool $bClose TRUE to close the <pre>. FALSE to leave it open.
     * @return string Converted HTML.
     */    
    private function _handlePreFormat($aMatches, $bClose = false) 
    {
        if ($bClose) 
        {
            $this->_bPreformat = false;
            return "</pre>n";
        }
        
        $this->_bStopAll = true;
        $sOutput = "";
        if (!$this->_bPreformat)
        {
            $sOutput .= "<pre>";
        }
        $this->_bPreformat = true;
        
        $sOutput .= $aMatches[1];
        
        return $sOutput."n";
    }
    
    /**
     * Handle <hr />
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */
    private function _handleHorizontalRule($aMatches) 
    {
        return "<hr />";
    }
    
    /**
     * Handle images (<img>).
     *
     * @param string $sHref Link
     * @param string $sTitle Title
     * @param array $aOptions Extra options for the link.
     * @return string Converted HTML.
     */
    private function _handleImage($sHref, $sTitle, $aOptions) 
    {
        if ($this->_bIgnoreImages)
        {
            return '';
        }
        
        if (!$this->_sImageUri)
        {
            return $sTitle;
        }
        
        $sHref = $this->_sImageUri . $sHref;
        
        $sImageTag = sprintf('<img src="%s" alt="%s" />', $sHref, $sTitle);
        foreach ($aOptions as $iKey => $sOption) 
        {
            switch($sOption) 
            {
                case 'frame':
                    $sImageTag = sprintf('<div style="float: right; background-color: #F5F5F5; border: 1px solid #D0D0D0; padding: 2px">%s<div>%s</div></div>', $sImageTag, $sTitle);
                    break;
                case 'right':
                    $sImageTag = sprintf('<div style="float: right">%s</div>', $sImageTag);
                    break;
            }
        }
        
        return $sImageTag;
    }
    
    /**
     * Handles internal links.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */
    private function _handleInternalLink($aMatches) 
    {
        $bNoLink = false;    
        $sHref = $aMatches[4];
        $sTitle = (isset($aMatches[6]) ? $aMatches[6] : $sHref . (isset($aMatches[7]) ? $aMatches[7] : ''));
        $namespace = (isset($aMatches[3]) ? $aMatches[3] : false);
        if ($namespace == 'Image') 
        {
            $aOptions = explode('|', $sTitle);
            $sTitle = array_pop($aOptions);
            
            return $this->_handleImage($sHref, $sTitle, $aOptions);
        }        
        
        $sTitle = preg_replace('/(.*?)/', '', $sTitle);
        $sTitle = preg_replace('/^.*?:/', '', $sTitle);
        
        if ($this->_sReferenceWiki) 
        {
            $sHref = $this->_sReferenceWiki . ($namespace ? $namespace . ':' : '') . ucfirst(str_replace(' ', '_', $sHref));
        } 
        else 
        {
            $bNoLink = true;
        }
        if ($bNoLink)
        {
            return $sTitle;
        }
        
        $bNewWindow = true;
        
        return sprintf('<a href="%s"%s>%s</a>', $sHref, ($bNewWindow ? ' target="_blank"' : ''), $sTitle);
    }
    
    /**
     * Handles external links.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */    
    private function _handleExternalLink($aMatches) 
    {
        $sHref = $aMatches[2];
        $sTitle = (isset($aMatches[3]) ? $aMatches[3] : false);
        if (!$sTitle) 
        {
            $this->_iLinkNumber++;
            $sTitle = "[{$this->_iLinkNumber}]";
        }
        $bNewWindow = true;
        
        return sprintf('<a href="%s"%s>%s</a>', $sHref, ($bNewWindow ? ' target="_blank"' : ''), $sTitle);        
    }
    
    /**
     * Handles emphasize blocks (em, strong).
     *
     * @param int Type of HTML.
     * @return string Converted HTML.
     */    
    private function emphasize($iAmount) 
    {
        $aAmounts = array(
            2 => array('<em>','</em>'),
            3 => array('<strong>','</strong>'),
            4 => array('<strong>','</strong>'),
            5 => array('<em><strong>','</strong></em>')
        );
        
        $sOutput = "";
        if ((isset($this->_aEmphasis[$iAmount]) && !$this->_aEmphasis[$iAmount]) && (isset($this->_aEmphasis[$iAmount-1]) && $this->_aEmphasis[$iAmount-1])) 
        {
            $iAmount--;
            $sOutput = "'";
        }
        $sOutput .= $aAmounts[$iAmount][(int) (isset($this->_aEmphasis[$iAmount]) ? $this->_aEmphasis[$iAmount] : 0)];
        $this->_aEmphasis[$iAmount] = (isset($this->_aEmphasis[$iAmount]) ? !$this->_aEmphasis[$iAmount] : true);
        
        return $sOutput;
    }
    
    /**
     * Handles emphasize blocks (em, strong).
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */        
    private function _handleEmphasize($aMatches) 
    {
        return $this->emphasize(strlen($aMatches[1]));
    }
    
    /**
     * Checks emphasize diff.
     *
     * @return string Converted HTML.
     */    
    private function emphasize_off() 
    {
        $sOutput = "";
        
        if (!is_array($this->_aEmphasis))
        {
            return $sOutput;
        }
        
        foreach ($this->_aEmphasis as $iAmount => $sState) 
        {
            if ($sState)
            {
                $sOutput .= $this->emphasize($iAmount);
            }
        }
        
        return $sOutput;
    }
    
    /**
     * Adds empty string.
     *
     * @return string Empty string.
     */        
    private function _handleEliminate($aMatches) 
    {
        return '';
    }
    
    /**
     * Converts WIKI variables.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */
    private function _handleVariable($aMatches) 
    {
        switch($aMatches[2]) 
        {
            case 'CURRENTMONTH': return date('m');
            case 'CURRENTMONTHNAMEGEN':
            case 'CURRENTMONTHNAME': return date('F');
            case 'CURRENTDAY': return date('d');
            case 'CURRENTDAYNAME': return date('l');
            case 'CURRENTYEAR': return date('Y');
            case 'CURRENTTIME': return date('H:i');
            case 'NUMBEROFARTICLES': return 0;
            case 'PAGENAME': return '';
            case 'NAMESPACE': return 'None';
            case 'SITENAME': return $_SERVER['HTTP_HOST'];
            default: return '';    
        }
    }
    
    /**
     * Handles <nowiki>.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */    
    private function _handleSaveNoWiki($aMatches) 
    {
        array_push($this->_aNoWikis, $aMatches[1]);
        
        return "<nowiki></nowiki>";
    }
    
    /**
     * Restores wiki.
     *
     * @param array $aMatches ARRAY matches from regex.
     * @return string Converted HTML.
     */
    private function _handleRestoreNoWiki($aMatches) 
    {
        return array_pop($this->_aNoWikis);
    }
}
?>