1<?php
2/**
3 *
4 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author     Christoph Clausen <christoph.clausen@gmail.com>
6 */
7// must be run within Dokuwiki
8if(!defined('DOKU_INC')) die();
9$dataEntryFile = DOKU_PLUGIN.'data/syntax/entry.php';
10if(file_exists($dataEntryFile)){
11    require_once $dataEntryFile;
12} else {
13    msg('datatemplate: Cannot find Data plugin.', -1);
14    return;
15}
16
17class syntax_plugin_datatemplate_entry extends syntax_plugin_data_entry {
18
19    /**
20     * @var $dthlp helper_plugin_data will hold the data helper plugin
21     */
22    var $dthlp = null;
23
24    /**
25     * Constructor. Load helper plugin
26     */
27    function __construct(){
28        $this->dthlp = plugin_load('helper', 'data');
29        if(!$this->dthlp) msg('Loading the data helper failed. Make sure the data plugin is installed.',-1);
30    }
31
32    /**
33     * Connect pattern to lexer
34     */
35    function connectTo($mode) {
36        $this->Lexer->addSpecialPattern('----+ *datatemplateentry(?: [ a-zA-Z0-9_]*)?-+\n.*?\n----+',
37                                        $mode,'plugin_datatemplate_entry');
38    }
39
40    /**
41     * Handle the match - parse the data
42     */
43    function handle($match, $state, $pos, Doku_Handler $handler){
44        // The parser of the parent class should have nicely parsed all
45        // parameters. We want to extract the template parameter and treat
46        // it separately.
47
48        // strip datatemplateentry to get right classes
49        $match = preg_replace('/datatemplateentry/', '', $match, 1);
50        $data = parent::handle($match, $state, $pos, $handler);
51        if(array_key_exists('template',  $data['cols'])) {
52            unset($data['cols']['template']);
53            $data['template'] = $data['data']['template'];
54            unset($data['data']['template']);
55        }
56        if(array_key_exists('template', $data)) {
57            $data['instructions'] = $this->_getInstructions($data);
58        }
59        return $data;
60    }
61
62
63    /**
64     * Create output or save the data
65     */
66    function render($format, Doku_Renderer $renderer, $data) {
67        global $ID;
68        switch ($format){
69            case 'xhtml':
70                /** @var $renderer Doku_Renderer_xhtml */
71                $this->_showData($data,$renderer);
72                return true;
73            case 'metadata':
74                /** @var $renderer Doku_Renderer_metadata */
75                $this->_saveRendereredData($data,$ID,$renderer);
76                return true;
77            case 'plugin_data_edit':
78                /** @var $renderer Doku_Renderer_plugin_data_edit */
79                $this->_editData($data, $renderer);
80                return true;
81            default:
82                return false;
83        }
84    }
85
86    /**
87     * Get template file name and check existance and access rights.
88     *
89     * @param string $template value of 'template' key in entry
90     * @return int|string
91     *           0 if the file does not exist
92     *          -1 if no permission to read the file
93     *          the file name otherwise
94     */
95    function _getFile($template) {
96        global $ID;
97        $wikipage = preg_split('/\#/u', $template, 2);
98
99        resolve_pageid(getNS($ID), $wikipage[0], $exists);
100
101        $file = wikiFN($wikipage[0]);
102
103        if (!@file_exists($file)) return 0;
104
105        if (auth_quickaclcheck($wikipage[0]) < 1) return -1;
106
107        return $file;
108    }
109
110    /**
111     * Generate wiki output from instructions
112     *
113     * @param $data array as returned by handle()
114     * @param &$R Doku_Renderer_xhtml
115     * @return bool|void
116     */
117    function _showData($data, &$R) {
118
119        if(!array_key_exists('template', $data)) {
120            // If keyword "template" not present, we can leave
121            // the rendering to the parent class.
122            parent::_showData($data, $R);
123            return true;
124        }
125
126        // Treat possible errors first
127        if($data['instructions'] == 0) {
128            $R->doc .= '<div class="datatemplateentry">';
129            $R->doc .= "Template {$data['template']} not found. ";
130            $R->internalLink($data['template'], '[Click here to create it]');
131            $R->doc .= '</div>';
132            return true;
133        } elseif ($data['instructions'] == -1) {
134            $R->doc .= '<div class="datatemplateentry"> No permissions to view the template </div>';
135            return true;
136        }
137
138        // embed the included page
139        $R->doc .= '<div class="datatemplateentry ' . $data['classes'] . '">';
140
141        // render the instructructions on the fly
142        $text = p_render('xhtml', $data['instructions'], $info);
143
144        // remove toc, section edit buttons and category tags
145        $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s',
146                          '#<!-- EDIT.*? \[(\d*-\d*)\] -->#e',
147                          '!<div class="category">.*?</div>!s');
148        $replace  = array('','','');
149        $text = preg_replace($patterns,$replace,$text);
150
151        $R->doc .= $text;
152        $R->doc .= '</div>';
153
154        return true;
155    }
156
157
158    /**
159     * Read and process template file and return wiki instructions.
160     * Passes through the return value of _getFile if the file does not exist or cannot be accessed.
161     * If no template was specified, return empty array.
162     *
163     * @param array $data return of handle()
164     * @return bool|int|string
165     *           0 if the template file does not exist
166     *          -1 if no permission to read the template file
167     *           otherwise the template page as list of instructions with replacements performed
168     */
169    function _getInstructions($data){
170        // Get the raw file, and parse it into its instructions. This could be cached... maybe.
171        $file = $this->_getFile($data['template']);
172        if(!is_string($file)) return $file;
173        $rawFile = io_readfile($file);
174
175        $replacers['raw_keys'] = array();
176        $replacers['raw_vals'] = array();
177        $replacers['keys'] = array();
178        $replacers['vals'] = array();
179
180        foreach($data['data'] as $key => $val){
181            if($val == '' || !count($val)) continue;
182
183            $replacers['keys'][]     = "@@" . $key . "@@";
184            $replacers['raw_keys'][] = "@@!" . $key . "@@";
185            if(is_array($val)){
186                $cnt = count($val);
187                $ret = '';
188                $ret_raw = '';
189                for ($i=0; $i<$cnt; $i++){
190                    $ret .= $this->_formatData($data['cols'][$key], $val[$i]);
191                    $ret_raw .= $val[$i];
192                    if($i < $cnt - 1) {
193                        $ret .= ', ';
194                        $ret_raw .= ', ';
195                    }
196                }
197                $replacers['vals'][] = $ret;
198                $replacers['raw_vals'][] = $ret_raw;
199            } else {
200                $replacers['vals'][] = $this->_formatData($data['cols'][$key], $val);
201                $replacers['raw_vals'][] = $val;
202            }
203        }
204        // First do raw replacements
205        $raw = str_replace($replacers['raw_keys'], $replacers['raw_vals'], $rawFile);
206        $raw = str_replace($replacers['keys'], $replacers['vals'], $raw);
207        $raw = preg_replace('/@@.*?@@/', '', $raw);
208        $instr = p_get_instructions($raw);
209
210        return $instr;
211    }
212
213    /**
214     * The data plugin defines this function in its helper class with the purpose
215     * to output XHTML code for the different column types.
216     *   We want to let the dokuwiki renderer generate the required output, such
217     * that also metadata is handled correctly. Hence, we will try to translate
218     * each column type to the corresponding dokuwiki syntax.
219     *
220     * @param array $column
221     * @param string $value value of column.
222     * @return string DokuWiki syntax interpretation of value
223     */
224    function _formatData($column, $value){
225        $vals = explode("\n",$value);
226        $outs = array();
227        foreach($vals as $val){
228            $val = trim($val);
229            if($val=='') continue;
230            $type = $column['type'];
231            if (is_array($type)) $type = $type['type'];
232            switch($type){
233                case 'page':
234                    $val = $this->dthlp->_addPrePostFixes($column['type'], $val);
235                    $outs[] = '[[' . $val. ']]';
236                    break;
237                case 'pageid':
238                case 'title':
239                    list($id,$title) = explode('|',$val,2);
240                    $id = $this->dthlp->_addPrePostFixes($column['type'], $id);
241                    $outs[] = '[[' . $id . '|' . $title . ']]';
242                    break;
243                case 'nspage':
244                    // no prefix/postfix here
245                    $val = ':'.$column['key'].":$val";
246                    $outs[] = '[[' . $val . ']]';
247                    break;
248                case 'mail':
249                    list($id,$title) = explode(' ',$val,2);
250                    $id = $this->dthlp->_addPrePostFixes($column['type'], $id);
251                    $outs[] = '[[' . $id . '|' . $title . ']]';
252                    break;
253                case 'url':
254                    $val = $this->dthlp->_addPrePostFixes($column['type'], $val);
255                    $outs[] = '[[' . $val . ']]';
256                    break;
257                case 'tag':
258                    #todo not handled by datatemplate so far
259                        $outs[] = '<a href="'.wl(str_replace('/',':',cleanID($column['key'])),array('dataflt'=>$column['key'].':'.$val )).
260                        '" title="'.sprintf($this->getLang('tagfilter'),hsc($val)).
261                        '" class="wikilink1">'.hsc($val).'</a>';
262                    break;
263                case 'wiki':
264                    $val = $this->dthlp->_addPrePostFixes($column['type'], $val);
265                    $outs[] = $val;
266                    break;
267                default:
268                    $val = $this->dthlp->_addPrePostFixes($column['type'], $val);
269                    if(substr($type,0,3) == 'img'){
270                        $sz = (int) substr($type,3);
271                        if(!$sz) $sz = 40;
272                        $title = $column['key'].': '.basename(str_replace(':','/',$val));
273                        $outs[] = '{{' . $val. ($sz ? '?'.$sz:'') .'|'.$title. '}}';
274                    }else{
275                        $outs[] = $val;
276                    }
277            }    //todo add type 'timestamp' ... returns html..
278        }
279        return join(', ',$outs);
280    }
281
282    /**
283     * Save date to the database
284     *
285     * We are overriding this function to properly generate the metadata for
286     * the parsed template, such that page title and table of contents are
287     * correct.
288     */
289    function _saveRendereredData($data,$id,&$renderer){
290        if(!array_key_exists('template', $data)) {
291            parent::_saveData($data, $id, $renderer->meta['title']);
292            return;
293        }
294
295        $file = $this->_getFile($data['template']);
296        $instr = $data['instructions'];
297        // If for some reason there are no instructions, don't do anything
298        // (Maybe for cache handling one should hand the template file name to the
299        // metadata, even though the file does not exist)
300        if(!is_string($file)) parent::_saveData($data, $id, $renderer->meta['title']);
301        $renderer->meta['relation']['haspart'][$file] = array('owner'=>$this->getPluginName());
302
303        // Remove document_start and document_end from instructions
304        array_shift($instr);
305        array_pop($instr);
306
307        // loop through the instructions
308        for($i = 0; $i < count($instr); $i++) {
309            call_user_func_array(array($renderer, $instr[$i][0]), $instr[$i][1]);
310        }
311
312        parent::_saveData($data, $id, $renderer->meta['title']);
313    }
314}
315/* Local Variables: */
316/* c-basic-offset: 4 */
317/* End: */
318