Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Markdown/Wiki/Link.php
<?php
/**
 * 
 * Replaces wiki and interwiki links in source text with XHTML anchors.
 * 
 * Wiki links are in this format ...
 * 
 *     [[wiki page]]
 *     [[wiki page #anchor]]
 *     [[wiki page]]s
 *     [[wiki page | display this instead]]
 *     [[wiki page #anchor | ]]
 * 
 * The "wiki page" name is normalized to "Wiki_page".  The last 
 * example, the one with the blank display text, will not display
 * the anchor fragment.
 * 
 * Page links are replaced with encoded placeholders.  At cleanup()
 * time, the placeholders are transformed into XHTML anchors.
 * 
 * This plugin also supports Interwiki links, in this format ...
 * 
 *     [[site::page]]
 *     [[site::page #anchor]]
 *     [[site::page]]s
 *     [[site::page | display this instead]]
 *     [[site::page #anchor | ]]
 * 
 * Site prefixes and page names are **not** normalize.  The last
 * example, the one with the blank display text, will not display 
 * the site prefix or the anchor fragment.
 * 
 * Interwiki links are replaced with HTML immediately and are not
 * checked for existence.
 * 
 * @category Solar
 * 
 * @package Solar_Markdown_Wiki
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Link.php 3988 2009-09-04 13:51:51Z pmjones $
 * 
 */
class Solar_Markdown_Wiki_Link extends Solar_Markdown_Plugin
{
    /**
     * 
     * This is a span plugin.
     * 
     * @var bool
     * 
     */
    protected $_is_span = true;
    
    /**
     * 
     * Runs during the cleanup() phase.
     * 
     * @var bool
     * 
     */
    protected $_is_cleanup = true;
    
    /**
     * 
     * Array of which pages exist and which don't.
     * 
     * Format is page name => true/false.
     * 
     * @var array
     * 
     */
    protected $_pages;
    
    /**
     * 
     * Array of information for each link found in the source text.
     * 
     * Each element is an array with these keys ...
     * 
     * `norm`
     * : The normalized form of the page name.
     * 
     * `page`
     * : The page name as entered in the source text.
     * 
     * `frag`
     * : A fragment anchor for the target page (for example, "#example").
     * 
     * `text`
     * : The text to display in place of the page name.
     * 
     * `atch`
     * : Attached suffix text to go on the end of the displayed text.
     * 
     * @var array
     * 
     */
    protected $_links;
    
    /**
     * 
     * Running count of $this->_links, so we don't have to call count()
     * on it all the time.
     * 
     * @var int
     * 
     */
    protected $_count = 0;
    
    /**
     * 
     * The name of this class, for identifying encoded keys in the
     * source text.
     * 
     * @var string
     * 
     */
    protected $_class;
    
    /**
     * 
     * Attribs for 'read' and 'add' style links.
     * 
     * Note that 'href' is special, in that it is an sprintf() format 
     * string.
     * 
     * @var array
     * 
     */
    protected $_attribs = array(
        'read' => array(
            'href' => '/wiki/read/%s'
        ),
        'add' => array(
            'href' => '/wiki/add/%s'
        ),
    );
    
    /**
     * 
     * Array of interwiki site names to base hrefs.
     * 
     * Interwiki href values are actually sprintf() strings, where %s
     * will be replaced with the page requested at the interwiki site.
     * For example, this key-value pair ...
     * 
     *     'php' => 'http://php.net/%s'
     * 
     * ... means that ``[[php::print()]]`` will become a link to
     * ``http://php.net/print()``.
     * 
     * @var array
     * 
     */
    protected $_interwiki = array(
        'amazon' => 'http://amazon.com/s?keywords=%s',
        'ask'    => 'http://www.ask.com/web?q=%s',
        'google' => 'http://www.google.com/search?q=%s',
        'imdb'   => 'http://imdb.com/find?s=all&q=%s',
        'php'    => 'http://php.net/%s',
    );
    
    /**
     * 
     * Callback to check if pages linked from the source text exist or 
     * not.
     * 
     * @var callback
     * 
     */
    protected $_check_pages = false;
    
    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    protected function _postConstruct()
    {
        parent::_postConstruct();
        $this->_class = get_class($this);
    }
    
