1<?php
2/**
3 * Filelist Plugin: Lists files matching a given glob pattern.
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Gina Haeussge <osd@foosel.net>
7 */
8
9define('DOKU_PLUGIN_FILELIST_NOMATCH', -1);
10define('DOKU_PLUGIN_FILELIST_OUTSIDEJAIL', -2);
11
12/**
13 * All DokuWiki plugins to extend the parser/rendering mechanism
14 * need to inherit from this class
15 */
16class syntax_plugin_filelist extends DokuWiki_Syntax_Plugin {
17
18    var $mediadir;
19    var $is_odt_export = false;
20
21    function __construct() {
22        global $conf;
23        $mediadir = $conf['mediadir'];
24        if (!$this->_path_is_absolute($mediadir)) {
25            $mediadir = DOKU_INC . '/' . $mediadir;
26        }
27        $this->mediadir = $this->_win_path_convert($this->_realpath($mediadir).'/');
28    }
29
30    function getType(){ return 'substition'; }
31    function getPType(){ return 'block'; }
32    function getSort(){ return 222; }
33
34    function connectTo($mode) {
35        $this->Lexer->addSpecialPattern('\{\{filename>.+?\}\}',$mode,'plugin_filelist');
36        $this->Lexer->addSpecialPattern('\{\{filelist>.+?\}\}',$mode,'plugin_filelist');
37    }
38
39    /**
40     * Handle the match
41     */
42    function handle($match, $state, $pos, Doku_Handler $handler) {
43
44        // do not allow the syntax in comments
45        if (!$this->getConf('allow_in_comments') && isset($_REQUEST['comment']))
46        return false;
47
48        $match = substr($match, 2, -2);
49        list($type, $match) = explode('>', $match, 2);
50        list($pattern, $flags) = explode('&', $match, 2);
51
52        if ($type == 'filename') {
53            if (strpos($flags, '|') !== FALSE) {
54                list($flags, $title) = explode('\|', $flags);
55            } else {
56                $title = '';
57            }
58        }
59
60        // load default config options
61        $flags = $this->getConf('defaults').'&'.$flags;
62
63        $flags = explode('&', $flags);
64        $params = array(
65            'sort' => 'name',
66            'order' => 'asc',
67            'index' => 0,
68            'limit' => 0,
69            'offset' => 0,
70            'style' => 'list',
71            'tableheader' => 0,
72            'tableshowsize' => 0,
73            'tableshowdate' => 0,
74            'direct' => 0,
75            'recursive' => 0,
76            'titlefile' => '_title.txt',
77            'cache' => 0,
78            'randlinks' => 0,
79            'preview' => 0,
80            'previewsize' => 32,
81            'link' => 2,
82            'showsize' => 0,
83            'showdate' => 0,
84            'showcreator' => 0,
85            'listsep' => '", "',
86            'onhover' => 0,
87            'ftp' => 0,
88        );
89        foreach($flags as $flag) {
90            list($name, $value) = explode('=', $flag);
91            $params[trim($name)] = trim($value);
92        }
93
94        // recursive filelistings are not supported for the filename command
95        if ($type == 'filename') {
96            $params['recursive'] = 0;
97        }
98
99        // Trim list separator
100        $params['listsep'] = trim($params['listsep'], '"');
101
102        return array($type, $pattern, $params, $title, $pos);
103    }
104
105    /**
106     * Create output
107     */
108    function render($mode, Doku_Renderer $renderer, $data) {
109        global $conf;
110
111        list($type, $pattern, $params, $title, $pos) = $data;
112
113        if ($mode == 'odt') {
114            $this->is_odt_export = true;
115        }
116
117        // disable caching
118        if ($params['cache'] === 0) {
119            $renderer->nocache();
120        }
121        if ($mode == 'xhtml' || $mode == 'odt') {
122
123            $result = $this->_create_filelist($pattern, $params);
124            if ($type == 'filename') {
125                $result = $this->_filter_out_directories($result);
126            }
127
128            // if we got nothing back, display a message
129            if ($result == DOKU_PLUGIN_FILELIST_NOMATCH) {
130                $renderer->cdata('[n/a: ' . $this->getLang('error_nomatch') . ']');
131                return true;
132            } else if ($result == DOKU_PLUGIN_FILELIST_OUTSIDEJAIL) {
133                $renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
134                return true;
135            }
136
137            // if limit is set for a filelist, cut out the relevant slice from the files
138            if (($type == 'filelist') && ($params['limit'] != 0)) {
139                $result['files'] = array_slice($result['files'], $params['offset'], $params['limit']);
140            }
141
142            switch ($type) {
143
144                case 'filename':
145
146                    $filename = $result['files'][$params['index']]['name'];
147                    $filepath = $result['files'][$params['index']]['path'];
148
149                    $this->_render_link($filename, $filepath, $result['basedir'], $result['webdir'], $params, $renderer);
150                    return true;
151
152                case 'filelist':
153                    if (count($result['files']) == 0)
154                        break;
155
156                    switch ($params['style']) {
157                        case 'list':
158                        case 'olist':
159                            if (!$this->is_odt_export) {
160                                $renderer->doc .= '<div class="filelist-plugin">'.DOKU_LF;
161                            }
162                            $this->_render_list($result, $params, $renderer);
163                            if (!$this->is_odt_export) {
164                                $renderer->doc .= '</div>'.DOKU_LF;
165                            }
166                            break;
167
168                        case 'table':
169                            if (!$this->is_odt_export) {
170                                $renderer->doc .= '<div class="filelist-plugin">'.DOKU_LF;
171                            }
172                            $this->_render_table($result, $params, $pos, $renderer);
173                            if (!$this->is_odt_export) {
174                                $renderer->doc .= '</div>'.DOKU_LF;
175                            }
176                            break;
177
178                        case 'page':
179                            $this->_render_page($result, $params, $renderer);
180                            break;
181                    }
182                    return true;
183
184            }
185        }
186        return false;
187    }
188
189    //~~ Render functions
190
191    /**
192     * Creates the downloadlink for the given filename, based on the given
193     * parameters, and adds it to the output of the renderer.
194     *
195     * @param $filename the name of the file
196     * @param $filepath the path of the file
197     * @param $basedir the basedir of the file
198     * @param $webdir the base URL of the file
199     * @param $params the parameters of the filelist command
200     * @param $renderer the renderer to use
201     * @return void
202     */
203    function _render_link($filename, $filepath, $basedir, $webdir, $params, Doku_Renderer $renderer) {
204        global $conf;
205
206        //prepare for formating
207        $link['target'] = $conf['target']['extern'];
208        $link['style']  = '';
209        $link['pre']    = '';
210        $link['suf']    = '';
211        $link['more']   = '';
212        $link['url'] = $this->_get_link_url ($filepath, $basedir, $webdir, $params['randlinks'], $params['direct'], $params['ftp']);
213
214        $link['name']   = $filename;
215        $link['title']  = $renderer->_xmlEntities($link['url']);
216        if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
217
218        if ($params['link']) {
219            switch ($params['link']) {
220                case 1:
221                    // Link without background image
222                    $link['class']  = 'media';
223                    break;
224                default:
225                    // Link with background image
226                    list($ext,$mime) = mimetype(basename($filepath));
227                    $link['class'] .= ' mediafile mf_'.$ext;
228                    break;
229            }
230
231            //output formatted
232            if ( !$this->is_odt_export ) {
233                $renderer->doc .= $renderer->_formatLink($link);
234            } else {
235                $this->render_odt_link ($link, $renderer);
236            }
237        } else {
238            // No link, just plain text.
239            $renderer->cdata($filename);
240        }
241    }
242
243    /**
244     * Renders a link for odt mode.
245     *
246     * @param $link the link parameters
247     * @param $renderer the renderer to use
248     * @return void
249     */
250    protected function render_odt_link ($link, Doku_Renderer $renderer) {
251        if ( method_exists ($renderer, 'getODTProperties') === true ) {
252            $properties = array ();
253
254            // Get CSS properties for ODT export.
255            $renderer->getODTProperties ($properties, 'a', $link['class'], NULL, 'screen');
256
257            // Insert image if present for that media class.
258            if ( empty($properties ['background-image']) === false ) {
259                $properties ['background-image'] =
260                    $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
261                $renderer->_odtAddImage ($properties ['background-image']);
262            }
263        }
264
265        // Render link.
266        $renderer->externallink($link['url'], $link['name']);
267    }
268
269    /**
270     * Renders a list.
271     *
272     * @param $result the filelist to render
273     * @param $params the parameters of the filelist call
274     * @param $renderer the renderer to use
275     * @return void
276     */
277    function _render_list($result, $params, Doku_Renderer $renderer) {
278        $this->_render_list_items($result['files'], $result['basedir'], $result['webdir'], $params, $renderer);
279    }
280
281    /**
282     * Recursively renders a tree of files as list items.
283     *
284     * @param $files the files to render
285     * @param $basedir the basedir to use
286     * @param $webdir the webdir to use
287     * @param $params the parameters of the filelist call
288     * @param $renderer the renderer to use
289     * @param $level the level to render
290     * @return void
291     */
292    function _render_list_items($files, $basedir, $webdir, $params, Doku_Renderer $renderer, $level = 1) {
293        global $conf;
294
295        if ($params['style'] == 'olist') {
296            $renderer->listo_open();
297        } else {
298            $renderer->listu_open();
299        }
300
301        foreach ($files as $file) {
302            if ($file['children'] !== false && $file['treesize'] > 0) {
303                // render the directory and its subtree
304                $renderer->listitem_open($level);
305                if ($this->is_odt_export) {
306                    $renderer->p_open();
307                }
308                $renderer->cdata($file['name']);
309                $this->_render_list_items($file['children'], $basedir, $webdir, $params, $renderer, $level+1);
310                if ($this->is_odt_export) {
311                    $renderer->p_close();
312                }
313                $renderer->listitem_close();
314            } else if ($file['children'] === false) {
315                // open list item
316                $renderer->listitem_open($level);
317                if ($this->is_odt_export) {
318                    $renderer->p_open();
319                }
320
321                // render the preview image
322                if ($params['preview']) {
323                    $this->_render_preview_image($file['path'], $basedir, $webdir, $params, $renderer);
324                }
325
326                // render the file link
327                $this->_render_link($file['name'], $file['path'], $basedir, $webdir, $params, $renderer);
328
329                // render filesize
330                if ($params['showsize']) {
331                    $renderer->cdata($params['listsep'].$this->_size_readable($file['size'], 'PiB', 'bi', '%01.1f %s'));
332                }
333
334                // render lastmodified
335                if ($params['showdate']) {
336                    $renderer->cdata($params['listsep'].strftime($conf['dformat'], $file['mtime']));
337                }
338
339                // render lastmodified
340                if ($params['showcreator']) {
341                    $renderer->cdata($params['listsep'].$file['creator']);
342                }
343
344                // close list item
345                if ($this->is_odt_export) {
346                    $renderer->p_close();
347                }
348                $renderer->listitem_close();
349
350            } else {
351                // ignore empty directories
352                continue;
353            }
354        }
355
356        if ($params['style'] == 'olist') {
357            $renderer->listo_close();
358        } else {
359            $renderer->listu_close();
360        }
361    }
362
363    /**
364     * Renders the files as a table, including details if configured that way.
365     *
366     * @param $result the filelist to render
367     * @param $params the parameters of the filelist call
368     * @param $renderer the renderer to use
369     * @return void
370     */
371    function _render_table($result, $params, $pos, Doku_Renderer $renderer) {
372        global $conf;
373
374        if (!$this->is_odt_export) {
375            $renderer->table_open(NULL, NULL, $pos);
376        } else {
377            $columns = 1;
378            if ($params['tableshowsize'] || $params['showsize']) {
379                $columns++;
380            }
381            if ($params['tableshowdate'] || $params['showdate']) {
382                $columns++;
383            }
384            if ($params['showcreator']) {
385                $columns++;
386            }
387            if ($params['preview']) {
388                $columns++;
389            }
390            $renderer->table_open($columns, NULL, $pos);
391        }
392
393        if ($params['tableheader']) {
394            if ($this->is_odt_export) {
395                $renderer->tablerow_open();
396            }
397
398            $renderer->tableheader_open();
399            $renderer->cdata($this->getLang('filename'));
400            $renderer->tableheader_close();
401
402            if ($params['tableshowsize'] || $params['showsize']) {
403                $renderer->tableheader_open();
404                $renderer->cdata($this->getLang('filesize'));
405                $renderer->tableheader_close();
406            }
407
408            if ($params['tableshowdate'] || $params['showdate']) {
409                $renderer->tableheader_open();
410                $renderer->cdata($this->getLang('lastmodified'));
411                $renderer->tableheader_close();
412            }
413
414            if ($params['showcreator']) {
415                $renderer->tableheader_open();
416                $renderer->cdata($this->getLang('createdby'));
417                $renderer->tableheader_close();
418            }
419
420            if ($params['preview']) {
421                $renderer->tableheader_open(1, 'center', 1);
422                switch ($params['preview']) {
423                    case 1:
424                        $renderer->cdata($this->getLang('preview').' / '.$this->getLang('filetype'));
425                        break;
426                    case 2:
427                        $renderer->cdata($this->getLang('preview'));
428                        break;
429                    case 3:
430                        $renderer->cdata($this->getLang('filetype'));
431                        break;
432                }
433                $renderer->tableheader_close();
434            }
435
436            if ($this->is_odt_export) {
437                $renderer->tablerow_close();
438            }
439        }
440
441        foreach ($result['files'] as $file) {
442            $renderer->tablerow_open();
443            $renderer->tablecell_open();
444            $this->_render_link($file['name'], $file['path'], $result['basedir'], $result['webdir'], $params, $renderer);
445            $renderer->tablecell_close();
446
447            if ($params['tableshowsize'] || $params['showsize']) {
448                $renderer->tablecell_open(1, 'right');
449                $renderer->cdata($this->_size_readable($file['size'], 'PiB', 'bi', '%01.1f %s'));
450                $renderer->tablecell_close();
451            }
452
453            if ($params['tableshowdate'] || $params['showdate']) {
454                $renderer->tablecell_open();
455                $renderer->cdata(strftime($conf['dformat'], $file['mtime']));
456                $renderer->tablecell_close();
457            }
458
459            if ($params['showcreator']) {
460                $renderer->tablecell_open();
461                $renderer->cdata($file['creator']);
462                $renderer->tablecell_close();
463            }
464
465            if ($params['preview']) {
466                $renderer->tablecell_open(1, 'center', 1);
467
468                $this->_render_preview_image($file['path'], $result['basedir'], $result['webdir'], $params, $renderer);
469                $renderer->tablecell_close();
470            }
471
472            $renderer->tablerow_close();
473        }
474        $renderer->table_close($pos);
475    }
476
477    /**
478     * Renders a page.
479     *
480     * @param $result the filelist to render
481     * @param $params the parameters of the filelist call
482     * @param $renderer the renderer to use
483     * @return void
484     */
485    function _render_page($result, $params, Doku_Renderer $renderer) {
486        if ( method_exists ($renderer, 'getLastlevel') === false ) {
487            $class_vars = get_class_vars (get_class($renderer));
488            if ($class_vars ['lastlevel'] !== NULL) {
489                // Old releases before "hrun": $lastlevel is accessible
490                $lastlevel = $renderer->lastlevel + 1;
491            } else {
492                // Release "hrun" or newer without method 'getLastlevel()'.
493                // Lastlevel can't be determined. Workaroud: always use level 1.
494                $lastlevel = 1;
495            }
496        } else {
497            $lastlevel = $renderer->getLastlevel() + 1;
498        }
499        $this->_render_page_section($result['files'], $result['basedir'], $result['webdir'], $params, $renderer, $lastlevel);
500    }
501
502    /**
503     * Recursively renders a tree of files as page sections using headlines.
504     *
505     * @param $files the files to render
506     * @param $basedir the basedir to use
507     * @param $webdir the webdir to use
508     * @param $params the parameters of the filelist call
509     * @param $renderer the renderer to use
510     * @param $level the level to render
511     * @return void
512     */
513    function _render_page_section($files, $basedir, $webdir, $params, Doku_Renderer $renderer, $level) {
514        $trees = array();
515        $leafs = array();
516
517        foreach ($files as $file) {
518            if ($file['children'] !== false) {
519                if ($file['treesize'] > 0) {
520                    $trees[] = $file;
521                }
522            } else {
523                $leafs[] = $file;
524            }
525        }
526
527        $this->_render_list_items($leafs, $basedir, $webdir, $params, $renderer);
528
529        if ($level < 7) {
530            foreach ($trees as $tree) {
531                $renderer->header($tree['name'], $level, 0);
532                $renderer->section_open($level);
533                $this->_render_page_section($tree['children'], $basedir, $webdir, $params, $renderer, $level + 1);
534                $renderer->section_close();
535            }
536        } else {
537            $this->_render_list_items($trees, $basedir, $webdir, $params, $renderer);
538        }
539    }
540
541    /**
542     * Render a preview item for file $filepath.
543     *
544     * @param $filepath the file for which a preview image shall be rendered
545     * @param $basedir the basedir to use
546     * @param $webdir the webdir to use
547     * @param $params the parameters of the filelist call
548     * @param $renderer the renderer to use
549     * @return void
550     */
551    protected function _render_preview_image ($filepath, $basedir, $webdir, $params, Doku_Renderer $renderer) {
552        $imagepath = $this->get_preview_image_path($filepath, $params, $isImage);
553        if (!empty($imagepath)) {
554            if ($isImage == false) {
555                // Generate link to returned filetype icon
556                $imgLink = $this->_get_link_url ($imagepath, $basedir, $webdir, 0, 1);
557            } else {
558                // Generate link to image file
559                $imgLink = $this->_get_link_url ($filepath, $basedir, $webdir, $params['randlinks'], $params['direct'], $params['ftp']);
560            }
561
562            $previewsize = $params['previewsize'];
563            if ($previewsize == 0) {
564                $previewsize = 32;
565            }
566            $imgclass = '';
567            if ($params['onhover']) {
568                $imgclass = 'class="filelist_preview"';
569            }
570
571            if (!$this->is_odt_export) {
572                $renderer->doc .= '<img '.$imgclass.' style=" max-height: '.$previewsize.'px; max-width: '.$previewsize.'px;" src="'.$imgLink.'">';
573            } else {
574                list($width, $height)  = $renderer->_odtGetImageSize ($imagepath, $previewsize, $previewsize);
575                $renderer->_odtAddImage ($imagepath, $width.'cm', $height.'cm');
576            }
577        }
578    }
579
580    //~~ Filelist functions
581
582    /**
583     * Creates the filelist based on the given glob-pattern and
584     * sorting and ordering parameters.
585     *
586     * @param $pattern the pattern
587     * @param $params the parameters of the filelist command
588     * @return a filelist data structure containing the found files and base-
589     *         and webdir
590     */
591    function _create_filelist($pattern, $params) {
592        global $conf;
593        global $ID;
594
595        $allowed_absolute_paths = explode(',', $this->getConf('allowed_absolute_paths'));
596
597        $result = array(
598            'files' => array(),
599            'basedir' => false,
600            'webdir' => false,
601        );
602
603        // we don't want to use $conf['media'] here as that path has symlinks resolved
604        if (!$params['direct']) {
605            // if media path is not absolute, precede it with the current namespace
606            if ($pattern[0] != ':') {
607                $pattern = ':'.getNS($ID) . ':' . $pattern;
608            }
609            // replace : with / and prepend mediadir
610            $pattern = $this->mediadir . str_replace(':', '/', $pattern);
611        } elseif($params['direct'] == 2){
612            // treat path as relative to first configured path
613            $pattern = $allowed_absolute_paths[0].'/'.$pattern;
614        } else {
615            // if path is not absolute, precede it with DOKU_INC
616            if (!$this->_path_is_absolute($pattern)) {
617                $pattern = DOKU_INC.$pattern;
618            }
619        }
620        // get the canonicalized basedir (without resolving symlinks)
621        $dir = $this->_realpath($this->_win_path_convert(dirname($pattern))).'/';
622
623        // if the directory does not exist, we of course have no matches
624        if (!$dir || !file_exists($dir)) {
625            return DOKU_PLUGIN_FILELIST_NOMATCH;
626        }
627
628        // match pattern aginst allowed paths
629        $web_paths = explode(',', $this->getConf('web_paths'));
630        $basedir = false;
631        $webdir = false;
632        if (count($allowed_absolute_paths) == count($web_paths)) {
633            for($i = 0; $i < count($allowed_absolute_paths); $i++) {
634                $abs_path = $this->_win_path_convert(trim($allowed_absolute_paths[$i]));
635                if (strstr($dir, $abs_path) == $dir) {
636                    $basedir = $abs_path;
637                    $webdir = trim($web_paths[$i]);
638                    break;
639                }
640            }
641        }
642
643        // $basedir is false if $dir was not in one of the allowed paths
644        if ($basedir === false) {
645            return DOKU_PLUGIN_FILELIST_OUTSIDEJAIL;
646        }
647
648        // retrieve fileinformation
649        $result['basedir'] = $basedir;
650        $result['webdir'] = $webdir;
651        $result['files'] = $this->_crawl_files($this->_win_path_convert($pattern), $params);
652        if (!$result['files']) {
653            return DOKU_PLUGIN_FILELIST_NOMATCH;
654        }
655
656        // flatten filelist if the displaymode is table
657        if ($params['style'] == 'table') {
658            $result['files'] = $this->_flatten_filelist($result['files']);
659        }
660
661        // sort the list
662        $callback = false;
663        $reverseflag = false;
664        if ($params['sort'] == 'mtime') {
665            $callback = array($this, '_compare_mtimes');
666            if ($params['order'] == 'asc') $reverseflag = true;
667        } else if ($params['sort'] == 'ctime') {
668            $callback = array($this, '_compare_ctimes');
669            if ($params['order'] == 'asc') $reverseflag = true;
670        } else if ($params['sort'] == 'size') {
671            $callback = array($this, '_compare_sizes');
672            if ($params['order'] == 'desc') $reverseflag = true;
673        } else if ($params['sort'] == 'iname') {
674            $callback = array($this, '_compare_inames');
675            if ($params['order'] == 'desc') $reverseflag = true;
676        } else {
677            $callback = array($this, '_compare_names');
678            if ($params['order'] == 'desc') $reverseflag = true;
679        }
680        $this->_sort_filelist($result['files'], $callback, $reverseflag);
681
682        // return the list
683        if (count($result['files']) > 0)
684            return $result;
685        else
686            return DOKU_PLUGIN_FILELIST_NOMATCH;
687    }
688
689    /**
690     * Recursively sorts the given tree using the given callback. Optionally
691     * reverses the sorted tree before returning it.
692     *
693     * @param $files the files to sort
694     * @param $callback the callback function to use for comparison
695     * @param $reverse true if the result is to be reversed
696     * @return the sorted tree
697     */
698    function _sort_filelist(&$files, $callback, $reverse) {
699        // sort subtrees
700        for ($i = 0; $i < count($files); $i++) {
701            if ($files[$i]['children'] !== false) {
702                $children = $files[$i]['children'];
703                $this->_sort_filelist($children, $callback, $reverse);
704                $files[$i]['children'] = $children;
705            }
706        }
707
708        // sort current tree
709        usort($files, $callback);
710        if ($reverse) {
711            $files = array_reverse($files);
712        }
713    }
714
715    /**
716     * Flattens the filelist by recursively walking through all subtrees and
717     * merging them with a prefix attached to the filenames.
718     *
719     * @param $files the tree to flatten
720     * @param $prefix the prefix to attach to all processed nodes
721     * @return a flattened representation of the tree
722     */
723    function _flatten_filelist($files, $prefix = '') {
724        $result = array();
725        foreach ($files as $file) {
726            if ($file['children'] !== false) {
727                $result = array_merge($result, $this->_flatten_filelist($file['children'], $prefix . $file['name'] . '/'));
728            } else {
729                $file['name'] = $prefix . $file['name'];
730                $result[] = $file;
731            }
732        }
733        return $result;
734    }
735
736    /**
737     * Filters out directories and their subtrees from the result.
738     *
739     * @param $result the result to filter
740     * @return the result without any directories contained therein,
741     *         DOKU_PLUGIN_FILELIST_NOMATCH if there are no files left or
742     *         the given result if it was not an array (but an errorcode)
743     */
744    function _filter_out_directories($result) {
745        if (!is_array($result)) {
746            return $result;
747        }
748
749        $filtered = array();
750        $files = $result['files'];
751        foreach ($files as $file) {
752            if ($file['children'] === false) {
753                $filtered[] = $file;
754            }
755        }
756
757        if (count($filtered) == 0) {
758            return DOKU_PLUGIN_FILELIST_NOMATCH;
759        } else {
760            $result['files'] = $filtered;
761            return $result;
762        }
763    }
764
765    /**
766     * Does a (recursive) crawl for finding files based on a given pattern.
767     * Based on a safe glob reimplementation using fnmatch and opendir.
768     *
769     * @param $pattern the pattern to match to
770     * @param params the parameters of the filelist call
771     * @return a hierarchical filelist or false if nothing could be found
772     *
773     * @see <http://www.php.net/manual/en/function.glob.php#71083>
774     */
775    function _crawl_files($pattern, $params) {
776        $split = explode('/', $pattern);
777        $match = array_pop($split);
778        $path = implode('/', $split);
779        if (!is_dir($path)) {
780            return false;
781        }
782
783        $ext = explode(',',$this->getConf('extensions'));
784        $ext = array_map('trim',$ext);
785        $ext = array_map('preg_quote_cb',$ext);
786        $ext = join('|',$ext);
787
788        if (($dir = opendir($path)) !== false) {
789            $result = array();
790            while (($file = readdir($dir)) !== false) {
791                if ($file == '.' || $file == '..') {
792                    // ignore . and ..
793                    continue;
794                }
795                if ($file == $params['titlefile']) {
796                    // ignore the title file
797                    continue;
798                }
799                if ($file[0] == '.') {
800                    // ignore hidden files
801                    continue;
802                }
803                $filepath = $path . '/' . $file;
804
805                if ($this->_fnmatch($match, $file) || (is_dir($filepath) && $params['recursive'])) {
806                    if(!is_dir($filepath) && !preg_match('/('.$ext.')$/i',$file)){
807                        continue;
808                    }
809
810                    if (!$params['direct']) {
811                        // exclude prohibited media files via ACLs
812                        $mid = $this->_convert_mediapath($filepath);
813                        $perm = auth_quickaclcheck($mid);
814                        if ($perm < AUTH_READ) continue;
815                    } else {
816                        if (!is_readable($filepath)) continue;
817                    }
818
819                    $filename = $file;
820                    if (is_dir($filepath)) {
821                        $titlefile = $filepath . '/' . $params['titlefile'];
822                        if (!$params['direct']) {
823                            $mid = $this->_convert_mediapath($titlefile);
824                            $perm = auth_quickaclcheck($mid);
825                            if (is_readable($titlefile) && $perm >= AUTH_READ) {
826                                $filename = io_readFile($titlefile, false);
827                            }
828                        } else {
829                            if (is_readable($titlefile)) {
830                                $filename = io_readFile($titlefile, false);
831                            }
832                        }
833                    }
834
835                    // prepare entry
836                    $creator = '';
837                    if (!is_dir($filepath) || $params['recursive']) {
838                        if (!$params['direct']) {
839                            $medialog = new MediaChangeLog($mid);
840                            $revinfo = $medialog->getRevisionInfo(@filemtime(fullpath(mediaFN($mid))));
841
842                            if($revinfo['user']) {
843                                $creator = $revinfo['user'];
844                            } else {
845                                $creator = $revinfo['ip'];
846                            }
847                        }
848                        if (empty($creator)) {
849                            $creator = $this->getLang('creatorunknown');
850                        }
851
852                        $entry = array(
853                            'name' => $filename,
854                            'path' => $filepath,
855                            'mtime' => filemtime($filepath),
856                            'ctime' => filectime($filepath),
857                            'size' => filesize($filepath),
858                            'children' => ((is_dir($filepath) && $params['recursive']) ? $this->_crawl_files($filepath . '/' . $match, $params) : false),
859                            'treesize' => 0,
860                            'creator' => $creator,
861                        );
862
863                        // calculate tree size
864                        if ($entry['children'] !== false) {
865                            foreach ($entry['children'] as $child) {
866                                $entry['treesize'] += $child['treesize'];
867                            }
868                        } else {
869                            $entry['treesize'] = 1;
870                        }
871
872                        // add entry to result
873                        $result[] = $entry;
874                    }
875                }
876            }
877            closedir($dir);
878            return $result;
879        } else {
880            return false;
881        }
882    }
883
884    //~~ Comparators
885
886    function _compare_names($a, $b) {
887        return strcmp($a['name'], $b['name']);
888    }
889
890    function _compare_inames($a, $b) {
891        return strcmp(strtolower($a['name']), strtolower($b['name']));
892    }
893
894    function _compare_ctimes($a, $b) {
895        if ($a['ctime'] == $b['ctime']) {
896            return 0;
897        }
898        return (($a['ctime'] < $b['ctime']) ? -1 : 1);
899    }
900
901    function _compare_mtimes($a, $b) {
902        if ($a['mtime'] == $b['mtime']) {
903            return 0;
904        }
905        return (($a['mtime'] < $b['mtime']) ? -1 : 1);
906    }
907
908    function _compare_sizes($a, $b) {
909        if ($a['size'] == $b['size']) {
910            return 0;
911        }
912        return (($a['size'] < $b['size']) ? -1 : 1);
913    }
914
915    //~~ Utility functions
916
917    /**
918     * Canonicalizes a given path. A bit like realpath, but without the resolving of symlinks.
919     *
920     * @author anonymous
921     * @see <http://www.php.net/manual/en/function.realpath.php#73563>
922     */
923    function _realpath($path) {
924        $path=explode('/', $path);
925        $output=array();
926        for ($i=0; $i<sizeof($path); $i++) {
927            if ('.' == $path[$i]) continue;
928            if ('..' == $path[$i] && '..' != $output[sizeof($output) - 1]) {
929                array_pop($output);
930                continue;
931            }
932            array_push($output, $path[$i]);
933        }
934        return implode('/', $output);
935    }
936
937    /**
938     * Replacement for fnmatch() for windows systems.
939     *
940     * @author jk at ricochetsolutions dot com
941     * @see <http://www.php.net/manual/en/function.fnmatch.php#71725>
942     */
943    function _fnmatch($pattern, $string) {
944        return preg_match("#^".strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']'))."$#i", $string);
945    }
946
947    /**
948     * Converts backslashs in paths to slashs.
949     *
950     * @param $path the path to convert
951     * @return the converted path
952     */
953    function _win_path_convert($path) {
954        return str_replace('\\', '/', trim($path));
955    }
956
957    /**
958     * Return human readable sizes
959     *
960     * @author      Aidan Lister <aidan@php.net>
961     * @version     1.3.0
962     * @link        http://aidanlister.com/repos/v/function.size_readable.php
963     * @param       int     $size        size in bytes
964     * @param       string  $max         maximum unit
965     * @param       string  $system      'si' for SI, 'bi' for binary prefixes
966     * @param       string  $retstring   return string format
967     */
968    function _size_readable($size, $max = null, $system = 'si', $retstring = '%01.2f %s')
969    {
970        // Pick units
971        $systems['si']['prefix'] = array('B', 'K', 'MB', 'GB', 'TB', 'PB');
972        $systems['si']['size']   = 1000;
973        $systems['bi']['prefix'] = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
974        $systems['bi']['size']   = 1024;
975        $sys = isset($systems[$system]) ? $systems[$system] : $systems['si'];
976
977        // Max unit to display
978        $depth = count($sys['prefix']) - 1;
979        if ($max && false !== $d = array_search($max, $sys['prefix'])) {
980            $depth = $d;
981        }
982
983        // Loop
984        $i = 0;
985        while ($size >= $sys['size'] && $i < $depth) {
986            $size /= $sys['size'];
987            $i++;
988        }
989
990        return sprintf($retstring, $size, $sys['prefix'][$i]);
991    }
992
993    /**
994     * Determines whether a given path is absolute or relative.
995     * On windows plattforms, it does so by checking whether the second character
996     * of the path is a :, on all other plattforms it checks for a / as the
997     * first character.
998     *
999     * @param $path the path to check
1000     * @return true if path is absolute, false otherwise
1001     */
1002    function _path_is_absolute($path) {
1003        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1004            if ($path[1] == ':' || ($path[0] == '/' && $path[1] == '/')) {
1005                return true;
1006            }
1007            return false;
1008        } else {
1009            return ($path[0] == '/');
1010        }
1011    }
1012
1013    function _convert_mediapath($path) {
1014        $mid = str_replace('/', ':', substr($path, strlen($this->mediadir))); // strip media base dir
1015        return ltrim($mid, ':'); // strip leading :
1016    }
1017
1018    /**
1019     * The function determines the preview image path for the given file
1020     * depending on the file type and the 'preview' config option value:
1021     * 1: Display file as preview image if itself is an image otherwise
1022     *    choose DokuWiki image corresponding to the file extension
1023     * 2: Display file as preview image if itself is an image otherwise
1024     *    display no image
1025     * 3. Display DokuWiki image corresponding to the file extension
1026     *
1027     * @param $filename the file to check
1028     * @return string Image to use for preview image
1029     */
1030    protected function get_preview_image_path ($filename, $params, &$isImage) {
1031        list($ext,$mime) = mimetype(basename($filename));
1032        $imagepath = '';
1033        $isImage = false;
1034        if (($params['preview'] == 1 || $params['preview'] == 2) &&
1035            strncmp($mime, 'image', strlen('image')) == 0) {
1036            // The file is an image. Return itself as the image path.
1037            $imagepath = $filename;
1038            $isImage = true;
1039        }
1040        if (($params['preview'] == 1 && empty($imagepath)) ||
1041            $params['preview'] == 3 ) {
1042            // No image. Return DokuWiki image for file extension.
1043            if (!empty($ext)) {
1044                $imagepath = DOKU_INC.'lib/images/fileicons/32x32/'.$ext.'.png';
1045            } else {
1046                $imagepath = DOKU_INC.'lib/images/fileicons/32x32/file.png';
1047            }
1048        }
1049        return $imagepath;
1050    }
1051
1052    /**
1053     * Create URL for file $filepath.
1054     *
1055     * @param $filepath the file for which a preview image shall be rendered
1056     * @param $basedir the basedir to use
1057     * @param $webdir the webdir to use
1058     * @param $params the parameters of the filelist call
1059     * @return string the generated URL
1060     */
1061    protected function _get_link_url ($filepath, $basedir, $webdir, $randlinks, $direct, $ftp=false) {
1062        $urlparams = '';
1063        if ($randlinks) {
1064            $urlparams = '?'.time();
1065        }
1066        if (!$direct) {
1067            $url = ml(':'.$this->_convert_mediapath($filepath)).$urlparams;
1068        } else {
1069            $url = $webdir.substr($filepath, strlen($basedir)).$urlparams;
1070            if ($ftp)
1071            {
1072                $url = str_replace('\\','/', $url);
1073                if (strpos($url, 'http') === false) {
1074                    $url = 'ftp:'.$url;
1075                } else {
1076                    $url = str_replace('http','ftp', $url);
1077                }
1078            }
1079        }
1080        return $url;
1081    }
1082}
1083