1<?php 2/** 3 * Renderer for metadata 4 * 5 * @author Esther Brunner <wikidesign@gmail.com> 6 */ 7if(!defined('DOKU_INC')) die('meh.'); 8 9if ( !defined('DOKU_LF') ) { 10 // Some whitespace to help View > Source 11 define ('DOKU_LF',"\n"); 12} 13 14if ( !defined('DOKU_TAB') ) { 15 // Some whitespace to help View > Source 16 define ('DOKU_TAB',"\t"); 17} 18 19require_once DOKU_INC . 'inc/parser/renderer.php'; 20 21/** 22 * The Renderer 23 */ 24class Doku_Renderer_metadata extends Doku_Renderer { 25 26 var $doc = ''; 27 var $meta = array(); 28 var $persistent = array(); 29 30 var $headers = array(); 31 var $capture = true; 32 var $store = ''; 33 var $firstimage = ''; 34 35 function getFormat(){ 36 return 'metadata'; 37 } 38 39 function document_start(){ 40 global $ID; 41 42 $this->headers = array(); 43 44 // external pages are missing create date 45 if(!$this->persistent['date']['created']){ 46 $this->persistent['date']['created'] = filectime(wikiFN($ID)); 47 } 48 if(!isset($this->persistent['user'])){ 49 $this->persistent['user'] = ''; 50 } 51 if(!isset($this->persistent['creator'])){ 52 $this->persistent['creator'] = ''; 53 } 54 // reset metadata to persistent values 55 $this->meta = $this->persistent; 56 } 57 58 function document_end(){ 59 global $ID; 60 61 // store internal info in metadata (notoc,nocache) 62 $this->meta['internal'] = $this->info; 63 64 if (!isset($this->meta['description']['abstract'])){ 65 // cut off too long abstracts 66 $this->doc = trim($this->doc); 67 if (strlen($this->doc) > 500) 68 $this->doc = utf8_substr($this->doc, 0, 500).'…'; 69 $this->meta['description']['abstract'] = $this->doc; 70 } 71 72 $this->meta['relation']['firstimage'] = $this->firstimage; 73 74 if(!isset($this->meta['date']['modified'])){ 75 $this->meta['date']['modified'] = filemtime(wikiFN($ID)); 76 } 77 78 } 79 80 function toc_additem($id, $text, $level) { 81 global $conf; 82 83 //only add items within configured levels 84 if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){ 85 // the TOC is one of our standard ul list arrays ;-) 86 $this->meta['description']['tableofcontents'][] = array( 87 'hid' => $id, 88 'title' => $text, 89 'type' => 'ul', 90 'level' => $level-$conf['toptoclevel']+1 91 ); 92 } 93 94 } 95 96 function header($text, $level, $pos) { 97 if (!isset($this->meta['title'])) $this->meta['title'] = $text; 98 99 // add the header to the TOC 100 $hid = $this->_headerToLink($text,'true'); 101 $this->toc_additem($hid, $text, $level); 102 103 // add to summary 104 if ($this->capture && ($level > 1)) $this->doc .= DOKU_LF.$text.DOKU_LF; 105 } 106 107 function section_open($level){} 108 function section_close(){} 109 110 function cdata($text){ 111 if ($this->capture) $this->doc .= $text; 112 } 113 114 function p_open(){ 115 if ($this->capture) $this->doc .= DOKU_LF; 116 } 117 118 function p_close(){ 119 if ($this->capture){ 120 if (strlen($this->doc) > 250) $this->capture = false; 121 else $this->doc .= DOKU_LF; 122 } 123 } 124 125 function linebreak(){ 126 if ($this->capture) $this->doc .= DOKU_LF; 127 } 128 129 function hr(){ 130 if ($this->capture){ 131 if (strlen($this->doc) > 250) $this->capture = false; 132 else $this->doc .= DOKU_LF.'----------'.DOKU_LF; 133 } 134 } 135 136 /** 137 * Callback for footnote start syntax 138 * 139 * All following content will go to the footnote instead of 140 * the document. To achieve this the previous rendered content 141 * is moved to $store and $doc is cleared 142 * 143 * @author Andreas Gohr <andi@splitbrain.org> 144 */ 145 function footnote_open() { 146 if ($this->capture){ 147 // move current content to store and record footnote 148 $this->store = $this->doc; 149 $this->doc = ''; 150 } 151 } 152 153 /** 154 * Callback for footnote end syntax 155 * 156 * All rendered content is moved to the $footnotes array and the old 157 * content is restored from $store again 158 * 159 * @author Andreas Gohr 160 */ 161 function footnote_close() { 162 if ($this->capture){ 163 // restore old content 164 $this->doc = $this->store; 165 $this->store = ''; 166 } 167 } 168 169 function listu_open(){ 170 if ($this->capture) $this->doc .= DOKU_LF; 171 } 172 173 function listu_close(){ 174 if ($this->capture && (strlen($this->doc) > 250)) $this->capture = false; 175 } 176 177 function listo_open(){ 178 if ($this->capture) $this->doc .= DOKU_LF; 179 } 180 181 function listo_close(){ 182 if ($this->capture && (strlen($this->doc) > 250)) $this->capture = false; 183 } 184 185 function listitem_open($level){ 186 if ($this->capture) $this->doc .= str_repeat(DOKU_TAB, $level).'* '; 187 } 188 189 function listitem_close(){ 190 if ($this->capture) $this->doc .= DOKU_LF; 191 } 192 193 function listcontent_open(){} 194 function listcontent_close(){} 195 196 function unformatted($text){ 197 if ($this->capture) $this->doc .= $text; 198 } 199 200 function preformatted($text){ 201 if ($this->capture) $this->doc .= $text; 202 } 203 204 function file($text, $lang = null, $file = null){ 205 if ($this->capture){ 206 $this->doc .= DOKU_LF.$text; 207 if (strlen($this->doc) > 250) $this->capture = false; 208 else $this->doc .= DOKU_LF; 209 } 210 } 211 212 function quote_open(){ 213 if ($this->capture) $this->doc .= DOKU_LF.DOKU_TAB.'"'; 214 } 215 216 function quote_close(){ 217 if ($this->capture){ 218 $this->doc .= '"'; 219 if (strlen($this->doc) > 250) $this->capture = false; 220 else $this->doc .= DOKU_LF; 221 } 222 } 223 224 function code($text, $language = null, $file = null){ 225 if ($this->capture){ 226 $this->doc .= DOKU_LF.$text; 227 if (strlen($this->doc) > 250) $this->capture = false; 228 else $this->doc .= DOKU_LF; 229 } 230 } 231 232 function acronym($acronym){ 233 if ($this->capture) $this->doc .= $acronym; 234 } 235 236 function smiley($smiley){ 237 if ($this->capture) $this->doc .= $smiley; 238 } 239 240 function entity($entity){ 241 if ($this->capture) $this->doc .= $entity; 242 } 243 244 function multiplyentity($x, $y){ 245 if ($this->capture) $this->doc .= $x.'×'.$y; 246 } 247 248 function singlequoteopening(){ 249 global $lang; 250 if ($this->capture) $this->doc .= $lang['singlequoteopening']; 251 } 252 253 function singlequoteclosing(){ 254 global $lang; 255 if ($this->capture) $this->doc .= $lang['singlequoteclosing']; 256 } 257 258 function apostrophe() { 259 global $lang; 260 if ($this->capture) $this->doc .= $lang['apostrophe']; 261 } 262 263 function doublequoteopening(){ 264 global $lang; 265 if ($this->capture) $this->doc .= $lang['doublequoteopening']; 266 } 267 268 function doublequoteclosing(){ 269 global $lang; 270 if ($this->capture) $this->doc .= $lang['doublequoteclosing']; 271 } 272 273 function camelcaselink($link) { 274 $this->internallink($link, $link); 275 } 276 277 function locallink($hash, $name = null){ 278 if(is_array($name)) { 279 $this->_firstimage($name['src']); 280 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 281 } 282 } 283 284 /** 285 * keep track of internal links in $this->meta['relation']['references'] 286 */ 287 function internallink($id, $name = null){ 288 global $ID; 289 290 if(is_array($name)) { 291 $this->_firstimage($name['src']); 292 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 293 } 294 295 $parts = explode('?', $id, 2); 296 if (count($parts) === 2) { 297 $id = $parts[0]; 298 } 299 300 $default = $this->_simpleTitle($id); 301 302 // first resolve and clean up the $id 303 resolve_pageid(getNS($ID), $id, $exists); 304 list($page, $hash) = explode('#', $id, 2); 305 306 // set metadata 307 $this->meta['relation']['references'][$page] = $exists; 308 // $data = array('relation' => array('isreferencedby' => array($ID => true))); 309 // p_set_metadata($id, $data); 310 311 // add link title to summary 312 if ($this->capture){ 313 $name = $this->_getLinkTitle($name, $default, $id); 314 $this->doc .= $name; 315 } 316 } 317 318 function externallink($url, $name = null){ 319 if(is_array($name)) { 320 $this->_firstimage($name['src']); 321 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 322 } 323 324 if ($this->capture){ 325 $this->doc .= $this->_getLinkTitle($name, '<' . $url . '>'); 326 } 327 } 328 329 function interwikilink($match, $name = null, $wikiName, $wikiUri){ 330 if(is_array($name)) { 331 $this->_firstimage($name['src']); 332 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 333 } 334 335 if ($this->capture){ 336 list($wikiUri, $hash) = explode('#', $wikiUri, 2); 337 $name = $this->_getLinkTitle($name, $wikiUri); 338 $this->doc .= $name; 339 } 340 } 341 342 function windowssharelink($url, $name = null){ 343 if(is_array($name)) { 344 $this->_firstimage($name['src']); 345 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 346 } 347 348 if ($this->capture){ 349 if ($name) $this->doc .= $name; 350 else $this->doc .= '<'.$url.'>'; 351 } 352 } 353 354 function emaillink($address, $name = null){ 355 if(is_array($name)) { 356 $this->_firstimage($name['src']); 357 if ($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); 358 } 359 360 if ($this->capture){ 361 if ($name) $this->doc .= $name; 362 else $this->doc .= '<'.$address.'>'; 363 } 364 } 365 366 function internalmedia($src, $title=null, $align=null, $width=null, 367 $height=null, $cache=null, $linking=null){ 368 if ($this->capture && $title) $this->doc .= '['.$title.']'; 369 $this->_firstimage($src); 370 $this->_recordMediaUsage($src); 371 } 372 373 function externalmedia($src, $title=null, $align=null, $width=null, 374 $height=null, $cache=null, $linking=null){ 375 if ($this->capture && $title) $this->doc .= '['.$title.']'; 376 $this->_firstimage($src); 377 } 378 379 function rss($url,$params) { 380 $this->meta['relation']['haspart'][$url] = true; 381 382 $this->meta['date']['valid']['age'] = 383 isset($this->meta['date']['valid']['age']) ? 384 min($this->meta['date']['valid']['age'],$params['refresh']) : 385 $params['refresh']; 386 } 387 388 //---------------------------------------------------------- 389 // Utils 390 391 /** 392 * Removes any Namespace from the given name but keeps 393 * casing and special chars 394 * 395 * @author Andreas Gohr <andi@splitbrain.org> 396 */ 397 function _simpleTitle($name){ 398 global $conf; 399 400 if(is_array($name)) return ''; 401 402 if($conf['useslash']){ 403 $nssep = '[:;/]'; 404 }else{ 405 $nssep = '[:;]'; 406 } 407 $name = preg_replace('!.*'.$nssep.'!','',$name); 408 //if there is a hash we use the anchor name only 409 $name = preg_replace('!.*#!','',$name); 410 return $name; 411 } 412 413 /** 414 * Creates a linkid from a headline 415 * 416 * @param string $title The headline title 417 * @param boolean $create Create a new unique ID? 418 * @author Andreas Gohr <andi@splitbrain.org> 419 */ 420 function _headerToLink($title, $create=false) { 421 if($create){ 422 return sectionID($title,$this->headers); 423 }else{ 424 $check = false; 425 return sectionID($title,$check); 426 } 427 } 428 429 /** 430 * Construct a title and handle images in titles 431 * 432 * @author Harry Fuecks <hfuecks@gmail.com> 433 */ 434 function _getLinkTitle($title, $default, $id=null) { 435 global $conf; 436 437 $isImage = false; 438 if (is_array($title)){ 439 if($title['title']) return '['.$title['title'].']'; 440 } else if (is_null($title) || trim($title)==''){ 441 if (useHeading('content') && $id){ 442 $heading = p_get_first_heading($id,METADATA_DONT_RENDER); 443 if ($heading) return $heading; 444 } 445 return $default; 446 } else { 447 return $title; 448 } 449 } 450 451 function _firstimage($src){ 452 if($this->firstimage) return; 453 global $ID; 454 455 list($src,$hash) = explode('#',$src,2); 456 if(!media_isexternal($src)){ 457 resolve_mediaid(getNS($ID),$src, $exists); 458 } 459 if(preg_match('/.(jpe?g|gif|png)$/i',$src)){ 460 $this->firstimage = $src; 461 } 462 } 463 464 function _recordMediaUsage($src) { 465 global $ID; 466 467 list ($src, $hash) = explode('#', $src, 2); 468 if (media_isexternal($src)) return; 469 resolve_mediaid(getNS($ID), $src, $exists); 470 $this->meta['relation']['media'][$src] = $exists; 471 } 472} 473 474//Setup VIM: ex: et ts=4 : 475