1<?php
2if (! class_exists('syntax_plugin_tip')) {
3	if (! defined('DOKU_PLUGIN')) {
4		if (! defined('DOKU_INC')) {
5			define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/');
6		} // if
7		define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
8	} // if
9	// Include parent class:
10	require_once(DOKU_PLUGIN . 'syntax.php');
11
12/**
13 * <tt>syntax_plugin_tip.php </tt>- A PHP4 class that implements
14 * a <tt>DokuWiki</tt> plugin for <tt>small notes/tips fragments</tt>.
15 *
16 * <p>
17 * Usage:<br>
18 * <tt>&lt;tip [c|l|r] [u|e|d] [h|i|n|w]&gt;some text&lt;/tip&gt;</tt>
19 * </p><pre>
20 *	Copyright (C) 2006, 2007  M.Watermann, D-10247 Berlin, FRG
21 *			All rights reserved
22 *		EMail : &lt;support@mwat.de&gt;
23 * </pre><div class="disclaimer">
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either
27 * <a href="http://www.gnu.org/licenses/gpl.html">version 3</a> of the
28 * License, or (at your option) any later version.<br>
29 * This software is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32 * General Public License for more details.
33 * </div>
34 * @author <a href="mailto:support@mwat.de">Matthias Watermann</a>
35 * @version <tt>$Id: syntax_plugin_tip.php,v 1.5 2007/08/15 12:36:19 matthias Exp $</tt>
36 * @since created 02-Dec-2006
37 */
38class syntax_plugin_tip extends DokuWiki_Syntax_Plugin {
39
40	/**
41	 * @privatesection
42	 */
43	//@{
44
45	/**
46	 * HTML special characters to replace in <tt>render()</tt>.
47	 *
48	 * @private
49	 * @see render()
50	 */
51	var $_Chars = array('&', '<', '>');
52
53	/**
54	 * Entity replacements for HTML special characters.
55	 *
56	 * @private
57	 * @see render()
58	 */
59	var $_Ents = array('&#38;', '&#60;', '&#62;');
60
61	//@}
62	/**
63	 * @publicsection
64	 */
65	//@{
66
67	/**
68	 * Tell the parser whether the plugin accepts syntax mode
69	 * <tt>$aMode</tt> within its own markup.
70	 *
71	 * @param $aMode String The requested syntaxmode.
72	 * @return Boolean <tt>TRUE</tt> unless <tt>$aMode</tt> is
73	 * <tt>'plugin_tip'</tt> (which would result in a <tt>FALSE</tt>
74	 * method result).
75	 * @public
76	 * @see getAllowedTypes()
77	 */
78	function accepts($aMode) {
79		return ('plugin_tip' != $aMode);
80	} // accepts()
81
82	/**
83	 * Connect lookup pattern to lexer.
84	 *
85	 * @param $aMode String The desired rendermode.
86	 * @public
87	 * @see render()
88	 */
89	function connectTo($aMode) {
90		$this->Lexer->addEntryPattern(
91			'\x3Ctip(?:\s+[a-z ]*\s*)*\x3E(?=.*?\x3C\x2Ftip\x3E)',
92			$aMode, 'plugin_tip');
93	} // connectTo()
94
95	/**
96	 * Get an array of mode types that may be nested within the
97	 * plugin's own markup.
98	 *
99	 * @return Array Allowed nested types (i.e. all known types).
100	 * @public
101	 * @see accepts()
102	 * @static
103	 */
104	function getAllowedTypes() {
105		return array(
106			'container',
107			'baseonly',
108			'formatting',
109			'substition', // sic! should be __substitution__
110			'protected',
111			'disabled',
112			'paragraphs');
113	} // getAllowedTypes()
114
115	/**
116	 * Get an associative array with plugin info.
117	 *
118	 * <p>
119	 * The returned array holds the following fields:
120	 * <dl>
121	 * <dt>author</dt><dd>Author of the plugin</dd>
122	 * <dt>email</dt><dd>Email address to contact the author</dd>
123	 * <dt>date</dt><dd>Last modified date of the plugin in
124	 * <tt>YYYY-MM-DD</tt> format</dd>
125	 * <dt>name</dt><dd>Name of the plugin</dd>
126	 * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd>
127	 * <dt>url</dt><dd>Website with more information on the plugin
128	 * (eg. syntax description)</dd>
129	 * </dl>
130	 * @return Array Information about this plugin class.
131	 * @public
132	 * @static
133	 */
134	function getInfo() {
135		return array(
136			'author' =>	'Matthias Watermann',
137			'email' =>	'support@mwat.de',
138			'date' =>	'2007-08-15',
139			'name' =>	'Tip Syntax Plugin',
140			'desc' =>	'Place hints &c. on a page '
141				. '[<tip [c|l|r] [u|e|d] [h|i|n|w]> ... </tip>]',
142			'url' =>	'http://wiki.splitbrain.org/plugin:tip');
143	} // getInfo()
144
145	/**
146	 * Define how this plugin is handled regarding paragraphs.
147	 *
148	 * <p>
149	 * This method is important for correct XHTML nesting.
150	 * It returns one of the following values:
151	 * </p>
152	 * <dl>
153	 * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
154	 * <dt>block</dt><dd>Open paragraphs need to be closed before
155	 * plugin output.</dd>
156	 * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
157	 * </dl>
158	 * @return String <tt>"normal"</tt> instead of the (correct) "block"
159	 * since otherwise the current DokuWiki parser wouldn't allow for
160	 * nested paragraphs.
161	 * @public
162	 * @static
163	 */
164	function getPType() {
165		return 'normal';
166	} // getPType()
167
168	/**
169	 * Where to sort in?
170	 *
171	 * @return Integer <tt>415</tt> (not really important here).
172	 * @public
173	 * @static
174	 */
175	function getSort() {
176		return 415;
177	} // getSort()
178
179	/**
180	 * Get the type of syntax this plugin defines.
181	 *
182	 * @return String <tt>"container"</tt>.
183	 * @public
184	 * @static
185	 */
186	function getType() {
187		return 'container';
188	} // getType()
189
190	/**
191	 * Handler to prepare matched data for the rendering process.
192	 *
193	 * <p>
194	 * The <tt>$aState</tt> parameter gives the type of pattern
195	 * which triggered the call to this method:
196	 * </p>
197	 * <dl>
198	 * <dt>DOKU_LEXER_ENTER</dt>
199	 * <dd>a pattern set by <tt>addEntryPattern()</tt></dd>
200	 * <dt>DOKU_LEXER_EXIT</dt>
201	 * <dd> a pattern set by <tt>addExitPattern()</tt></dd>
202	 * <dt>DOKU_LEXER_UNMATCHED</dt>
203	 * <dd>ordinary text encountered within the plugin's syntax mode
204	 * which doesn't match any pattern.</dd>
205	 * </dl>
206	 * @param $aMatch String The text matched by the patterns.
207	 * @param $aState Integer The lexer state for the match.
208	 * @param $aPos Integer The character position of the matched text.
209	 * @param $aHandler Object Reference to the Doku_Handler object.
210	 * @return Array Index <tt>[0]</tt> holds the current
211	 * <tt>$aState</tt>, index <tt>[1]</tt> the match prepared for
212	 * the <tt>render()</tt> method.
213	 * @public
214	 * @see render()
215	 * @static
216	 */
217	function handle($aMatch, $aState, $aPos, &$aHandler) {
218		switch ($aState) {
219			case DOKU_LEXER_ENTER:
220				$o = 'r';	// orientation, default: 'right'
221				$p = 'e';	// positioning, default: 'even'
222				$t = '';	// type, default: no image
223				$hits = array();
224				$aMatch = preg_split('|\s+|', substr($aMatch, 4),
225					-1, PREG_SPLIT_NO_EMPTY);
226				while (list($i, $arg) = each($aMatch)) {
227					switch ($i = strtolower($arg{0})) {
228						case 'c':	// center
229						case 'l':	// left
230						case 'r':	// right
231							$o = $i;
232							break;
233						case 'd':	// down
234						case 'e':	// even
235						case 'u':	// up
236							$p = $i;
237							break;
238						case 'h':	// hint
239						case 'i':	// info
240						case 'n':	// note
241						case 'w':	// warn
242							$t = $i;
243						default:
244							break;
245					} // switch
246				} // while
247				return array(DOKU_LEXER_ENTER, $o . $p . $t);
248			case DOKU_LEXER_UNMATCHED:
249				return array(DOKU_LEXER_UNMATCHED, $aMatch);
250			case DOKU_LEXER_EXIT:
251				return array(DOKU_LEXER_EXIT, '');
252			default:
253				// This state isn't used by 'render()'
254				// and causes it to do nothing at all:
255				return array(DOKU_LEXER_SPECIAL, '');
256		} // switch
257	} // handle()
258
259	/**
260	 * Add exit pattern to lexer.
261	 *
262	 * @public
263	 */
264	function postConnect() {
265		$this->Lexer->addExitPattern('\x3C\x2Ftip\x3E', 'plugin_tip');
266	} // postConnect()
267
268	/**
269	 * Handle the actual output creation.
270	 *
271	 * <p>
272	 * The method checks for the given <tt>$aFormat</tt> and returns
273	 * <tt>FALSE</tt> when a format isn't supported. <tt>$aRenderer</tt>
274	 * is a reference to the renderer object which is currently
275	 * handling the rendering. The contents of <tt>$aData</tt> is
276	 * the return value of the <tt>handle()</tt> method.
277	 * </p>
278	 * @param $aFormat String The output format to generate.
279	 * @param $aRenderer Object A reference to the renderer object.
280	 * @param $aData Array The data created/returned by the
281	 * <tt>handle()</tt> method.
282	 * @return Boolean <tt>TRUE</tt> if rendered successfully,
283	 * or <tt>FALSE</tt> otherwise.
284	 * @public
285	 * @see handle()
286	 */
287	function render($aFormat, &$aRenderer, &$aData) {
288		if ('xhtml' != $aFormat) {
289			return FALSE;
290		} // if
291		// XXX: All those "</p>" and "<p>" tags handled here are just kind
292		// of workaround problems with the current DokuWiki renderer; it's
293		// needed because we've to lie (in "getPType()") about the created
294		// markup's type.
295		// Basically they are __wrong__ here (since they are outside the
296		// plugin's domain of responsibility) but, alas, without them
297		// invalid HTML would be generated :-(
298		// If and when DokuWiki becomes more stateful
299		// the superflous tags should be removed.
300		switch ($aData[0]) {
301			case DOKU_LEXER_ENTER:
302				// Since we have to use PType 'normal' we must close the
303				// current paragraph and open a new one for our own contents.
304				$hits = array();
305				if (preg_match('|\s*<p>\s*$|i', $aRenderer->doc, $hits)) {
306					$aRenderer->doc = substr($aRenderer->doc,
307						0, -strlen($hits[0])) . '<div class="tipAll tip'
308							. $aData[1] . '"><p>';
309				} else {
310					$aRenderer->doc .= '</p><div class="tipAll tip'
311						. $aData[1] . '"><p>';
312				} // if
313				// The "tipAll" class is meant to make CSS easier.
314				return TRUE;
315			case DOKU_LEXER_UNMATCHED:
316				$aRenderer->doc .=
317					str_replace($this->_Chars, $this->_Ents, $aData[1]);
318				return TRUE;
319			case DOKU_LEXER_EXIT:
320				// Since we have to use PType 'normal' we must open
321				// a new paragraph for the following text.
322				$aRenderer->doc = preg_replace('|\s*<p>\s*</p>\s*|', '',
323					$aRenderer->doc) . '</p></div><p>';
324			default:
325				return TRUE;
326		} // switch
327	} // render()
328
329	//@}
330} // class syntax_plugin_tip
331} // if
332?>
333