<?php
if (! class_exists('syntax_plugin_tip')) {
	if (! defined('DOKU_PLUGIN')) {
		if (! defined('DOKU_INC')) {
			define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/');
		} // if
		define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
	} // if
	// Include parent class:
	require_once(DOKU_PLUGIN . 'syntax.php');

/**
 * <tt>syntax_plugin_tip.php </tt>- A PHP4 class that implements
 * a <tt>DokuWiki</tt> plugin for <tt>small notes/tips fragments</tt>.
 *
 * <p>
 * Usage:<br>
 * <tt>&lt;tip [c|l|r] [u|e|d] [h|i|n|w]&gt;some text&lt;/tip&gt;</tt>
 * </p><pre>
 *	Copyright (C) 2006, 2007  M.Watermann, D-10247 Berlin, FRG
 *			All rights reserved
 *		EMail : &lt;support@mwat.de&gt;
 * </pre><div class="disclaimer">
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either
 * <a href="http://www.gnu.org/licenses/gpl.html">version 3</a> of the
 * License, or (at your option) any later version.<br>
 * This software 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 more details.
 * </div>
 * @author <a href="mailto:support@mwat.de">Matthias Watermann</a>
 * @version <tt>$Id: syntax_plugin_tip.php,v 1.5 2007/08/15 12:36:19 matthias Exp $</tt>
 * @since created 02-Dec-2006
 */
class syntax_plugin_tip extends DokuWiki_Syntax_Plugin {

	/**
	 * @privatesection
	 */
	//@{

	/**
	 * HTML special characters to replace in <tt>render()</tt>.
	 *
	 * @private
	 * @see render()
	 */
	var $_Chars = array('&', '<', '>');

	/**
	 * Entity replacements for HTML special characters.
	 *
	 * @private
	 * @see render()
	 */
	var $_Ents = array('&#38;', '&#60;', '&#62;');

	//@}
	/**
	 * @publicsection
	 */
	//@{

	/**
	 * Tell the parser whether the plugin accepts syntax mode
	 * <tt>$aMode</tt> within its own markup.
	 *
	 * @param $aMode String The requested syntaxmode.
	 * @return Boolean <tt>TRUE</tt> unless <tt>$aMode</tt> is
	 * <tt>'plugin_tip'</tt> (which would result in a <tt>FALSE</tt>
	 * method result).
	 * @public
	 * @see getAllowedTypes()
	 */
	function accepts($aMode) {
		return ('plugin_tip' != $aMode);
	} // accepts()

	/**
	 * Connect lookup pattern to lexer.
	 *
	 * @param $aMode String The desired rendermode.
	 * @public
	 * @see render()
	 */
	function connectTo($aMode) {
		$this->Lexer->addEntryPattern(
			'\x3Ctip(?:\s+[a-z ]*\s*)*\x3E(?=.*?\x3C\x2Ftip\x3E)',
			$aMode, 'plugin_tip');
	} // connectTo()

	/**
	 * Get an array of mode types that may be nested within the
	 * plugin's own markup.
	 *
	 * @return Array Allowed nested types (i.e. all known types).
	 * @public
	 * @see accepts()
	 * @static
	 */
	function getAllowedTypes() {
		return array(
			'container',
			'baseonly',
			'formatting',
			'substition', // sic! should be __substitution__
			'protected',
			'disabled',
			'paragraphs');
	} // getAllowedTypes()

	/**
	 * Get an associative array with plugin info.
	 *
	 * <p>
	 * The returned array holds the following fields:
	 * <dl>
	 * <dt>author</dt><dd>Author of the plugin</dd>
	 * <dt>email</dt><dd>Email address to contact the author</dd>
	 * <dt>date</dt><dd>Last modified date of the plugin in
	 * <tt>YYYY-MM-DD</tt> format</dd>
	 * <dt>name</dt><dd>Name of the plugin</dd>
	 * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd>
	 * <dt>url</dt><dd>Website with more information on the plugin
	 * (eg. syntax description)</dd>
	 * </dl>
	 * @return Array Information about this plugin class.
	 * @public
	 * @static
	 */
	function getInfo() {
		return array(
			'author' =>	'Matthias Watermann',
			'email' =>	'support@mwat.de',
			'date' =>	'2007-08-15',
			'name' =>	'Tip Syntax Plugin',
			'desc' =>	'Place hints &c. on a page '
				. '[<tip [c|l|r] [u|e|d] [h|i|n|w]> ... </tip>]',
			'url' =>	'http://wiki.splitbrain.org/plugin:tip');
	} // getInfo()

	/**
	 * Define how this plugin is handled regarding paragraphs.
	 *
	 * <p>
	 * This method is important for correct XHTML nesting.
	 * It returns one of the following values:
	 * </p>
	 * <dl>
	 * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
	 * <dt>block</dt><dd>Open paragraphs need to be closed before
	 * plugin output.</dd>
	 * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
	 * </dl>
	 * @return String <tt>"normal"</tt> instead of the (correct) "block"
	 * since otherwise the current DokuWiki parser wouldn't allow for
	 * nested paragraphs.
	 * @public
	 * @static
	 */
	function getPType() {
		return 'normal';
	} // getPType()

	/**
	 * Where to sort in?
	 *
	 * @return Integer <tt>415</tt> (not really important here).
	 * @public
	 * @static
	 */
	function getSort() {
		return 415;
	} // getSort()

	/**
	 * Get the type of syntax this plugin defines.
	 *
	 * @return String <tt>"container"</tt>.
	 * @public
	 * @static
	 */
	function getType() {
		return 'container';
	} // getType()

	/**
	 * Handler to prepare matched data for the rendering process.
	 *
	 * <p>
	 * The <tt>$aState</tt> parameter gives the type of pattern
	 * which triggered the call to this method:
	 * </p>
	 * <dl>
	 * <dt>DOKU_LEXER_ENTER</dt>
	 * <dd>a pattern set by <tt>addEntryPattern()</tt></dd>
	 * <dt>DOKU_LEXER_EXIT</dt>
	 * <dd> a pattern set by <tt>addExitPattern()</tt></dd>
	 * <dt>DOKU_LEXER_UNMATCHED</dt>
	 * <dd>ordinary text encountered within the plugin's syntax mode
	 * which doesn't match any pattern.</dd>
	 * </dl>
	 * @param $aMatch String The text matched by the patterns.
	 * @param $aState Integer The lexer state for the match.
	 * @param $aPos Integer The character position of the matched text.
	 * @param $aHandler Object Reference to the Doku_Handler object.
	 * @return Array Index <tt>[0]</tt> holds the current
	 * <tt>$aState</tt>, index <tt>[1]</tt> the match prepared for
	 * the <tt>render()</tt> method.
	 * @public
	 * @see render()
	 * @static
	 */
	function handle($aMatch, $aState, $aPos, &$aHandler) {
		switch ($aState) {
			case DOKU_LEXER_ENTER:
				$o = 'r';	// orientation, default: 'right'
				$p = 'e';	// positioning, default: 'even'
				$t = '';	// type, default: no image
				$hits = array();
				$aMatch = preg_split('|\s+|', substr($aMatch, 4),
					-1, PREG_SPLIT_NO_EMPTY);
				while (list($i, $arg) = each($aMatch)) {
					switch ($i = strtolower($arg{0})) {
						case 'c':	// center
						case 'l':	// left
						case 'r':	// right
							$o = $i;
							break;
						case 'd':	// down
						case 'e':	// even
						case 'u':	// up
							$p = $i;
							break;
						case 'h':	// hint
						case 'i':	// info
						case 'n':	// note
						case 'w':	// warn
							$t = $i;
						default:
							break;
					} // switch
				} // while
				return array(DOKU_LEXER_ENTER, $o . $p . $t);
			case DOKU_LEXER_UNMATCHED:
				return array(DOKU_LEXER_UNMATCHED, $aMatch);
			case DOKU_LEXER_EXIT:
				return array(DOKU_LEXER_EXIT, '');
			default:
				// This state isn't used by 'render()'
				// and causes it to do nothing at all:
				return array(DOKU_LEXER_SPECIAL, '');
		} // switch
	} // handle()

	/**
	 * Add exit pattern to lexer.
	 *
	 * @public
	 */
	function postConnect() {
		$this->Lexer->addExitPattern('\x3C\x2Ftip\x3E', 'plugin_tip');
	} // postConnect()

	/**
	 * Handle the actual output creation.
	 *
	 * <p>
	 * The method checks for the given <tt>$aFormat</tt> and returns
	 * <tt>FALSE</tt> when a format isn't supported. <tt>$aRenderer</tt>
	 * is a reference to the renderer object which is currently
	 * handling the rendering. The contents of <tt>$aData</tt> is
	 * the return value of the <tt>handle()</tt> method.
	 * </p>
	 * @param $aFormat String The output format to generate.
	 * @param $aRenderer Object A reference to the renderer object.
	 * @param $aData Array The data created/returned by the
	 * <tt>handle()</tt> method.
	 * @return Boolean <tt>TRUE</tt> if rendered successfully,
	 * or <tt>FALSE</tt> otherwise.
	 * @public
	 * @see handle()
	 */
	function render($aFormat, &$aRenderer, &$aData) {
		if ('xhtml' != $aFormat) {
			return FALSE;
		} // if
		// XXX: All those "</p>" and "<p>" tags handled here are just kind
		// of workaround problems with the current DokuWiki renderer; it's
		// needed because we've to lie (in "getPType()") about the created
		// markup's type.
		// Basically they are __wrong__ here (since they are outside the
		// plugin's domain of responsibility) but, alas, without them
		// invalid HTML would be generated :-(
		// If and when DokuWiki becomes more stateful
		// the superflous tags should be removed.
		switch ($aData[0]) {
			case DOKU_LEXER_ENTER:
				// Since we have to use PType 'normal' we must close the
				// current paragraph and open a new one for our own contents.
				$hits = array();
				if (preg_match('|\s*<p>\s*$|i', $aRenderer->doc, $hits)) {
					$aRenderer->doc = substr($aRenderer->doc,
						0, -strlen($hits[0])) . '<div class="tipAll tip'
							. $aData[1] . '"><p>';
				} else {
					$aRenderer->doc .= '</p><div class="tipAll tip'
						. $aData[1] . '"><p>';
				} // if
				// The "tipAll" class is meant to make CSS easier.
				return TRUE;
			case DOKU_LEXER_UNMATCHED:
				$aRenderer->doc .=
					str_replace($this->_Chars, $this->_Ents, $aData[1]);
				return TRUE;
			case DOKU_LEXER_EXIT:
				// Since we have to use PType 'normal' we must open
				// a new paragraph for the following text.
				$aRenderer->doc = preg_replace('|\s*<p>\s*</p>\s*|', '',
					$aRenderer->doc) . '</p></div><p>';
			default:
				return TRUE;
		} // switch
	} // render()

	//@}
} // class syntax_plugin_tip
} // if
?>
