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}