1<?php
2/**
3 * DokuWiki Plugin filelisting (Helper Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Szymon Olewniczak <dokuwiki@cosmocode.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12class helper_plugin_filelisting extends DokuWiki_Plugin {
13
14    /**
15     * Display the file listing for the current page
16     *
17     * @param bool $print Should the HTML be printed or returned?
18     *
19     * @return string
20     */
21    public function tpl_filelisting($print = true, $ns = NULL) {
22        global $INFO;
23        global $lang;
24
25        if (is_null($ns)) $ns = getNS($INFO['id']);
26
27        if ($ns == false) {
28            $ns = ':';
29            $ns_string = '[' . $lang['mediaroot'] . ']';
30        } else {
31            $ns_string = $ns;
32        }
33
34        $colgroup = '<colgroup>';
35        $colgroup .= '<col style="width: 22px;">';
36        $colgroup .= '<col style="width: 25px;">';
37        $colgroup .= '<col style="width: 50%;">';
38        $colgroup .= '<col style="width: 15%;">';
39        $colgroup .= '<col style="width: 35%;">';
40        $colgroup .= '</colgroup>';
41
42        $ret = '<div class="plugin__filelisting" data-namespace="' . $ns . '">';
43
44        $ret .= '<div class="plugin__filelisting_capiton">';
45        $ret .= sprintf($this->getLang('files_in_namespace'), $ns_string);
46        $ret .= '</div>';
47
48        //collapsible is for filter box (added dynamicly by JS)
49        $ret .= '<div class="plugin__filelisting_collapsible">';
50
51        //form for file deletion
52        $form = new dokuwiki\Form\Form(['action' => wl($INFO['id'], '', false, '&')]);
53        $form->addHTML('<div class="plugin__filelisting_content">');
54
55        $form->addHTML('<div class="plugin__filelisting_headertable">');
56        $form->addHTML('<table>');
57        $form->addHTML($colgroup);
58        $form->addHTML('<thead>');
59        $form->addHTML('<tr>');
60        //colspan for delete checkbox and icon
61        $form->addHTML('<th colspan="2"></th>');
62        $form->addHTML('<th>' . $this->getLang('header filename') .'</th>');
63        $form->addHTML('<th>' . $this->getLang('header filesize') .'</th>');
64        $form->addHTML('<th>' . $this->getLang('header filedate') .'</th>');
65        $form->addHTML('</tr>');
66        $form->addHTML('</thead>');
67        $form->addHTML('</table>');
68        $form->addHTML('</div>');
69
70        $form->addHTML('<div class="plugin__filelisting_bodytable">');
71        $form->addHTML('<table>');
72        $form->addHTML($colgroup);
73        $form->addHTML('<tbody>');
74
75        $rowElements = $this->getFilesRows($ns);
76        foreach($rowElements as $element) {
77            $form->addElement($element);
78        }
79        $form->addHTML('</tbody>');
80        $form->addHTML('</table>');
81        $form->addHTML('</div>');
82
83        //div.plugin__filelisting_content
84        $form->addHTML('</div>');
85
86        $form->addHTML('<div class="plugin__filelisting_footer">');
87        //user can delete on this namespace
88        $form->addButton('do[plugin_filelisting_delete]', $this->getLang('delete_selected'));
89        $form->addHTML('</div>');
90
91        $ret .= $form->toHTML();
92
93        //div.plugin__filelisting_collapsible
94        $ret .= '</div>';
95        //div.plugin__filelisting
96        $ret .= '</div>';
97
98        if ($print) {
99            echo $ret;
100        }
101
102        return $ret;
103    }
104
105    /**
106     * Return namespace files as html table rows
107     * @param string    $ns
108     * @param int       $lvl
109     * @param bool      $filesOnly if true, then the directories in a namespace are ignored
110     * @return array    of \dokuwiki\Form\Element
111     */
112    public function getFilesRows($ns, $lvl=0, $filesOnly = false) {
113        $files = $this->getFiles($ns);
114        $elements = array();
115        foreach ($files as $file) {
116            //skip dirs
117            if ($filesOnly && $file['isdir']) continue;
118
119            //empty $ns means root
120            $trOpen = new \dokuwiki\Form\TagOpenElement('tr', array('data-childOf' => $ns));
121            if ($file['isdir']) {
122                $trOpen->attr('data-namespace', $file['id']);
123            }
124            $elements[] = $trOpen;
125
126            //delete checkbox
127            $elements[] = new \dokuwiki\Form\TagOpenElement('td');
128            if (!$file['isdir'] && $file['perm'] >= AUTH_DELETE) {
129                $name = 'delete[' . $file['id'] . ']';
130                $elements[] = new \dokuwiki\Form\CheckableElement('checkbox', $name, '');
131            }
132            $elements[] = new \dokuwiki\Form\TagCloseElement('td');
133
134            $html = '<td class="plugin__filelisting_cell_icon">' . $file['icon'] . '</td>';
135            $elements[] = new \dokuwiki\Form\HTMLElement($html);
136
137            $td_name = new \dokuwiki\Form\TagOpenElement('td', array('data-sort' => $file['file']));
138            $td_name->addClass('plugin__filelisting_cell_name');
139            $elements[] = $td_name;
140
141            if ($lvl > 0) {
142                $html = '<span style="margin-left: ' . $lvl * 10 . 'px;">↳ </span>';
143                $elements[] = new \dokuwiki\Form\HTMLElement($html);
144            }
145            $elements[] = new \dokuwiki\Form\HTMLElement($file['link']);
146            $elements[] = new \dokuwiki\Form\TagCloseElement('td');
147
148            if ($file['isdir']) {
149                $elements[] = new \dokuwiki\Form\HTMLElement('<td data-sort=""> — </td>');
150                $elements[] = new \dokuwiki\Form\HTMLElement('<td data-sort=""> — </td>');
151            } else {
152                $html = '<td data-sort="' . $file['size'] . '">' . filesize_h($file['size']) . '</td>';
153                $elements[] = new \dokuwiki\Form\HTMLElement($html);
154                $html = '<td data-sort="' . $file['mtime'] . '">' . dformat($file['mtime']) . '</td>';
155                $elements[] = new \dokuwiki\Form\HTMLElement($html);
156            }
157            $elements[] = new \dokuwiki\Form\TagCloseElement('tr');
158        }
159        return $elements;
160    }
161
162    /**
163     * Get a list of namespace files
164     *
165     * Suppress error messages for invalid file names: nothing to be done here,
166     * they should be fixed in the media manager.
167     *
168     * @param $ns
169     * @return array
170     */
171    public function getFiles($ns) {
172        global $conf;
173
174        $ns = cleanId($ns);
175
176        $dir = utf8_encodeFN(str_replace(':','/',$ns));
177        $data = array();
178        search($data, $conf['mediadir'], array($this, 'search_media_and_namespaces'),
179               array('showmsg' => false, 'depth' => 1), $dir, 1, false);
180
181        return array_map(array($this, 'fileInfo'), $data);
182    }
183
184    /**
185     * Add additional info to search() function result item
186     *
187     * @param $item
188     * @return mixed
189     */
190    protected function fileInfo($item) {
191
192        // Prepare filename
193        $item['file'] = utf8_decodeFN($item['file']);
194
195        //handle directory diffirently
196        if (isset($item['isdir'])) {
197            $item['icon'] = $this->dirClosedIcon();
198
199            $item['link'] = '<a href="'.wl($item['id']. ':start').'">' . $item['file'] . '</a>';
200        } else {
201            // consistent info needed for rendering
202            $item['isdir'] = false;
203            // Prepare fileicons
204            list($ext) = mimetype($item['file'],false);
205            $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
206            $class = 'mf_' . $class;
207            $item['icon'] = '<div style="width: 16px; height:16px;" class="' . $class . '"></div>';
208
209            $item['link'] = '<a href="'.ml($item['id']).'" target="_blank">' . $item['file'] . '</a>';
210        }
211
212        return $item;
213    }
214
215    /**
216     * Html for closed dir icon
217     *
218     * @return false|string
219     */
220    public function dirClosedIcon() {
221        return inlineSVG(dirname(__FILE__) . '/images/folder.svg');
222    }
223
224    /**
225     * Html for opened dir icon
226     *
227     * @return false|string
228     */
229    public function dirOpenedIcon() {
230        return inlineSVG(dirname(__FILE__) . '/images/folder-open.svg');
231    }
232
233    /**
234     * Html for loading icon
235     *
236     * @return string
237     */
238    public function loadingIcon() {
239        $file = dirname(__FILE__) . '/images/loading.gif';
240        $contents = file_get_contents($file);
241        $base64 = base64_encode($contents);
242        return '<img src="data:image/gif;base64,'.$base64.'">';
243    }
244
245    /**
246     * List all mediafiles in a namespace
247     *   $opts['depth']     recursion level, 0 for all
248     *   $opts['showmsg']   shows message if invalid media id is used
249     *   $opts['skipacl']   skip acl checking
250     *   $opts['pattern']   check given pattern
251     *   $opts['hash']      add hashes to result list
252     *
253     *
254     * @param array $data
255     * @param string $base
256     * @param string $file
257     * @param string $type
258     * @param integer $lvl
259     * @param array $opts
260     *
261     * @return bool
262     */
263    public function search_media_and_namespaces(&$data,$base,$file,$type,$lvl,$opts){
264
265        if ($type == 'd') {
266            $info = array();
267
268            $info['id']    = pathID($file,true);
269            $info['file']  = utf8_basename($file);
270            $info['isdir'] = true;
271
272            //check ACL for namespace
273            $info['perm'] = auth_quickaclcheck($info['id'].':*');
274
275            if(!empty($opts['skipacl']) || $info['perm'] >= AUTH_READ){
276                $data[] = $info;
277            }
278        }
279
280        return search_media($data, $base, $file, $type, $lvl, $opts);
281    }
282
283    public function delete_files($files_to_delete) {
284        global $lang;
285        global $INFO;
286
287        $msgs = array();
288        foreach ($files_to_delete as $DEL) {
289            $res = media_delete($DEL, $INFO['perm']);
290            if ($res & DOKU_MEDIA_DELETED) {
291                $msg = sprintf($lang['deletesucc'], noNS($DEL));
292                $msgs[] = array('message' => $msg, 'lvl' => 1);
293            } elseif ($res & DOKU_MEDIA_INUSE) {
294                    $msg = sprintf($lang['mediainuse'],noNS($DEL));
295                    $msgs[] = array('message' => $msg, 'lvl' => 0);
296            } else {
297                $msg = sprintf($lang['deletefail'],noNS($DEL));
298                $msgs[] = array('message' => $msg, 'lvl' => -1);
299            }
300        }
301
302        return $msgs;
303    }
304
305}
306
307// vim:ts=4:sw=4:et:
308