[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * The MetaData Renderer 4 * 5 * Metadata is additional information about a DokuWiki page that gets extracted mainly from the page's content 6 * but also it's own filesystem data (like the creation time). All metadata is stored in the fields $meta and 7 * $persistent. 8 * 9 * Some simplified rendering to $doc is done to gather the page's (text-only) abstract. 10 * 11 * @author Esther Brunner <wikidesign@gmail.com> 12 */ 13 class Doku_Renderer_metadata extends Doku_Renderer 14 { 15 /** the approximate byte lenght to capture for the abstract */ 16 const ABSTRACT_LEN = 250; 17 18 /** the maximum UTF8 character length for the abstract */ 19 const ABSTRACT_MAX = 500; 20 21 /** @var array transient meta data, will be reset on each rendering */ 22 public $meta = array(); 23 24 /** @var array persistent meta data, will be kept until explicitly deleted */ 25 public $persistent = array(); 26 27 /** @var array the list of headers used to create unique link ids */ 28 protected $headers = array(); 29 30 /** @var string temporary $doc store */ 31 protected $store = ''; 32 33 /** @var string keeps the first image reference */ 34 protected $firstimage = ''; 35 36 /** @var bool whether or not data is being captured for the abstract, public to be accessible by plugins */ 37 public $capturing = true; 38 39 /** @var bool determines if enough data for the abstract was collected, yet */ 40 public $capture = true; 41 42 /** @var int number of bytes captured for abstract */ 43 protected $captured = 0; 44 45 /** 46 * Returns the format produced by this renderer. 47 * 48 * @return string always 'metadata' 49 */ 50 public function getFormat() 51 { 52 return 'metadata'; 53 } 54 55 /** 56 * Initialize the document 57 * 58 * Sets up some of the persistent info about the page if it doesn't exist, yet. 59 */ 60 public function document_start() 61 { 62 global $ID; 63 64 $this->headers = array(); 65 66 // external pages are missing create date 67 if (!isset($this->persistent['date']['created']) || !$this->persistent['date']['created']) { 68 $this->persistent['date']['created'] = filectime(wikiFN($ID)); 69 } 70 if (!isset($this->persistent['user'])) { 71 $this->persistent['user'] = ''; 72 } 73 if (!isset($this->persistent['creator'])) { 74 $this->persistent['creator'] = ''; 75 } 76 // reset metadata to persistent values 77 $this->meta = $this->persistent; 78 } 79 80 /** 81 * Finalize the document 82 * 83 * Stores collected data in the metadata 84 */ 85 public function document_end() 86 { 87 global $ID; 88 89 // store internal info in metadata (notoc,nocache) 90 $this->meta['internal'] = $this->info; 91 92 if (!isset($this->meta['description']['abstract'])) { 93 // cut off too long abstracts 94 $this->doc = trim($this->doc); 95 if (strlen($this->doc) > self::ABSTRACT_MAX) { 96 $this->doc = \dokuwiki\Utf8\PhpString::substr($this->doc, 0, self::ABSTRACT_MAX).'…'; 97 } 98 $this->meta['description']['abstract'] = $this->doc; 99 } 100 101 $this->meta['relation']['firstimage'] = $this->firstimage; 102 103 if (!isset($this->meta['date']['modified'])) { 104 $this->meta['date']['modified'] = filemtime(wikiFN($ID)); 105 } 106 } 107 108 /** 109 * Render plain text data 110 * 111 * This function takes care of the amount captured data and will stop capturing when 112 * enough abstract data is available 113 * 114 * @param $text 115 */ 116 public function cdata($text) 117 { 118 if (!$this->capture || !$this->capturing) { 119 return; 120 } 121 122 $this->doc .= $text; 123 124 $this->captured += strlen($text); 125 if ($this->captured > self::ABSTRACT_LEN) { 126 $this->capture = false; 127 } 128 } 129 130 /** 131 * Add an item to the TOC 132 * 133 * @param string $id the hash link 134 * @param string $text the text to display 135 * @param int $level the nesting level 136 */ 137 public function toc_additem($id, $text, $level) 138 { 139 global $conf; 140 141 //only add items within configured levels 142 if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 143 // the TOC is one of our standard ul list arrays ;-) 144 $this->meta['description']['tableofcontents'][] = array( 145 'hid' => $id, 146 'title' => $text, 147 'type' => 'ul', 148 'level' => $level - $conf['toptoclevel'] + 1 149 ); 150 } 151 } 152 153 /** 154 * Render a heading 155 * 156 * @param string $text the text to display 157 * @param int $level header level 158 * @param int $pos byte position in the original source 159 */ 160 public function header($text, $level, $pos) 161 { 162 if (!isset($this->meta['title'])) { 163 $this->meta['title'] = $text; 164 } 165 166 // add the header to the TOC 167 $hid = $this->_headerToLink($text, true); 168 $this->toc_additem($hid, $text, $level); 169 170 // add to summary 171 $this->cdata(DOKU_LF.$text.DOKU_LF); 172 } 173 174 /** 175 * Open a paragraph 176 */ 177 public function p_open() 178 { 179 $this->cdata(DOKU_LF); 180 } 181 182 /** 183 * Close a paragraph 184 */ 185 public function p_close() 186 { 187 $this->cdata(DOKU_LF); 188 } 189 190 /** 191 * Create a line break 192 */ 193 public function linebreak() 194 { 195 $this->cdata(DOKU_LF); 196 } 197 198 /** 199 * Create a horizontal line 200 */ 201 public function hr() 202 { 203 $this->cdata(DOKU_LF.'----------'.DOKU_LF); 204 } 205 206 /** 207 * Callback for footnote start syntax 208 * 209 * All following content will go to the footnote instead of 210 * the document. To achieve this the previous rendered content 211 * is moved to $store and $doc is cleared 212 * 213 * @author Andreas Gohr <andi@splitbrain.org> 214 */ 215 public function footnote_open() 216 { 217 if ($this->capture) { 218 // move current content to store 219 // this is required to ensure safe behaviour of plugins accessed within footnotes 220 $this->store = $this->doc; 221 $this->doc = ''; 222 223 // disable capturing 224 $this->capturing = false; 225 } 226 } 227 228 /** 229 * Callback for footnote end syntax 230 * 231 * All content rendered whilst within footnote syntax mode is discarded, 232 * the previously rendered content is restored and capturing is re-enabled. 233 * 234 * @author Andreas Gohr 235 */ 236 public function footnote_close() 237 { 238 if ($this->capture) { 239 // re-enable capturing 240 $this->capturing = true; 241 // restore previously rendered content 242 $this->doc = $this->store; 243 $this->store = ''; 244 } 245 } 246 247 /** 248 * Open an unordered list 249 */ 250 public function listu_open() 251 { 252 $this->cdata(DOKU_LF); 253 } 254 255 /** 256 * Open an ordered list 257 */ 258 public function listo_open() 259 { 260 $this->cdata(DOKU_LF); 261 } 262 263 /** 264 * Open a list item 265 * 266 * @param int $level the nesting level 267 * @param bool $node true when a node; false when a leaf 268 */ 269 public function listitem_open($level, $node=false) 270 { 271 $this->cdata(str_repeat(DOKU_TAB, $level).'* '); 272 } 273 274 /** 275 * Close a list item 276 */ 277 public function listitem_close() 278 { 279 $this->cdata(DOKU_LF); 280 } 281 282 /** 283 * Output preformatted text 284 * 285 * @param string $text 286 */ 287 public function preformatted($text) 288 { 289 $this->cdata($text); 290 } 291 292 /** 293 * Start a block quote 294 */ 295 public function quote_open() 296 { 297 $this->cdata(DOKU_LF.DOKU_TAB.'"'); 298 } 299 300 /** 301 * Stop a block quote 302 */ 303 public function quote_close() 304 { 305 $this->cdata('"'.DOKU_LF); 306 } 307 308 /** 309 * Display text as file content, optionally syntax highlighted 310 * 311 * @param string $text text to show 312 * @param string $lang programming language to use for syntax highlighting 313 * @param string $file file path label 314 */ 315 public function file($text, $lang = null, $file = null) 316 { 317 $this->cdata(DOKU_LF.$text.DOKU_LF); 318 } 319 320 /** 321 * Display text as code content, optionally syntax highlighted 322 * 323 * @param string $text text to show 324 * @param string $language programming language to use for syntax highlighting 325 * @param string $file file path label 326 */ 327 public function code($text, $language = null, $file = null) 328 { 329 $this->cdata(DOKU_LF.$text.DOKU_LF); 330 } 331 332 /** 333 * Format an acronym 334 * 335 * Uses $this->acronyms 336 * 337 * @param string $acronym 338 */ 339 public function acronym($acronym) 340 { 341 $this->cdata($acronym); 342 } 343 344 /** 345 * Format a smiley 346 * 347 * Uses $this->smiley 348 * 349 * @param string $smiley 350 */ 351 public function smiley($smiley) 352 { 353 $this->cdata($smiley); 354 } 355 356 /** 357 * Format an entity 358 * 359 * Entities are basically small text replacements 360 * 361 * Uses $this->entities 362 * 363 * @param string $entity 364 */ 365 public function entity($entity) 366 { 367 $this->cdata($entity); 368 } 369 370 /** 371 * Typographically format a multiply sign 372 * 373 * Example: ($x=640, $y=480) should result in "640×480" 374 * 375 * @param string|int $x first value 376 * @param string|int $y second value 377 */ 378 public function multiplyentity($x, $y) 379 { 380 $this->cdata($x.'×'.$y); 381 } 382 383 /** 384 * Render an opening single quote char (language specific) 385 */ 386 public function singlequoteopening() 387 { 388 global $lang; 389 $this->cdata($lang['singlequoteopening']); 390 } 391 392 /** 393 * Render a closing single quote char (language specific) 394 */ 395 public function singlequoteclosing() 396 { 397 global $lang; 398 $this->cdata($lang['singlequoteclosing']); 399 } 400 401 /** 402 * Render an apostrophe char (language specific) 403 */ 404 public function apostrophe() 405 { 406 global $lang; 407 $this->cdata($lang['apostrophe']); 408 } 409 410 /** 411 * Render an opening double quote char (language specific) 412 */ 413 public function doublequoteopening() 414 { 415 global $lang; 416 $this->cdata($lang['doublequoteopening']); 417 } 418 419 /** 420 * Render an closinging double quote char (language specific) 421 */ 422 public function doublequoteclosing() 423 { 424 global $lang; 425 $this->cdata($lang['doublequoteclosing']); 426 } 427 428 /** 429 * Render a CamelCase link 430 * 431 * @param string $link The link name 432 * @see http://en.wikipedia.org/wiki/CamelCase 433 */ 434 public function camelcaselink($link) 435 { 436 $this->internallink($link, $link); 437 } 438 439 /** 440 * Render a page local link 441 * 442 * @param string $hash hash link identifier 443 * @param string $name name for the link 444 */ 445 public function locallink($hash, $name = null) 446 { 447 if (is_array($name)) { 448 $this->_firstimage($name['src']); 449 if ($name['type'] == 'internalmedia') { 450 $this->_recordMediaUsage($name['src']); 451 } 452 } 453 } 454 455 /** 456 * keep track of internal links in $this->meta['relation']['references'] 457 * 458 * @param string $id page ID to link to. eg. 'wiki:syntax' 459 * @param string|array|null $name name for the link, array for media file 460 */ 461 public function internallink($id, $name = null) 462 { 463 global $ID; 464 465 if (is_array($name)) { 466 $this->_firstimage($name['src']); 467 if ($name['type'] == 'internalmedia') { 468 $this->_recordMediaUsage($name['src']); 469 } 470 } 471 472 $parts = explode('?', $id, 2); 473 if (count($parts) === 2) { 474 $id = $parts[0]; 475 } 476 477 $default = $this->_simpleTitle($id); 478 479 // first resolve and clean up the $id 480 $resolver = new \dokuwiki\File\PageResolver($ID); 481 $id = $resolver->resolveId($id); 482 list($page) = sexplode('#', $id, 2); 483 484 // set metadata 485 $this->meta['relation']['references'][$page] = page_exists($page); 486 // $data = array('relation' => array('isreferencedby' => array($ID => true))); 487 // p_set_metadata($id, $data); 488 489 // add link title to summary 490 if ($this->capture) { 491 $name = $this->_getLinkTitle($name, $default, $id); 492 $this->doc .= $name; 493 } 494 } 495 496 /** 497 * Render an external link 498 * 499 * @param string $url full URL with scheme 500 * @param string|array|null $name name for the link, array for media file 501 */ 502 public function externallink($url, $name = null) 503 { 504 if (is_array($name)) { 505 $this->_firstimage($name['src']); 506 if ($name['type'] == 'internalmedia') { 507 $this->_recordMediaUsage($name['src']); 508 } 509 } 510 511 if ($this->capture) { 512 $this->doc .= $this->_getLinkTitle($name, '<'.$url.'>'); 513 } 514 } 515 516 /** 517 * Render an interwiki link 518 * 519 * You may want to use $this->_resolveInterWiki() here 520 * 521 * @param string $match original link - probably not much use 522 * @param string|array $name name for the link, array for media file 523 * @param string $wikiName indentifier (shortcut) for the remote wiki 524 * @param string $wikiUri the fragment parsed from the original link 525 */ 526 public function interwikilink($match, $name, $wikiName, $wikiUri) 527 { 528 if (is_array($name)) { 529 $this->_firstimage($name['src']); 530 if ($name['type'] == 'internalmedia') { 531 $this->_recordMediaUsage($name['src']); 532 } 533 } 534 535 if ($this->capture) { 536 list($wikiUri) = explode('#', $wikiUri, 2); 537 $name = $this->_getLinkTitle($name, $wikiUri); 538 $this->doc .= $name; 539 } 540 } 541 542 /** 543 * Link to windows share 544 * 545 * @param string $url the link 546 * @param string|array $name name for the link, array for media file 547 */ 548 public function windowssharelink($url, $name = null) 549 { 550 if (is_array($name)) { 551 $this->_firstimage($name['src']); 552 if ($name['type'] == 'internalmedia') { 553 $this->_recordMediaUsage($name['src']); 554 } 555 } 556 557 if ($this->capture) { 558 if ($name) { 559 $this->doc .= $name; 560 } else { 561 $this->doc .= '<'.$url.'>'; 562 } 563 } 564 } 565 566 /** 567 * Render a linked E-Mail Address 568 * 569 * Should honor $conf['mailguard'] setting 570 * 571 * @param string $address Email-Address 572 * @param string|array $name name for the link, array for media file 573 */ 574 public function emaillink($address, $name = null) 575 { 576 if (is_array($name)) { 577 $this->_firstimage($name['src']); 578 if ($name['type'] == 'internalmedia') { 579 $this->_recordMediaUsage($name['src']); 580 } 581 } 582 583 if ($this->capture) { 584 if ($name) { 585 $this->doc .= $name; 586 } else { 587 $this->doc .= '<'.$address.'>'; 588 } 589 } 590 } 591 592 /** 593 * Render an internal media file 594 * 595 * @param string $src media ID 596 * @param string $title descriptive text 597 * @param string $align left|center|right 598 * @param int $width width of media in pixel 599 * @param int $height height of media in pixel 600 * @param string $cache cache|recache|nocache 601 * @param string $linking linkonly|detail|nolink 602 */ 603 public function internalmedia($src, $title = null, $align = null, $width = null, 604 $height = null, $cache = null, $linking = null) 605 { 606 if ($this->capture && $title) { 607 $this->doc .= '['.$title.']'; 608 } 609 $this->_firstimage($src); 610 $this->_recordMediaUsage($src); 611 } 612 613 /** 614 * Render an external media file 615 * 616 * @param string $src full media URL 617 * @param string $title descriptive text 618 * @param string $align left|center|right 619 * @param int $width width of media in pixel 620 * @param int $height height of media in pixel 621 * @param string $cache cache|recache|nocache 622 * @param string $linking linkonly|detail|nolink 623 */ 624 public function externalmedia($src, $title = null, $align = null, $width = null, 625 $height = null, $cache = null, $linking = null) 626 { 627 if ($this->capture && $title) { 628 $this->doc .= '['.$title.']'; 629 } 630 $this->_firstimage($src); 631 } 632 633 /** 634 * Render the output of an RSS feed 635 * 636 * @param string $url URL of the feed 637 * @param array $params Finetuning of the output 638 */ 639 public function rss($url, $params) 640 { 641 $this->meta['relation']['haspart'][$url] = true; 642 643 $this->meta['date']['valid']['age'] = 644 isset($this->meta['date']['valid']['age']) ? 645 min($this->meta['date']['valid']['age'], $params['refresh']) : 646 $params['refresh']; 647 } 648 649 #region Utils 650 651 /** 652 * Removes any Namespace from the given name but keeps 653 * casing and special chars 654 * 655 * @author Andreas Gohr <andi@splitbrain.org> 656 * 657 * @param string $name 658 * 659 * @return mixed|string 660 */ 661 public function _simpleTitle($name) 662 { 663 global $conf; 664 665 if (is_array($name)) { 666 return ''; 667 } 668 669 if ($conf['useslash']) { 670 $nssep = '[:;/]'; 671 } else { 672 $nssep = '[:;]'; 673 } 674 $name = preg_replace('!.*'.$nssep.'!', '', $name); 675 //if there is a hash we use the anchor name only 676 $name = preg_replace('!.*#!', '', $name); 677 return $name; 678 } 679 680 /** 681 * Construct a title and handle images in titles 682 * 683 * @author Harry Fuecks <hfuecks@gmail.com> 684 * @param string|array|null $title either string title or media array 685 * @param string $default default title if nothing else is found 686 * @param null|string $id linked page id (used to extract title from first heading) 687 * @return string title text 688 */ 689 public function _getLinkTitle($title, $default, $id = null) 690 { 691 if (is_array($title)) { 692 if ($title['title']) { 693 return '['.$title['title'].']'; 694 } else { 695 return $default; 696 } 697 } elseif (is_null($title) || trim($title) == '') { 698 if (useHeading('content') && $id) { 699 $heading = p_get_first_heading($id, METADATA_DONT_RENDER); 700 if ($heading) { 701 return $heading; 702 } 703 } 704 return $default; 705 } else { 706 return $title; 707 } 708 } 709 710 /** 711 * Remember first image 712 * 713 * @param string $src image URL or ID 714 */ 715 protected function _firstimage($src) 716 { 717 global $ID; 718 719 if ($this->firstimage) { 720 return; 721 } 722 723 list($src) = explode('#', $src, 2); 724 if (!media_isexternal($src)) { 725 $src = (new \dokuwiki\File\MediaResolver($ID))->resolveId($src); 726 } 727 if (preg_match('/.(jpe?g|gif|png)$/i', $src)) { 728 $this->firstimage = $src; 729 } 730 } 731 732 /** 733 * Store list of used media files in metadata 734 * 735 * @param string $src media ID 736 */ 737 protected function _recordMediaUsage($src) 738 { 739 global $ID; 740 741 list ($src) = explode('#', $src, 2); 742 if (media_isexternal($src)) { 743 return; 744 } 745 $src = (new \dokuwiki\File\MediaResolver($ID))->resolveId($src); 746 $file = mediaFN($src); 747 $this->meta['relation']['media'][$src] = file_exists($file); 748 } 749 750 #endregion 751 } 752 753 //Setup VIM: ex: et ts=4 :
title
Description
Body
title
Description
Body
title
Description
Body
title
Body