xref: /dokuwiki/inc/parser/metadata.php (revision 7783971742e69fbd5ad26b0daf75bd4a3043aff0)
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