1<?php
2/**
3 * Helper Component for the Ad Hoc Tags Plugin
4 *
5 * @license	GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Anika Henke <anika@selfthinker.org>
7 * @author Sascha Leib <sascha.leib(at)kolmio.com>
8 */
9
10class helper_plugin_adhoctags extends DokuWiki_Plugin {
11
12	/* list of languages which normally use RTL scripts */
13	static protected $rtllangs = array('ar','dv','fa','ha','he','ks','ku','ps','ur','yi','arc');
14	/* list of right-to-left scripts (may override the language defaults to rtl) */
15	static protected $rtlscripts = array('arab','thaa','hebr','deva','shrd');
16	/* selection of left-to-right scripts (may override the language defaults to ltr) */
17	static protected $ltrscripts = array('latn','cyrl','grek','cyrs','armn');
18
19	/* Helper plugins should return info about the methods supported. */
20	public function getMethods() {
21		$result = array();
22		$result[] = array(
23			'name' => 'buildAttributes',
24			'desc' => 'writes out element attributes',
25			'params' => array(
26				'data' => 'string',
27				'custom' => 'array',
28				'addClass (optional)' => 'string',
29				'mode (optional)' => 'string'
30			),
31			'return' => array('attributes' => 'array')
32		);
33		return $result;
34	}
35
36	/**
37	 * build attributes
38	 */
39	function buildAttributes($data, $myObj, $addClass='', $mode='xhtml') {
40
41		$attList = $this->getAttributes($data);
42		$out = '';
43
44		//dbg('attList=' . print_r($attList, true));
45
46		if ($mode=='xhtml') {
47
48			foreach($attList as $key => $val) {
49
50				switch ($key) {
51
52					/* common HTML attributes (always enabled) */
53
54					case 'id':			/* id */
55					case 'class':		/* custom classes */
56					case 'title':		/* title */
57					case 'lang':		/* language */
58					case 'tabindex':	/* tabindex */
59					case 'is':			/* is */
60
61					/* Microformat attributes */
62					case 'itemprop':	/* item property */
63					case 'itemscope':	/* item scope */
64					case 'itemref':		/* item reference */
65					case 'itemid':		/* item id (microformat) */
66
67						$out .= ' '.$key. (is_null($val) ? '' : '="'.$val.'"');
68						break;
69
70					case 'dir':		/* custom attribute: direction */
71
72						if (in_array(strtolower(trim($val)), array('ltr','rtl','auto'))) {
73							$out .= ' dir="'. hsc($val).'"';
74						}
75						break;
76
77
78					case 'hidden':		/* custom attribute: hidden */
79
80						if (in_array(strtolower(trim($val)), array('hidden','until-found'))) {
81							$out .= ' hidden="'. hsc($val).'"';
82						} else {
83							$out .= ' hidden';
84						}
85						break;
86
87					case 'style':		/* style can be disabled */
88						if ($this->getConf('allowStyle') == '1') {
89							$out .= ' '.$key.'="'.hsc($val).'"';
90						}
91						break;
92
93					default:
94
95						/* special case 1: data-* attributes: */
96						if (preg_match('/^data-[a-z][a-z0-9_-]*$/', $key)) {
97							$out .= ' '.$key.'="'.hsc($val).'"';
98						}
99
100						/* special case 2: aria-* attributes: */
101						if (preg_match('/^aria-[a-z]+$/', $key)) {
102							$out .= ' '.$key.'="'.hsc($val).'"';
103						}
104
105						/* any other attribute: ask the class if it is allowed: */
106
107						if ($myObj->allowAttribute($key, $val)) {
108							$out .= ' '.$key. (is_null($val) ? '' : '="'.$val.'"');
109						}
110				}
111			}
112
113			// special case: no class name specified, but there is one passed down from a plugin:
114			if($addClass !== '' && !isset($attr['class'])) {
115				$out .= ' class="'.$addClass.'"';
116			}
117
118		}
119
120		return $out;
121	}
122
123	/**
124	 * get attributes (pull apart the string between '<wrap' and '>')
125	 *  and identify classes, width, lang and dir
126	 *
127	 * @author Sascha Leib <sascha.leib(at)kolmio.com>
128	 * @author Anika Henke <anika@selfthinker.org>
129	 * @author Christopher Smith <chris@jalakai.co.uk>
130	 *   (parts taken from http://www.dokuwiki.org/plugin:box)
131	 */
132	function getAttributes($data) {
133
134		//dbg('getAttributes("$data="' . $data . '"');
135
136		// store the attributes here:
137		$attr = array();
138		// split up the attributes string (keep quoted and square brackets intact):
139		$tokens = $this->tokenizeAttr($data);
140
141		foreach ($tokens as $token) {
142
143			//get language attribute
144			if (preg_match('/^:([a-z\-]+)/', $token)) {
145				$attr['lang'] = strtolower(trim($token,':'));
146				continue;
147			}
148
149			//get id (IDs can not start with a number!)
150			if (preg_match('/^#([A-Za-z]\w+)/', $token)) {
151				$attr['id'] = trim($token,'#');
152				continue;
153			}
154
155			// get title (any quoted string)
156			if (preg_match('/^\"(.*)\"$/', $token)) {
157				$attr['title'] = trim($token,'"');
158				continue;
159			}
160
161			/* custom attributes */
162			if (preg_match('/^\[([^\]]+)\]$/', $token)) {
163
164				$cAttr = explode('=', trim($token,'[]'), 2);
165				//dbg('$token = ' . $token . ', $cAttr = ' . print_r($cAttr, true));
166				if ($cAttr) {
167					$attr[$cAttr[0]] = ( isset($cAttr[1]) ? $cAttr[1] : null );
168				}
169				continue;
170			}
171
172			//add to list of classes if it matches the pattern for class names:
173			if (preg_match('/^[\w\d\-\\_]*$/',$token)) {
174				$attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '') . $token;
175			}
176		}
177
178		 /* improved RTL detection to make sure it covers more cases: */
179		if(!array_key_exists('dir', $attr) && array_key_exists('lang', $attr) && $attr['lang'] !== '') {
180
181			// turn the language code into an array of components:
182			$arr = explode('-', $attr['lang']);
183
184			// is the language iso code (first field) in the list of RTL languages?
185			$rtl = in_array($arr[0], self::$rtllangs);
186
187			// is there a Script specified somewhere which overrides the text direction?
188			$rtl = ($rtl xor (bool) array_intersect( $rtl ? self::$ltrscripts : self::$rtlscripts, $arr));
189
190			$attr['dir'] = ( $rtl ? 'rtl' : 'ltr' );
191		}
192
193		return $attr;
194	}
195
196	/**
197	 * Split the input data into suitable tokens
198	 *
199	 * @author Sascha Leib <sascha.leib(at)kolmio.com>
200	 */
201	 function tokenizeAttr($data) {
202
203		$result = array();
204		$token = ''; // temporary storage of each item
205		$escaped = false; // should the next character be treated "as is"?
206		$state = 0; // parser state (0 = default, 1 = in quotation, 2 = in square backets)
207
208		// loop over all characters:
209		forEach(str_split($data) as $c) {
210
211			switch($c) {
212			 case ' ': // Space
213			 case '\t': // Horizontal tabulation
214			 case '\n': // Newline
215			 case '\r': // Carriage return
216
217				if (!$escaped && $state == 0) {
218					if (trim($token)!==''){array_push($result, $token);}
219					$token = '';
220				} else {
221					$token .= ' ';
222					$escaped = false;
223				}
224				break;
225
226			 case '"': // Quote
227
228				if (!$escaped) {
229					switch ($state) {
230					 case 0:
231						if (trim($token)!==''){array_push($result, $token);}
232						$state = 1;
233						$token = $c;
234						break;
235
236					 case 1:
237						$token .= $c;
238						array_push($result, $token);
239						$state = 0;
240						$token = '';
241						break;
242
243					 case 2:
244						$token .= $c;
245						break;
246
247					 default:
248						// should never happen!
249					}
250				} else {
251					$token .= $c;
252					$escaped = false;
253				}
254				break;
255
256			 case '[': // Opening Square Brackets
257
258				if (!$escaped && $state == 0) {
259
260					if (trim($token)!==''){array_push($result, $token);}
261					$token = $c;
262					$state = 2;
263
264				} else {
265					$token .= $c;
266				}
267				break;
268
269			 case ']': // Opening Square Brackets
270
271				if (!$escaped && $state == 2) {
272
273					$token .= $c;
274					array_push($result, $token);
275					$token = '';
276					$state = 0;
277
278				} else {
279					$token .= $c;
280					$escaped = false;
281				}
282				break;
283
284			 case '\\': // Escape character
285
286				if (!$escaped) {
287					// next character is escaped:
288					$escaped = true;
289				} else {
290					$token .= $c;
291					$escaped = false;
292				}
293				break;
294
295			 default:
296				$token .= $c;
297				$escaped = false;
298			}
299		}
300
301		if (trim($token)!=='') {array_push($result, $token);}
302
303		return $result;
304	 }
305
306	/* Does anyone miss ODT support? */
307}