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