xref: /dokuwiki/inc/parserutils.php (revision 67c15ecea77ff971dcb554de77cf1c25de1140a0)
1c112d578Sandi<?php
2c112d578Sandi/**
3b8595a66SAndreas Gohr * Utilities for accessing the parser
4c112d578Sandi *
5c112d578Sandi * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6c112d578Sandi * @author     Harry Fuecks <hfuecks@gmail.com>
7c112d578Sandi * @author     Andreas Gohr <andi@splitbrain.org>
8c112d578Sandi */
9c112d578Sandi
10fa8adffeSAndreas Gohrif(!defined('DOKU_INC')) die('meh.');
11c112d578Sandi
12c112d578Sandi/**
13ff725173SMichael Hamann * For how many different pages shall the first heading be loaded from the
14ff725173SMichael Hamann * metadata? When this limit is reached the title index is loaded and used for
15ff725173SMichael Hamann * all following requests.
16ff725173SMichael Hamann */
17*67c15eceSMichael Hamannif (!defined('P_GET_FIRST_HEADING_METADATA_LIMIT')) define('P_GET_FIRST_HEADING_METADATA_LIMIT', 20);
18*67c15eceSMichael Hamann
19*67c15eceSMichael Hamann/** Don't render metadata even if it is outdated or doesn't exist */
20*67c15eceSMichael Hamanndefine('METADATA_DONT_RENDER', 0);
21*67c15eceSMichael Hamann/** Render metadata when the page is really newer or the metadata doesn't exist. Uses just a simple check,
22*67c15eceSMichael Hamann    but should work pretty well for loading simple metadata values like the page title and avoids
23*67c15eceSMichael Hamann    rendering a lot of pages in one request. */
24*67c15eceSMichael Hamanndefine('METADATA_RENDER_USING_SIMPLE_CACHE', 1);
25*67c15eceSMichael Hamann/** Render metadata using the metadata cache logic. */
26*67c15eceSMichael Hamanndefine('METADATA_RENDER_USING_CACHE', 2);
27ff725173SMichael Hamann
28ff725173SMichael Hamann/**
29c112d578Sandi * Returns the parsed Wikitext in XHTML for the given id and revision.
30c112d578Sandi *
31c112d578Sandi * If $excuse is true an explanation is returned if the file
32c112d578Sandi * wasn't found
33c112d578Sandi *
34c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
35c112d578Sandi */
36c112d578Sandifunction p_wiki_xhtml($id, $rev='', $excuse=true){
37c112d578Sandi    $file = wikiFN($id,$rev);
38c112d578Sandi    $ret  = '';
39c112d578Sandi
40c112d578Sandi    //ensure $id is in global $ID (needed for parsing)
411e76272cSandi    global $ID;
423ff8773bSAndreas Gohr    $keep = $ID;
431e76272cSandi    $ID   = $id;
44c112d578Sandi
45c112d578Sandi    if($rev){
46c112d578Sandi        if(@file_exists($file)){
47bde4e341SGalaxyMaster            $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info); //no caching on old revisions
48c112d578Sandi        }elseif($excuse){
49c112d578Sandi            $ret = p_locale_xhtml('norev');
50c112d578Sandi        }
51c112d578Sandi    }else{
52c112d578Sandi        if(@file_exists($file)){
534b5f4f4eSchris            $ret = p_cached_output($file,'xhtml',$id);
54c112d578Sandi        }elseif($excuse){
55c112d578Sandi            $ret = p_locale_xhtml('newpage');
56c112d578Sandi        }
57c112d578Sandi    }
58c112d578Sandi
593ff8773bSAndreas Gohr    //restore ID (just in case)
603ff8773bSAndreas Gohr    $ID = $keep;
613ff8773bSAndreas Gohr
62c112d578Sandi    return $ret;
63c112d578Sandi}
64c112d578Sandi
65c112d578Sandi/**
666b7b33dcShfuecks * Returns starting summary for a page (e.g. the first few
676b7b33dcShfuecks * paragraphs), marked up in XHTML.
686b7b33dcShfuecks *
696b7b33dcShfuecks * If $excuse is true an explanation is returned if the file
706b7b33dcShfuecks * wasn't found
716b7b33dcShfuecks *
726b7b33dcShfuecks * @param string wiki page id
736b7b33dcShfuecks * @param reference populated with page title from heading or page id
748716966dSAndreas Gohr * @deprecated
758716966dSAndreas Gohr * @author Harry Fuecks <hfuecks@gmail.com>
766b7b33dcShfuecks */
776b7b33dcShfuecksfunction p_wiki_xhtml_summary($id, &$title, $rev='', $excuse=true){
786b7b33dcShfuecks    $file = wikiFN($id,$rev);
796b7b33dcShfuecks    $ret  = '';
806b7b33dcShfuecks
816b7b33dcShfuecks    //ensure $id is in global $ID (needed for parsing)
826b7b33dcShfuecks    global $ID;
836b7b33dcShfuecks    $keep = $ID;
846b7b33dcShfuecks    $ID   = $id;
856b7b33dcShfuecks
866b7b33dcShfuecks    if($rev){
876b7b33dcShfuecks        if(@file_exists($file)){
886b7b33dcShfuecks            //no caching on old revisions
89bde4e341SGalaxyMaster            $ins = p_get_instructions(io_readWikiPage($file,$id,$rev));
906b7b33dcShfuecks        }elseif($excuse){
916b7b33dcShfuecks            $ret = p_locale_xhtml('norev');
926b7b33dcShfuecks            //restore ID (just in case)
936b7b33dcShfuecks            $ID = $keep;
946b7b33dcShfuecks            return $ret;
956b7b33dcShfuecks        }
966b7b33dcShfuecks
976b7b33dcShfuecks    }else{
986b7b33dcShfuecks
996b7b33dcShfuecks        if(@file_exists($file)){
1006b7b33dcShfuecks            // The XHTML for a summary is not cached so use the instruction cache
1016b7b33dcShfuecks            $ins = p_cached_instructions($file);
1026b7b33dcShfuecks        }elseif($excuse){
1036b7b33dcShfuecks            $ret = p_locale_xhtml('newpage');
1046b7b33dcShfuecks            //restore ID (just in case)
1056b7b33dcShfuecks            $ID = $keep;
1066b7b33dcShfuecks            return $ret;
1076b7b33dcShfuecks        }
1086b7b33dcShfuecks    }
1096b7b33dcShfuecks
1106b7b33dcShfuecks    $ret = p_render('xhtmlsummary',$ins,$info);
1116b7b33dcShfuecks
1126b7b33dcShfuecks    if ( $info['sum_pagetitle'] ) {
1136b7b33dcShfuecks        $title = $info['sum_pagetitle'];
1146b7b33dcShfuecks    } else {
1156b7b33dcShfuecks        $title = $id;
1166b7b33dcShfuecks    }
1176b7b33dcShfuecks
1186b7b33dcShfuecks    $ID = $keep;
1196b7b33dcShfuecks    return $ret;
1206b7b33dcShfuecks}
1216b7b33dcShfuecks
1226b7b33dcShfuecks/**
123c112d578Sandi * Returns the specified local text in parsed format
124c112d578Sandi *
125c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
126c112d578Sandi */
127c112d578Sandifunction p_locale_xhtml($id){
128c112d578Sandi    //fetch parsed locale
1294b5f4f4eSchris    $html = p_cached_output(localeFN($id));
130c112d578Sandi    return $html;
131c112d578Sandi}
132c112d578Sandi
133c112d578Sandi/**
1344b5f4f4eSchris *     *** DEPRECATED ***
1354b5f4f4eSchris *
1364b5f4f4eSchris * use p_cached_output()
1374b5f4f4eSchris *
138c112d578Sandi * Returns the given file parsed to XHTML
139c112d578Sandi *
140c112d578Sandi * Uses and creates a cachefile
141c112d578Sandi *
1424b5f4f4eSchris * @deprecated
143c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
1449dc2c2afSandi * @todo   rewrite to use mode instead of hardcoded XHTML
145c112d578Sandi */
146c112d578Sandifunction p_cached_xhtml($file){
1474b5f4f4eSchris    return p_cached_output($file);
1484b5f4f4eSchris}
1494b5f4f4eSchris
1504b5f4f4eSchris/**
1514b5f4f4eSchris * Returns the given file parsed into the requested output format
1524b5f4f4eSchris *
1534b5f4f4eSchris * @author Andreas Gohr <andi@splitbrain.org>
1544b5f4f4eSchris * @author Chris Smith <chris@jalakai.co.uk>
1554b5f4f4eSchris */
1564b5f4f4eSchrisfunction p_cached_output($file, $format='xhtml', $id='') {
157c112d578Sandi    global $conf;
158c112d578Sandi
1594b5f4f4eSchris    $cache = new cache_renderer($id, $file, $format);
1604b5f4f4eSchris    if ($cache->useCache()) {
16185767031SAndreas Gohr        $parsed = $cache->retrieveCache(false);
162ea2a4271SAndreas Gohr        if($conf['allowdebug'] && $format=='xhtml') $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
163c112d578Sandi    } else {
1644b5f4f4eSchris        $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
165c112d578Sandi
1669dc2c2afSandi        if ($info['cache']) {
1674b5f4f4eSchris            $cache->storeCache($parsed);               //save cachefile
168ea2a4271SAndreas Gohr            if($conf['allowdebug'] && $format=='xhtml') $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n";
169c112d578Sandi        }else{
1704b5f4f4eSchris            $cache->removeCache();                     //try to delete cachefile
171ea2a4271SAndreas Gohr            if($conf['allowdebug'] && $format=='xhtml') $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
172c112d578Sandi        }
173c112d578Sandi    }
174c112d578Sandi
175c112d578Sandi    return $parsed;
176c112d578Sandi}
177c112d578Sandi
178c112d578Sandi/**
179c112d578Sandi * Returns the render instructions for a file
180c112d578Sandi *
181c112d578Sandi * Uses and creates a serialized cache file
182c112d578Sandi *
183c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
184c112d578Sandi */
1854b5f4f4eSchrisfunction p_cached_instructions($file,$cacheonly=false,$id='') {
186c112d578Sandi    global $conf;
18747b2d319SAndreas Gohr    static $run = null;
18847b2d319SAndreas Gohr    if(is_null($run)) $run = array();
189c112d578Sandi
1904b5f4f4eSchris    $cache = new cache_instructions($id, $file);
191c112d578Sandi
19247b2d319SAndreas Gohr    if ($cacheonly || $cache->useCache() || isset($run[$file])) {
1934b5f4f4eSchris        return $cache->retrieveCache();
194c112d578Sandi    } else if (@file_exists($file)) {
195c112d578Sandi        // no cache - do some work
196bde4e341SGalaxyMaster        $ins = p_get_instructions(io_readWikiPage($file,$id));
197cbaf4259SChris Smith        if ($cache->storeCache($ins)) {
19847b2d319SAndreas Gohr            $run[$file] = true; // we won't rebuild these instructions in the same run again
199cbaf4259SChris Smith        } else {
200cbaf4259SChris Smith            msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1);
201cbaf4259SChris Smith        }
202c112d578Sandi        return $ins;
203c112d578Sandi    }
204c112d578Sandi
2053b3f8916SAndreas Gohr    return null;
206c112d578Sandi}
207c112d578Sandi
208c112d578Sandi/**
209c112d578Sandi * turns a page into a list of instructions
210c112d578Sandi *
211c112d578Sandi * @author Harry Fuecks <hfuecks@gmail.com>
212c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
213c112d578Sandi */
2146bbae538Sandifunction p_get_instructions($text){
215c112d578Sandi
216107b01d6Sandi    $modes = p_get_parsermodes();
217ee20e7d1Sandi
218c112d578Sandi    // Create the parser
21967f9913dSAndreas Gohr    $Parser = new Doku_Parser();
220c112d578Sandi
221c112d578Sandi    // Add the Handler
22267f9913dSAndreas Gohr    $Parser->Handler = new Doku_Handler();
223c112d578Sandi
224107b01d6Sandi    //add modes to parser
225107b01d6Sandi    foreach($modes as $mode){
226107b01d6Sandi        $Parser->addMode($mode['mode'],$mode['obj']);
227c112d578Sandi    }
228c112d578Sandi
229c112d578Sandi    // Do the parsing
230677844afSchris    trigger_event('PARSER_WIKITEXT_PREPROCESS', $text);
231a2d649c4Sandi    $p = $Parser->parse($text);
232ee20e7d1Sandi    //  dbg($p);
233a2d649c4Sandi    return $p;
234c112d578Sandi}
235c112d578Sandi
236c112d578Sandi/**
23739a89382SEsther Brunner * returns the metadata of a page
23839a89382SEsther Brunner *
2394a819402SMichael Hamann * @param string $id The id of the page the metadata should be returned from
2404a819402SMichael Hamann * @param string $key The key of the metdata value that shall be read (by default everything) - separate hierarchies by " " like "date created"
241*67c15eceSMichael Hamann * @param int $render If the page should be rendererd - possible values:
242*67c15eceSMichael Hamann *     METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE, default:
243*67c15eceSMichael Hamann *     METADATA_RENDER_USING_CACHE
2444a819402SMichael Hamann * @return mixed The requested metadata fields
2454a819402SMichael Hamann *
24639a89382SEsther Brunner * @author Esther Brunner <esther@kaffeehaus.ch>
24798214867SMichael Hamann * @author Michael Hamann <michael@content-space.de>
24839a89382SEsther Brunner */
249*67c15eceSMichael Hamannfunction p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){
2501172f8dcSAdrian Lang    global $ID;
2516afe8dcaSchris
2520a7e3bceSchris    // cache the current page
2530a7e3bceSchris    // Benchmarking shows the current page's metadata is generally the only page metadata
2540a7e3bceSchris    // accessed several times. This may catch a few other pages, but that shouldn't be an issue.
2550a7e3bceSchris    $cache = ($ID == $id);
2560a7e3bceSchris    $meta = p_read_metadata($id, $cache);
25739a89382SEsther Brunner
258*67c15eceSMichael Hamann    if (!is_numeric($render)) {
259*67c15eceSMichael Hamann        if ($render) {
260*67c15eceSMichael Hamann            $render = METADATA_RENDER_USING_SIMPLE_CACHE;
261*67c15eceSMichael Hamann        } else {
262*67c15eceSMichael Hamann            $render = METADATA_DONT_RENDER;
263*67c15eceSMichael Hamann        }
264*67c15eceSMichael Hamann    }
265*67c15eceSMichael Hamann
26698214867SMichael Hamann    // prevent recursive calls in the cache
26798214867SMichael Hamann    static $recursion = false;
268*67c15eceSMichael Hamann    if (!$recursion && $render != METADATA_DONT_RENDER && page_exists($id)){
26998214867SMichael Hamann        $recursion = true;
27098214867SMichael Hamann
27198214867SMichael Hamann        $cachefile = new cache_renderer($id, wikiFN($id), 'metadata');
27298214867SMichael Hamann
273*67c15eceSMichael Hamann        $do_render = false;
274*67c15eceSMichael Hamann        if ($render == METADATA_RENDER_USING_SIMPLE_CACHE) {
275*67c15eceSMichael Hamann            $pagefn = wikiFN($id);
276*67c15eceSMichael Hamann            $metafn = metaFN($id, '.meta');
277*67c15eceSMichael Hamann            if (!@file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) {
278*67c15eceSMichael Hamann                $do_render = true;
279*67c15eceSMichael Hamann            }
280*67c15eceSMichael Hamann        } elseif (!$cachefile->useCache()){
281*67c15eceSMichael Hamann            $do_render = true;
282*67c15eceSMichael Hamann        }
283*67c15eceSMichael Hamann        if ($do_render) {
28469ba640bSMichael Hamann            $old_meta = $meta;
28539a89382SEsther Brunner            $meta = p_render_metadata($id, $meta);
28669ba640bSMichael Hamann            // only update the file when the metadata has been changed
28769ba640bSMichael Hamann            if ($meta == $old_meta || p_save_metadata($id, $meta)) {
28898214867SMichael Hamann                // store a timestamp in order to make sure that the cachefile is touched
28998214867SMichael Hamann                $cachefile->storeCache(time());
29079c1bbfeSMichael Hamann            } elseif ($meta != $old_meta) {
29198214867SMichael Hamann                msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1);
29298214867SMichael Hamann            }
29398214867SMichael Hamann        }
29498214867SMichael Hamann
29598214867SMichael Hamann        $recursion = false;
2966afe8dcaSchris    }
29739a89382SEsther Brunner
298ebf65d37SAdrian Lang    $val = $meta['current'];
299ebf65d37SAdrian Lang
30039a89382SEsther Brunner    // filter by $key
301569a0019SAdrian Lang    foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) {
302ebf65d37SAdrian Lang        if (!isset($val[$cur_key])) {
303ebf65d37SAdrian Lang            return null;
30466d29756SChris Smith        }
305ebf65d37SAdrian Lang        $val = $val[$cur_key];
30639a89382SEsther Brunner    }
307ebf65d37SAdrian Lang    return $val;
30839a89382SEsther Brunner}
30939a89382SEsther Brunner
31039a89382SEsther Brunner/**
31139a89382SEsther Brunner * sets metadata elements of a page
31239a89382SEsther Brunner *
313a365baeeSDominik Eckelmann * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata
314a365baeeSDominik Eckelmann *
315a365baeeSDominik Eckelmann * @param String  $id         is the ID of a wiki page
316a365baeeSDominik Eckelmann * @param Array   $data       is an array with key ⇒ value pairs to be set in the metadata
317a365baeeSDominik Eckelmann * @param Boolean $render     whether or not the page metadata should be generated with the renderer
318a365baeeSDominik Eckelmann * @param Boolean $persistent indicates whether or not the particular metadata value will persist through
319a365baeeSDominik Eckelmann *                            the next metadata rendering.
320a365baeeSDominik Eckelmann * @return boolean true on success
321a365baeeSDominik Eckelmann *
32239a89382SEsther Brunner * @author Esther Brunner <esther@kaffeehaus.ch>
3230e5fde48SMichael Hamann * @author Michael Hamann <michael@content-space.de>
32439a89382SEsther Brunner */
3250a7e3bceSchrisfunction p_set_metadata($id, $data, $render=false, $persistent=true){
32639a89382SEsther Brunner    if (!is_array($data)) return false;
32739a89382SEsther Brunner
3280e5fde48SMichael Hamann    global $ID, $METADATA_RENDERERS;
3290a7e3bceSchris
3300e5fde48SMichael Hamann    // if there is currently a renderer change the data in the renderer instead
3310e5fde48SMichael Hamann    if (isset($METADATA_RENDERERS[$id])) {
3320e5fde48SMichael Hamann        $orig =& $METADATA_RENDERERS[$id];
3330e5fde48SMichael Hamann        $meta = $orig;
3340e5fde48SMichael Hamann    } else {
3350a7e3bceSchris        // cache the current page
3360a7e3bceSchris        $cache = ($ID == $id);
3370a7e3bceSchris        $orig = p_read_metadata($id, $cache);
33839a89382SEsther Brunner
33939a89382SEsther Brunner        // render metadata first?
3400a7e3bceSchris        $meta = $render ? p_render_metadata($id, $orig) : $orig;
3410e5fde48SMichael Hamann    }
34239a89382SEsther Brunner
34339a89382SEsther Brunner    // now add the passed metadata
34439a89382SEsther Brunner    $protected = array('description', 'date', 'contributor');
34539a89382SEsther Brunner    foreach ($data as $key => $value){
34639a89382SEsther Brunner
34739a89382SEsther Brunner        // be careful with sub-arrays of $meta['relation']
34839a89382SEsther Brunner        if ($key == 'relation'){
3490a7e3bceSchris
35039a89382SEsther Brunner            foreach ($value as $subkey => $subvalue){
35166d29756SChris Smith                $meta['current'][$key][$subkey] = !empty($meta['current'][$key][$subkey]) ? array_merge($meta['current'][$key][$subkey], $subvalue) : $subvalue;
3520a7e3bceSchris                if ($persistent)
35366d29756SChris Smith                    $meta['persistent'][$key][$subkey] = !empty($meta['persistent'][$key][$subkey]) ? array_merge($meta['persistent'][$key][$subkey], $subvalue) : $subvalue;
35439a89382SEsther Brunner            }
35539a89382SEsther Brunner
35639a89382SEsther Brunner            // be careful with some senisitive arrays of $meta
35739a89382SEsther Brunner        } elseif (in_array($key, $protected)){
3580a7e3bceSchris
35966d29756SChris Smith            // these keys, must have subkeys - a legitimate value must be an array
36039a89382SEsther Brunner            if (is_array($value)) {
36166d29756SChris Smith                $meta['current'][$key] = !empty($meta['current'][$key]) ? array_merge($meta['current'][$key],$value) : $value;
3620a7e3bceSchris
3630a7e3bceSchris                if ($persistent) {
36466d29756SChris Smith                    $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? array_merge($meta['persistent'][$key],$value) : $value;
3650a7e3bceSchris                }
36639a89382SEsther Brunner            }
36739a89382SEsther Brunner
36839a89382SEsther Brunner            // no special treatment for the rest
36939a89382SEsther Brunner        } else {
3700a7e3bceSchris            $meta['current'][$key] = $value;
3710a7e3bceSchris            if ($persistent) $meta['persistent'][$key] = $value;
37239a89382SEsther Brunner        }
37339a89382SEsther Brunner    }
37439a89382SEsther Brunner
37539a89382SEsther Brunner    // save only if metadata changed
37639a89382SEsther Brunner    if ($meta == $orig) return true;
3776afe8dcaSchris
3780e5fde48SMichael Hamann    if (isset($METADATA_RENDERERS[$id])) {
3790e5fde48SMichael Hamann        // set both keys individually as the renderer has references to the individual keys
3800e5fde48SMichael Hamann        $METADATA_RENDERERS[$id]['current']    = $meta['current'];
3810e5fde48SMichael Hamann        $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent'];
3820e5fde48SMichael Hamann    } else {
3831172f8dcSAdrian Lang        return p_save_metadata($id, $meta);
38439a89382SEsther Brunner    }
3850e5fde48SMichael Hamann}
38639a89382SEsther Brunner
38739a89382SEsther Brunner/**
3883d1f9ec3SMichael Klier * Purges the non-persistant part of the meta data
3893d1f9ec3SMichael Klier * used on page deletion
3903d1f9ec3SMichael Klier *
3913d1f9ec3SMichael Klier * @author Michael Klier <chi@chimeric.de>
3923d1f9ec3SMichael Klier */
3933d1f9ec3SMichael Klierfunction p_purge_metadata($id) {
3943d1f9ec3SMichael Klier    $meta = p_read_metadata($id);
3953d1f9ec3SMichael Klier    foreach($meta['current'] as $key => $value) {
3963d1f9ec3SMichael Klier        if(is_array($meta[$key])) {
3973d1f9ec3SMichael Klier            $meta['current'][$key] = array();
3983d1f9ec3SMichael Klier        } else {
3993d1f9ec3SMichael Klier            $meta['current'][$key] = '';
4003d1f9ec3SMichael Klier        }
4011172f8dcSAdrian Lang
4023d1f9ec3SMichael Klier    }
4031172f8dcSAdrian Lang    return p_save_metadata($id, $meta);
4043d1f9ec3SMichael Klier}
4053d1f9ec3SMichael Klier
4063d1f9ec3SMichael Klier/**
4070a7e3bceSchris * read the metadata from source/cache for $id
4080a7e3bceSchris * (internal use only - called by p_get_metadata & p_set_metadata)
4090a7e3bceSchris *
4100a7e3bceSchris * @author   Christopher Smith <chris@jalakai.co.uk>
4110a7e3bceSchris *
4120a7e3bceSchris * @param    string   $id      absolute wiki page id
4130a7e3bceSchris * @param    bool     $cache   whether or not to cache metadata in memory
4140a7e3bceSchris *                             (only use for metadata likely to be accessed several times)
4150a7e3bceSchris *
4160a7e3bceSchris * @return   array             metadata
4170a7e3bceSchris */
4180a7e3bceSchrisfunction p_read_metadata($id,$cache=false) {
4190a7e3bceSchris    global $cache_metadata;
4200a7e3bceSchris
4213a50618cSgweissbach    if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id];
4220a7e3bceSchris
4230a7e3bceSchris    $file = metaFN($id, '.meta');
4240a7e3bceSchris    $meta = @file_exists($file) ? unserialize(io_readFile($file, false)) : array('current'=>array(),'persistent'=>array());
4250a7e3bceSchris
4260a7e3bceSchris    if ($cache) {
4273a50618cSgweissbach        $cache_metadata[(string)$id] = $meta;
4280a7e3bceSchris    }
4290a7e3bceSchris
4300a7e3bceSchris    return $meta;
4310a7e3bceSchris}
4320a7e3bceSchris
4330a7e3bceSchris/**
4341172f8dcSAdrian Lang * This is the backend function to save a metadata array to a file
4351172f8dcSAdrian Lang *
4361172f8dcSAdrian Lang * @param    string   $id      absolute wiki page id
4371172f8dcSAdrian Lang * @param    array    $meta    metadata
4381172f8dcSAdrian Lang *
4391172f8dcSAdrian Lang * @return   bool              success / fail
4401172f8dcSAdrian Lang */
4411172f8dcSAdrian Langfunction p_save_metadata($id, $meta) {
4421172f8dcSAdrian Lang    // sync cached copies, including $INFO metadata
4431172f8dcSAdrian Lang    global $cache_metadata, $INFO;
4441172f8dcSAdrian Lang
4451172f8dcSAdrian Lang    if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta;
4461172f8dcSAdrian Lang    if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; }
4471172f8dcSAdrian Lang
4481172f8dcSAdrian Lang    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
4491172f8dcSAdrian Lang}
4501172f8dcSAdrian Lang
4511172f8dcSAdrian Lang/**
45239a89382SEsther Brunner * renders the metadata of a page
45339a89382SEsther Brunner *
45439a89382SEsther Brunner * @author Esther Brunner <esther@kaffeehaus.ch>
45539a89382SEsther Brunner */
45639a89382SEsther Brunnerfunction p_render_metadata($id, $orig){
45748924015SAndreas Gohr    // make sure the correct ID is in global ID
4580e5fde48SMichael Hamann    global $ID, $METADATA_RENDERERS;
4590e5fde48SMichael Hamann
4600e5fde48SMichael Hamann    // avoid recursive rendering processes for the same id
4610e5fde48SMichael Hamann    if (isset($METADATA_RENDERERS[$id]))
4620e5fde48SMichael Hamann        return $orig;
4630e5fde48SMichael Hamann
4640e5fde48SMichael Hamann    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
4650e5fde48SMichael Hamann    $METADATA_RENDERERS[$id] =& $orig;
4660e5fde48SMichael Hamann
46748924015SAndreas Gohr    $keep = $ID;
46848924015SAndreas Gohr    $ID   = $id;
46948924015SAndreas Gohr
4700a7e3bceSchris    // add an extra key for the event - to tell event handlers the page whose metadata this is
4710a7e3bceSchris    $orig['page'] = $id;
4720a7e3bceSchris    $evt = new Doku_Event('PARSER_METADATA_RENDER', $orig);
4730a7e3bceSchris    if ($evt->advise_before()) {
4740a7e3bceSchris
47539a89382SEsther Brunner        require_once DOKU_INC."inc/parser/metadata.php";
47639a89382SEsther Brunner
47739a89382SEsther Brunner        // get instructions
4784b5f4f4eSchris        $instructions = p_cached_instructions(wikiFN($id),false,$id);
47948924015SAndreas Gohr        if(is_null($instructions)){
48048924015SAndreas Gohr            $ID = $keep;
4810e5fde48SMichael Hamann            unset($METADATA_RENDERERS[$id]);
48248924015SAndreas Gohr            return null; // something went wrong with the instructions
48348924015SAndreas Gohr        }
48439a89382SEsther Brunner
48539a89382SEsther Brunner        // set up the renderer
48667f9913dSAndreas Gohr        $renderer = new Doku_Renderer_metadata();
4870e5fde48SMichael Hamann        $renderer->meta =& $orig['current'];
4880e5fde48SMichael Hamann        $renderer->persistent =& $orig['persistent'];
48939a89382SEsther Brunner
49039a89382SEsther Brunner        // loop through the instructions
49139a89382SEsther Brunner        foreach ($instructions as $instruction){
49239a89382SEsther Brunner            // execute the callback against the renderer
4933b748871SAndreas Gohr            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
49439a89382SEsther Brunner        }
49539a89382SEsther Brunner
4960e5fde48SMichael Hamann        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
4970a7e3bceSchris    }
4980a7e3bceSchris    $evt->advise_after();
4990a7e3bceSchris
5000e5fde48SMichael Hamann    // clean up
50148924015SAndreas Gohr    $ID = $keep;
5020e5fde48SMichael Hamann    unset($METADATA_RENDERERS[$id]);
5030a7e3bceSchris    return $evt->result;
50439a89382SEsther Brunner}
50539a89382SEsther Brunner
50639a89382SEsther Brunner/**
507107b01d6Sandi * returns all available parser syntax modes in correct order
508107b01d6Sandi *
509107b01d6Sandi * @author Andreas Gohr <andi@splitbrain.org>
510107b01d6Sandi */
511107b01d6Sandifunction p_get_parsermodes(){
512107b01d6Sandi    global $conf;
513107b01d6Sandi
514107b01d6Sandi    //reuse old data
515107b01d6Sandi    static $modes = null;
516107b01d6Sandi    if($modes != null){
517107b01d6Sandi        return $modes;
518107b01d6Sandi    }
519107b01d6Sandi
520107b01d6Sandi    //import parser classes and mode definitions
521107b01d6Sandi    require_once DOKU_INC . 'inc/parser/parser.php';
522107b01d6Sandi
523107b01d6Sandi    // we now collect all syntax modes and their objects, then they will
524107b01d6Sandi    // be sorted and added to the parser in correct order
525107b01d6Sandi    $modes = array();
526107b01d6Sandi
527107b01d6Sandi    // add syntax plugins
528107b01d6Sandi    $pluginlist = plugin_list('syntax');
529107b01d6Sandi    if(count($pluginlist)){
530107b01d6Sandi        global $PARSER_MODES;
531107b01d6Sandi        $obj = null;
532107b01d6Sandi        foreach($pluginlist as $p){
533c90b2fb1Schris            if(!$obj =& plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
534107b01d6Sandi            $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
535107b01d6Sandi            //add to modes
536107b01d6Sandi            $modes[] = array(
537107b01d6Sandi                    'sort' => $obj->getSort(),
538107b01d6Sandi                    'mode' => "plugin_$p",
539107b01d6Sandi                    'obj'  => $obj,
540107b01d6Sandi                    );
541a46d0d65SAndreas Gohr            unset($obj); //remove the reference
542107b01d6Sandi        }
543107b01d6Sandi    }
544107b01d6Sandi
545107b01d6Sandi    // add default modes
546107b01d6Sandi    $std_modes = array('listblock','preformatted','notoc','nocache',
547107b01d6Sandi            'header','table','linebreak','footnote','hr',
548107b01d6Sandi            'unformatted','php','html','code','file','quote',
549e77ea1bcSAndreas Gohr            'internallink','rss','media','externallink',
550e77ea1bcSAndreas Gohr            'emaillink','windowssharelink','eol');
551e77ea1bcSAndreas Gohr    if($conf['typography']){
552e77ea1bcSAndreas Gohr        $std_modes[] = 'quotes';
553e77ea1bcSAndreas Gohr        $std_modes[] = 'multiplyentity';
554e77ea1bcSAndreas Gohr    }
555107b01d6Sandi    foreach($std_modes as $m){
556107b01d6Sandi        $class = "Doku_Parser_Mode_$m";
557107b01d6Sandi        $obj   = new $class();
558107b01d6Sandi        $modes[] = array(
559107b01d6Sandi                'sort' => $obj->getSort(),
560107b01d6Sandi                'mode' => $m,
561107b01d6Sandi                'obj'  => $obj
562107b01d6Sandi                );
563107b01d6Sandi    }
564107b01d6Sandi
565107b01d6Sandi    // add formatting modes
566107b01d6Sandi    $fmt_modes = array('strong','emphasis','underline','monospace',
567107b01d6Sandi            'subscript','superscript','deleted');
568107b01d6Sandi    foreach($fmt_modes as $m){
569107b01d6Sandi        $obj   = new Doku_Parser_Mode_formatting($m);
570107b01d6Sandi        $modes[] = array(
571107b01d6Sandi                'sort' => $obj->getSort(),
572107b01d6Sandi                'mode' => $m,
573107b01d6Sandi                'obj'  => $obj
574107b01d6Sandi                );
575107b01d6Sandi    }
576107b01d6Sandi
577107b01d6Sandi    // add modes which need files
578107b01d6Sandi    $obj     = new Doku_Parser_Mode_smiley(array_keys(getSmileys()));
579107b01d6Sandi    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
580107b01d6Sandi    $obj     = new Doku_Parser_Mode_acronym(array_keys(getAcronyms()));
581107b01d6Sandi    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
582107b01d6Sandi    $obj     = new Doku_Parser_Mode_entity(array_keys(getEntities()));
583107b01d6Sandi    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
584107b01d6Sandi
585107b01d6Sandi    // add optional camelcase mode
586107b01d6Sandi    if($conf['camelcase']){
587107b01d6Sandi        $obj     = new Doku_Parser_Mode_camelcaselink();
588107b01d6Sandi        $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
589107b01d6Sandi    }
590107b01d6Sandi
591107b01d6Sandi    //sort modes
592107b01d6Sandi    usort($modes,'p_sort_modes');
593107b01d6Sandi
594107b01d6Sandi    return $modes;
595107b01d6Sandi}
596107b01d6Sandi
597107b01d6Sandi/**
598107b01d6Sandi * Callback function for usort
599107b01d6Sandi *
600107b01d6Sandi * @author Andreas Gohr <andi@splitbrain.org>
601107b01d6Sandi */
602107b01d6Sandifunction p_sort_modes($a, $b){
603107b01d6Sandi    if($a['sort'] == $b['sort']) return 0;
604107b01d6Sandi    return ($a['sort'] < $b['sort']) ? -1 : 1;
605107b01d6Sandi}
606107b01d6Sandi
607107b01d6Sandi/**
608ac83b9d8Sandi * Renders a list of instruction to the specified output mode
609c112d578Sandi *
6107a8cc57eSElan Ruusamäe * In the $info array is information from the renderer returned
6119dc2c2afSandi *
612c112d578Sandi * @author Harry Fuecks <hfuecks@gmail.com>
613c112d578Sandi * @author Andreas Gohr <andi@splitbrain.org>
614c112d578Sandi */
6159dc2c2afSandifunction p_render($mode,$instructions,&$info){
616c112d578Sandi    if(is_null($instructions)) return '';
617c112d578Sandi
618d968d3e5SChris Smith    $Renderer =& p_get_renderer($mode);
619d968d3e5SChris Smith    if (is_null($Renderer)) return null;
620c327d6c4SAndreas Gohr
621d968d3e5SChris Smith    $Renderer->reset();
622c112d578Sandi
623c112d578Sandi    $Renderer->smileys = getSmileys();
624c112d578Sandi    $Renderer->entities = getEntities();
625c112d578Sandi    $Renderer->acronyms = getAcronyms();
626c112d578Sandi    $Renderer->interwiki = getInterwiki();
627c112d578Sandi
628c112d578Sandi    // Loop through the instructions
629c112d578Sandi    foreach ( $instructions as $instruction ) {
630c112d578Sandi        // Execute the callback against the Renderer
631c112d578Sandi        call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]);
632c112d578Sandi    }
6339dc2c2afSandi
6349dc2c2afSandi    //set info array
6359dc2c2afSandi    $info = $Renderer->info;
6369dc2c2afSandi
637677844afSchris    // Post process and return the output
638677844afSchris    $data = array($mode,& $Renderer->doc);
639677844afSchris    trigger_event('RENDERER_CONTENT_POSTPROCESS',$data);
640c112d578Sandi    return $Renderer->doc;
641c112d578Sandi}
642c112d578Sandi
643d968d3e5SChris Smithfunction & p_get_renderer($mode) {
6447aea91afSChris Smith    global $conf, $plugin_controller;
645d968d3e5SChris Smith
646d968d3e5SChris Smith    $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode;
647d968d3e5SChris Smith
648d968d3e5SChris Smith    // try default renderer first:
649d968d3e5SChris Smith    $file = DOKU_INC."inc/parser/$rname.php";
650d968d3e5SChris Smith    if(@file_exists($file)){
651d968d3e5SChris Smith        require_once $file;
652d968d3e5SChris Smith        $rclass = "Doku_Renderer_$rname";
653d968d3e5SChris Smith
654d968d3e5SChris Smith        if ( !class_exists($rclass) ) {
655d968d3e5SChris Smith            trigger_error("Unable to resolve render class $rclass",E_USER_WARNING);
656d968d3e5SChris Smith            msg("Renderer '$rname' for $mode not valid",-1);
657d968d3e5SChris Smith            return null;
658d968d3e5SChris Smith        }
65967f9913dSAndreas Gohr        $Renderer = new $rclass();
660d968d3e5SChris Smith    }else{
6617b27382cSGina Haeussge        // Maybe a plugin/component is available?
6627b27382cSGina Haeussge        list($plugin, $component) = $plugin_controller->_splitName($rname);
6637b27382cSGina Haeussge        if (!$plugin_controller->isdisabled($plugin)){
664f6ec8df8SAdrian Lang            $Renderer =& $plugin_controller->load('renderer',$rname);
6657aea91afSChris Smith        }
6667aea91afSChris Smith
667d968d3e5SChris Smith        if(is_null($Renderer)){
668d968d3e5SChris Smith            msg("No renderer '$rname' found for mode '$mode'",-1);
669d968d3e5SChris Smith            return null;
670d968d3e5SChris Smith        }
671d968d3e5SChris Smith    }
672d968d3e5SChris Smith
673d968d3e5SChris Smith    return $Renderer;
674d968d3e5SChris Smith}
675d968d3e5SChris Smith
676bb0a59d4Sjan/**
677bb0a59d4Sjan * Gets the first heading from a file
678bb0a59d4Sjan *
679*67c15eceSMichael Hamann * After P_GET_FIRST_HEADING_METADATA_LIMIT requests for different pages the title
680*67c15eceSMichael Hamann * index will be loaded and used instead. Use METADATA_DONT_RENDER when you are
681*67c15eceSMichael Hamann * requesting a lot of titles, METADATA_RENDER_USING_CACHE when you think
682*67c15eceSMichael Hamann * rendering the page although it hasn't changed might be needed (or also
683*67c15eceSMichael Hamann * want to influence rendering using events) and METADATA_RENDER_USING_SIMPLE_CACHE
684*67c15eceSMichael Hamann * otherwise. Use METADATA_RENDER_USING_CACHE with care as it could cause
685*67c15eceSMichael Hamann * parsing and rendering a lot of pages in one request.
686*67c15eceSMichael Hamann *
687fc18c0fbSchris * @param   string   $id       dokuwiki page id
688*67c15eceSMichael Hamann * @param   int      $render   rerender if first heading not known
689*67c15eceSMichael Hamann *                             default: METADATA_RENDER_USING_SIMPLE_CACHE
690*67c15eceSMichael Hamann *                             Possible values: METADATA_DONT_RENDER,
691*67c15eceSMichael Hamann *                                              METADATA_RENDER_USING_SIMPLE_CACHE,
692*67c15eceSMichael Hamann *                                              METADATA_RENDER_USING_CACHE
693fc18c0fbSchris *
69495dbfe57SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
695bf0c93c2SMichael Hamann * @author Michael Hamann <michael@content-space.de>
696bb0a59d4Sjan */
697*67c15eceSMichael Hamannfunction p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){
698bf0c93c2SMichael Hamann    // counter how many titles have been requested using p_get_metadata
699ff725173SMichael Hamann    static $count = 1;
700bf0c93c2SMichael Hamann    // the index of all titles, only loaded when many titles are requested
701bf0c93c2SMichael Hamann    static $title_index = null;
702bf0c93c2SMichael Hamann    // cache for titles requested using p_get_metadata
703bf0c93c2SMichael Hamann    static $title_cache = array();
704bf0c93c2SMichael Hamann
705bf0c93c2SMichael Hamann    $id = cleanID($id);
706bf0c93c2SMichael Hamann
707bf0c93c2SMichael Hamann    // check if this title has already been requested
708bf0c93c2SMichael Hamann    if (isset($title_cache[$id]))
709bf0c93c2SMichael Hamann      return $title_cache[$id];
710bf0c93c2SMichael Hamann
711bf0c93c2SMichael Hamann    // check if already too many titles have been requested and probably
712bf0c93c2SMichael Hamann    // using the title index is better
713ff725173SMichael Hamann    if ($count > P_GET_FIRST_HEADING_METADATA_LIMIT) {
714bf0c93c2SMichael Hamann        if (is_null($title_index)) {
715bf0c93c2SMichael Hamann            $pages  = array_map('rtrim', idx_getIndex('page', ''));
716bf0c93c2SMichael Hamann            $titles = array_map('rtrim', idx_getIndex('title', ''));
717bf0c93c2SMichael Hamann            // check for corrupt title index #FS2076
718bf0c93c2SMichael Hamann            if(count($pages) != count($titles)){
719bf0c93c2SMichael Hamann                $titles = array_fill(0,count($pages),'');
720bf0c93c2SMichael Hamann                @unlink($conf['indexdir'].'/title.idx'); // will be rebuilt in inc/init.php
72165f40513SMichael Hamann            } else {
72265f40513SMichael Hamann                if (!empty($pages)) // array_combine throws a warning when the parameters are empty arrays
723bf0c93c2SMichael Hamann                    $title_index = array_combine($pages, $titles);
72465f40513SMichael Hamann                else
72565f40513SMichael Hamann                    $title_index = array();
726bf0c93c2SMichael Hamann            }
72765f40513SMichael Hamann        }
72865f40513SMichael Hamann        if (!empty($title_index)) // don't use the index when it obviously isn't working
729bf0c93c2SMichael Hamann            return $title_index[$id];
730bf0c93c2SMichael Hamann    }
731bf0c93c2SMichael Hamann
732bf0c93c2SMichael Hamann    ++$count;
733bf0c93c2SMichael Hamann    $title_cache[$id] = p_get_metadata($id,'title',$render);
734bf0c93c2SMichael Hamann    return $title_cache[$id];
735bb0a59d4Sjan}
736bb0a59d4Sjan
7378f7d700cSchris/**
7388f7d700cSchris * Wrapper for GeSHi Code Highlighter, provides caching of its output
7398f7d700cSchris *
7405d568b99SChris Smith * @param  string   $code       source code to be highlighted
7415d568b99SChris Smith * @param  string   $language   language to provide highlighting
7425d568b99SChris Smith * @param  string   $wrapper    html element to wrap the returned highlighted text
7435d568b99SChris Smith *
7448f7d700cSchris * @author Christopher Smith <chris@jalakai.co.uk>
74535fbe9efSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
7468f7d700cSchris */
7475d568b99SChris Smithfunction p_xhtml_cached_geshi($code, $language, $wrapper='pre') {
748f8121585SChris Smith    global $conf, $config_cascade;
74935fbe9efSAndreas Gohr    $language = strtolower($language);
7505d568b99SChris Smith
7515d568b99SChris Smith    // remove any leading or trailing blank lines
7525d568b99SChris Smith    $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code);
7535d568b99SChris Smith
7548f7d700cSchris    $cache = getCacheName($language.$code,".code");
75535fbe9efSAndreas Gohr    $ctime = @filemtime($cache);
75635fbe9efSAndreas Gohr    if($ctime && !$_REQUEST['purge'] &&
757f8121585SChris Smith            $ctime > filemtime(DOKU_INC.'inc/geshi.php') &&                 // geshi changed
758f8121585SChris Smith            $ctime > @filemtime(DOKU_INC.'inc/geshi/'.$language.'.php') &&  // language syntax definition changed
759f8121585SChris Smith            $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed
7608f7d700cSchris        $highlighted_code = io_readFile($cache, false);
7618f7d700cSchris
7628f7d700cSchris    } else {
7638f7d700cSchris
76435fbe9efSAndreas Gohr        $geshi = new GeSHi($code, $language, DOKU_INC . 'inc/geshi');
7658f7d700cSchris        $geshi->set_encoding('utf-8');
7668f7d700cSchris        $geshi->enable_classes();
7678f7d700cSchris        $geshi->set_header_type(GESHI_HEADER_PRE);
7688f7d700cSchris        $geshi->set_link_target($conf['target']['extern']);
7698f7d700cSchris
7705d568b99SChris Smith        // remove GeSHi's wrapper element (we'll replace it with our own later)
7715d568b99SChris Smith        // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
77269ddc332SAnika Henke        $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r");
7738f7d700cSchris        io_saveFile($cache,$highlighted_code);
7748f7d700cSchris    }
7758f7d700cSchris
7765d568b99SChris Smith    // add a wrapper element if required
7775d568b99SChris Smith    if ($wrapper) {
7785d568b99SChris Smith        return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>";
7795d568b99SChris Smith    } else {
7808f7d700cSchris        return $highlighted_code;
7818f7d700cSchris    }
7825d568b99SChris Smith}
7838f7d700cSchris
784