    /**
     * 
     * Sets the callback to check if pages exist.
     * 
     * The callback has to take exactly one parameter, an array keyed
     * on page names, with the value being true or false.  It should
     * return a similar array, saying whether or not each page in the
     * array exists.
     * 
     * If left empty, the plugin will assume all links exist.
     * 
     * @param callback $callback The callback to check if pages exist.
     * 
     * @return array An array of which pages exist and which don't.
     * 
     */
    public function setCheckPagesCallback($callback)
    {
        $this->_check_pages = $callback;
    }
    
    /**
     * 
     * Sets one anchor attribute.
     * 
     * @param string $type The anchor type, generally 'read' or 'add'.
     * 
     * @param string $key The attribute key, for example 'href' or 'class'.
     * 
     * @param string $val The attribute value.
     * 
     * @return void
     * 
     */
    public function setAttrib($type, $key, $val)
    {
        $this->_attribs[$type][$key] = $val;
    }
    
    /**
     * 
     * Sets one or more interwiki name and href mapping.
     * 
     * Interwiki href values are actually sprintf() strings, where %s
     * will be replaced with the page requested at the interwiki site.
     * 
     * @param string|array $spec If a string, the interwiki site name;
     * if an array, an array of name => href mappings to merge with
     * current interwiki list.
     * 
     * @param string $val If $spec is a string, this is the sprintf()
     * format string for the href to the interwiki.
     * 
     * @return void
     * 
     */
    public function setInterwiki($spec, $val = null)
    {
        if (is_array($spec)) {
            $this->_interwiki = array_merge($spec, $this->_interwiki);
        } else {
            $this->_interwiki[$spec] = $val;
        }
    }
    
    /**
     * 
     * Gets the list of interwiki mappings.
     * 
     * @return array
     * 
     */
    public function getInterwiki()
    {
        return $this->_interwiki;
    }
    
    /**
     * 
     * Sets all attributes for one anchor type.
     * 
     * @param string $type The anchor type, generally 'read' or 'add'.
     * 
     * @param array $list The attributes to set in key => value format.
     * 
     * @return void
     * 
     */
    public function setAttribs($type, $list)
    {
        $this->_attribs[$type] = $list;
    }
    
    /**
     * 
     * Gets the list of pages found in the source text.
     * 
     * @return array
     * 
     */
    public function getPages()
    {
        return array_keys($this->_pages);
    }
    
    /**
     * 
     * Resets this plugin for a new transformation.
     * 
     * @return void
     * 
     */
    public function reset()
    {
        parent::reset();
        $this->_links = array();
        $this->_pages = array();
        $this->_count = 0;
    }
    
    /**
     * 
     * Parses the source text for wiki page and interwiki links.
     * 
     * @param string $text The source text.
     * 
     * @return string The parsed text.
     * 
     */
    public function parse($text)
    {
        $regex = '/\[\[(.*?)(\#.*?)?(\|.*?)?\]\](\w*)?/';
        return preg_replace_callback(
            $regex,
            array($this, '_parse'),
            $text
        );
    }
    
    /**
     * 
     * Support callback for parsing wiki links.
     * 
     * @param array $matches Matches from preg_replace_callback().
     * 
     * @return string The replacement text.
     * 
     */
    protected function _parse($matches)
    {
        $page = $matches[1];
        $frag = empty($matches[2]) ? null          : '#' . trim($matches[2], "# \t");
        $text = empty($matches[3]) ? $page . $frag : trim($matches[3], "| \t");
        $atch = empty($matches[4]) ? null          : trim($matches[4]);
        
        // is this an interwiki page?
        $pos = strpos($page, '::');
        if ($pos !== false) {
            return $this->_interwiki($matches);
        }
        
        // normalize the page name
        $norm = $this->_normalize($page);
        
        // assume the page exists
        $this->_pages[$norm] = true;
        
        // save the link
        $this->_links[$this->_count] = array(
            'norm' => $norm,
            'page' => $page,
            'frag' => $frag,
            'text' => $text,
            'atch' => $atch,
        );
        
        // generate an escaped WikiLink token to be replaced at
        // cleanup() time with real HTML.
        $key = $this->_class . ':' . $this->_count ++;
        return "\x1B$key\x1B";
    }
    
    /**
     * 
     * Normalizes a wiki page name.
     * 
     * @param string $page The page name from the source text.
     * 
     * @return string The normalized page name.
     * 
     */
    protected function _normalize($page)
    {
        // trim, force only the first letter to upper-case (leaving all
        // other characters alone), and then replace all whitespace
        // runs with a single underscore.
        return preg_replace('/\s+/', '_', ucfirst(trim($page)));
    }
    
    /**
     * 
     * Support callback for parsing interwiki links.
     * 
     * @param array $matches Matches from preg_replace_callback().
     * 
     * @return string The replacement text.
     * 
     */
    protected function _interwiki($matches)
    {
        $pos = strpos($matches[1], '::');
        $site = trim(substr($matches[1], 0, $pos));
        $page = trim(substr($matches[1], $pos + 2));
        
        // does the requested interwiki site exist?
        if (empty($this->_interwiki[$site])) {
            return $matches[0];
        }
        
        $frag = empty($matches[2]) ? null : '#' . trim($matches[2], "# \t");
        $text = empty($matches[3]) ? $site . '::' . $page . $frag : trim($matches[3], "| \t");
        $atch = empty($matches[4]) ? null : trim($matches[4]);
        
        if (empty($text)) {
            // support for [[php::function() #anchor | ]] to get "function()"
            $text = $page;
        }
        
        // allow indiviual access to url page and url fragment
        $href = sprintf($this->_interwiki[$site], $page . $frag, $page, $frag);            
        $html = '<a href="' . $this->_escape($href) . '">'
              . $this->_escape($text . $atch)
              . '</a>';
              
        return $this->_toHtmlToken($html);
    }
    
    /**
     * 
     * Cleans up text to replace encoded placeholders with anchors.
     * 
     * @param string $text The source text with placeholders.
     * 
     * @return string The text with anchors instead of placeholders.
     * 
     */
    public function cleanup($text)
    {
        // first, update $this->_pages against the data store to see
        // which pages exist and which do not.
        if ($this->_check_pages) {
            $this->_pages = call_user_func($this->_check_pages, $this->_pages);
        }
        
        // now go through and replace tokens
        $regex = "/\x1B{$this->_class}:(.*?)\x1B/";
        return preg_replace_callback(
            $regex,
            array($this, '_cleanup'),
            $text
        );
    }
    
    /**
     * 
     * Support callback for replacing placeholder with anchors.
     * 
     * @param array $matches Matches from preg_replace_callback().
     * 
     * @return string The replacement text.
     * 
     */
    protected function _cleanup($matches)
    {
        $key = $matches[1];
        $tmp = $this->_links[$key];
        
        // normalized page name
        $norm = $tmp['norm'];
        
        // page name as entered
        $page = $tmp['page'];
        
        // anchor "#fragment"
        $frag = $tmp['frag'];
        
        // optional display text
        $text = $tmp['text'];
        if (empty($text)) {
            // support for [][page name#anchor | ]] to get "page name"?
            $text = $page;
        }
        
        // optional attached text outside the link
        $atch = $tmp['atch'];
        
        // make sure the page is listed; the check-pages callback
        // may not have populated it back.
        if (empty($this->_pages[$norm])) {
            $this->_pages[$norm] = false;
        }
        
        // use "read" or "add" attribs?
        if ($this->_pages[$norm]) {
            // page exists
            $attribs = $this->_attribs['read'];
        } else {
            // page does not exist
            $attribs = $this->_attribs['add'];
        }
        
        // make sure we have an href attrib
        if (empty($attribs['href'])) {
            $attribs['href'] = '%s';
        }
        
        // build the opening <a href="" portion of the tag.
        $html = '<a href="'
              . $this->_escape(sprintf($attribs['href'], $norm . $frag, $norm, $frag))
              . '"';
              
        // add attributes and close the opening tag
        unset($attribs['href']);
        foreach ($attribs as $key => $val) {
            $key = $this->_escape($key);
            $val = $this->_escape($val);
            $html .= " $key=\"$val\"";
        }
        $html .= ">";
        
        // add the escaped the display text and close the tag
        $html .= $this->_escape($text . $atch) . "</a>";
        
        // done!
        return $html;
    }
}
Return current item: SolarPHP