1<?php
2/**
3 * DokuWiki Plugin PreserveFilenames / action_angua.php
4 *
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author  Kazutaka Miyasaka <kazmiya@gmail.com>
7 */
8
9// must be run within DokuWiki
10if (!defined('DOKU_INC')) {
11    die();
12}
13
14if (!defined('DOKU_PLUGIN')) {
15    define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
16}
17
18require_once(DOKU_PLUGIN . 'preservefilenames/common.php');
19require_once(DOKU_PLUGIN . 'preservefilenames/action_anteater.php');
20
21class action_plugin_preservefilenames_angua extends action_plugin_preservefilenames_anteater
22{
23    /**
24     * Registers event handlers
25     */
26    function register(&$controller)
27    {
28        $this->common = new PreserveFilenames_Common();
29
30        $controller->register_hook('MEDIA_UPLOAD_FINISH',          'AFTER',  $this, '_saveMeta');
31        $controller->register_hook('MEDIA_DELETE_FILE',            'AFTER',  $this, '_deleteMeta');
32        $controller->register_hook('MEDIA_SENDFILE',               'BEFORE', $this, '_sendFile');
33        $controller->register_hook('PARSER_HANDLER_DONE',          'BEFORE', $this, '_replaceLinkTitle');
34        $controller->register_hook('RENDERER_CONTENT_POSTPROCESS', 'AFTER',  $this, '_replaceLinkURL');
35        $controller->register_hook('MEDIAMANAGER_CONTENT_OUTPUT',  'BEFORE', $this, '_handleMediaContent');
36        $controller->register_hook('TPL_ACT_RENDER',               'BEFORE', $this, '_handleMediaFullscreen');
37        $controller->register_hook('AJAX_CALL_UNKNOWN',            'BEFORE', $this, '_handleAjaxMediaList');
38        $controller->register_hook('ACTION_ACT_PREPROCESS',        'BEFORE', $this, '_replaceSnippetDownload');
39    }
40
41    /**
42     * Saves the name of the uploaded media file to a meta file
43     */
44    function _saveMeta(&$event)
45    {
46        global $conf;
47
48        $id = $event->data[2];
49        $filename_tidy = noNS($id);
50
51        // retrieve original filename
52        if (isset($_GET['qqfile'])) {
53            // via ajax uploader
54            $filename_orig = (string) $_GET['qqfile'];
55        } elseif (isset($_POST['mediaid'])) {
56            if (isset($_FILES['qqfile']['name'])) {
57                // via ajax uploader
58                $filename_orig = (string) $_FILES['qqfile']['name'];
59            } elseif (isset($_FILES['upload']['name'])) {
60                // via old-fashioned upload form
61                $filename_orig = (string) $_FILES['upload']['name'];
62            } else {
63                return;
64            }
65
66            // check if filename is specified
67            $specified_name = (string) $_POST['mediaid'];
68
69            if ($specified_name !== '') {
70                $filename_orig = $specified_name;
71            }
72        } else {
73            return;
74        }
75
76        $filename_safe = $this->common->_sanitizeFileName($filename_orig);
77
78        // no need to save original filename
79        if ($filename_tidy === $filename_safe) {
80            return;
81        }
82
83        // fallback if suspicious characters found
84        if ($filename_orig !== $filename_safe) {
85            return;
86        }
87
88        // save original filename to meta file
89        io_saveFile(
90            mediaMetaFN($id, '.filename'),
91            serialize(array(
92                'filename' => $filename_safe,
93            ))
94        );
95    }
96
97    /**
98     * Deletes a meta file associated with the deleted media file
99     */
100    function _deleteMeta(&$event)
101    {
102        $id = $event->data['id'];
103        $metaFilePath = mediaMetaFN($id, '.filename');
104
105        if (@unlink($metaFilePath)) {
106            io_sweepNS($id, 'mediametadir');
107        } else {
108            parent::_deleteMeta($event);
109        }
110    }
111
112    /**
113     * Returns original filename if exists
114     */
115    function _getOriginalFileName($id)
116    {
117        $metaFilePath = mediaMetaFN($id, '.filename');
118        $meta = unserialize(io_readFile($metaFilePath, false));
119
120        if (empty($meta['filename'])) {
121            // check old meta file (for backward compatibility)
122            $filename = parent::_getOriginalFileName($id);
123
124            // move old meta file to media_meta directory
125            if ($filename !== false) {
126                $oldMetaFilePath = metaFN($id, '.filename');
127                io_rename($oldMetaFilePath, $metaFilePath);
128            }
129
130            return $filename;
131        } else {
132            return $this->common->_sanitizeFileName($meta['filename']);
133        }
134    }
135
136    /**
137     * Handles media manager content output
138     *
139     * @see tpl_mediaContent
140     */
141    function _handleMediaContent(&$event)
142    {
143        global $NS;
144        global $AUTH;
145        global $JUMPTO;
146
147        if ($event->data['do'] !== 'filelist') {
148            return;
149        }
150
151        $event->preventDefault();
152        $this->_mod_media_filelist($NS, $AUTH, $JUMPTO);
153    }
154
155    /**
156     * Handles an action that calls full-screen media manager
157     *
158     * @see tpl_content_core()
159     */
160    function _handleMediaFullscreen(&$event)
161    {
162        if ($event->data !== 'media') {
163            return;
164        }
165
166        $event->preventDefault();
167        $this->_mod_tpl_media();
168    }
169
170    /**
171     * Handles a 'medialist' ajax call
172     *
173     * @see ajax_medialist()
174     */
175    function _handleAjaxMediaList(&$event)
176    {
177        global $NS;
178
179        if ($event->data !== 'preservefilenames_medialist') {
180            return;
181        }
182
183        $event->preventDefault();
184        $NS = cleanID($_POST['ns']);
185
186        if ($_POST['do'] === 'media') {
187            $this->_mod_tpl_mediaFileList();
188        } else {
189            tpl_mediaContent('fromAjax');
190        }
191    }
192
193    // -------------------------------------------------------
194    // The following methods whose name starts with '_mod' are
195    // slightly modified versions of existing functions.
196    // -------------------------------------------------------
197
198    /**
199     * Prints full-screen media manager
200     *
201     * @see tpl_media()
202     */
203    function _mod_tpl_media()
204    {
205        global $DEL, $NS, $IMG, $AUTH, $JUMPTO, $REV, $lang, $fullscreen, $conf;
206        $fullscreen = true;
207        require_once DOKU_INC.'lib/exe/mediamanager.php';
208
209        if ($_REQUEST['image']) $image = cleanID($_REQUEST['image']);
210        if (isset($IMG)) $image = $IMG;
211        if (isset($JUMPTO)) $image = $JUMPTO;
212        if (isset($REV) && !$JUMPTO) $rev = $REV;
213
214        echo '<div id="mediamanager__page">'.NL;
215        echo '<h1>'.$lang['btn_media'].'</h1>'.NL;
216        html_msgarea();
217
218        echo '<div class="panel namespaces">'.NL;
219        echo '<h2>'.$lang['namespaces'].'</h2>'.NL;
220        echo '<div class="panelHeader">';
221        echo $lang['media_namespaces'];
222        echo '</div>'.NL;
223
224        echo '<div class="panelContent" id="media__tree">'.NL;
225        media_nstree($NS);
226        echo '</div>'.NL;
227        echo '</div>'.NL;
228
229        echo '<div class="panel filelist">'.NL;
230        $this->_mod_tpl_mediaFileList();
231        echo '</div>'.NL;
232
233        echo '<div class="panel file">'.NL;
234        echo '<h2 class="a11y">'.$lang['media_file'].'</h2>'.NL;
235        tpl_mediaFileDetails($image, $rev);
236        echo '</div>'.NL;
237
238        echo '</div>'.NL;
239    }
240
241    /**
242     * Prints the central column in full-screen media manager
243     *
244     * @see tpl_mediaFileList()
245     */
246    function _mod_tpl_mediaFileList()
247    {
248        global $AUTH;
249        global $NS;
250        global $JUMPTO;
251        global $lang;
252
253        $opened_tab = $_REQUEST['tab_files'];
254        if (!$opened_tab || !in_array($opened_tab, array('files', 'upload', 'search'))) $opened_tab = 'files';
255        if ($_REQUEST['mediado'] == 'update') $opened_tab = 'upload';
256
257        echo '<h2 class="a11y">' . $lang['mediaselect'] . '</h2>'.NL;
258
259        media_tabs_files($opened_tab);
260
261        echo '<div class="panelHeader">'.NL;
262        echo '<h3>';
263        $tabTitle = ($NS) ? $NS : '['.$lang['mediaroot'].']';
264        printf($lang['media_' . $opened_tab], '<strong>'.hsc($tabTitle).'</strong>');
265        echo '</h3>'.NL;
266        if ($opened_tab === 'search' || $opened_tab === 'files') {
267            media_tab_files_options();
268        }
269        echo '</div>'.NL;
270
271        echo '<div class="panelContent">'.NL;
272        if ($opened_tab == 'files') {
273            $this->_mod_media_tab_files($NS,$AUTH,$JUMPTO);
274        } elseif ($opened_tab == 'upload') {
275            media_tab_upload($NS,$AUTH,$JUMPTO);
276        } elseif ($opened_tab == 'search') {
277            media_tab_search($NS,$AUTH);
278        }
279        echo '</div>'.NL;
280    }
281
282    /**
283     * Prints tab that displays a list of all files
284     *
285     * @see media_tab_files()
286     */
287    function _mod_media_tab_files($ns,$auth=null,$jump='') {
288        global $lang;
289        if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
290
291        if($auth < AUTH_READ){
292            echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
293        }else{
294            $this->_mod_media_filelist($ns,$auth,$jump,true,_media_get_sort_type());
295        }
296    }
297
298    /**
299     * List all files in a given Media namespace
300     *
301     * @see media_filelist()
302     */
303    function _mod_media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){
304        global $conf;
305        global $lang;
306        $ns = cleanID($ns);
307
308        // check auth our self if not given (needed for ajax calls)
309        if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
310
311        if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
312
313        if($auth < AUTH_READ){
314            // FIXME: print permission warning here instead?
315            echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
316        }else{
317            if (!$fullscreenview) media_uploadform($ns, $auth);
318
319            $dir = utf8_encodeFN(str_replace(':','/',$ns));
320            $data = array();
321            search($data,$conf['mediadir'],'search_media',
322                    array('showmsg'=>true,'depth'=>1),$dir,1,$sort);
323
324            if(!count($data)){
325                echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
326            }else {
327                if ($fullscreenview) {
328                    echo '<ul class="' . _media_get_list_type() . '">';
329                }
330                foreach($data as $item){
331                    if (!$fullscreenview) {
332                        $this->_mod_media_printfile($item,$auth,$jump);
333                    } else {
334                        $this->_mod_media_printfile_thumbs($item,$auth,$jump);
335                    }
336                }
337                if ($fullscreenview) echo '</ul>'.NL;
338            }
339        }
340        if (!$fullscreenview) media_searchform($ns);
341    }
342
343    /**
344     * Formats and prints one file in the list
345     *
346     * @see media_printfile()
347     */
348    function _mod_media_printfile($item,$auth,$jump,$display_namespace=false){
349        global $lang;
350        global $conf;
351
352        // Prepare zebra coloring
353        // I always wanted to use this variable name :-D
354        static $twibble = 1;
355        $twibble *= -1;
356        $zebra = ($twibble == -1) ? 'odd' : 'even';
357
358        // Automatically jump to recent action
359        if($jump == $item['id']) {
360            $jump = ' id="scroll__here" ';
361        }else{
362            $jump = '';
363        }
364
365        // Prepare fileicons
366        list($ext,$mime,$dl) = mimetype($item['file'],false);
367        $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
368        $class = 'select mediafile mf_'.$class;
369
370        // Prepare filename
371        $file = $this->_getOriginalFileName($item['id']);
372
373        if ($file === false) {
374            $file = utf8_decodeFN($item['file']);
375        }
376
377        // build fake media id
378        $ns = getNS($item['id']);
379        $fakeId = $ns === false ? $file : "$ns:$file";
380        $fakeId_escaped = hsc($fakeId);
381
382        // Prepare info
383        $info = '';
384        if($item['isimg']){
385            $info .= (int) $item['meta']->getField('File.Width');
386            $info .= '&#215;';
387            $info .= (int) $item['meta']->getField('File.Height');
388            $info .= ' ';
389        }
390        $info .= '<i>'.dformat($item['mtime']).'</i>';
391        $info .= ' ';
392        $info .= filesize_h($item['size']);
393
394        // output
395        echo '<div class="'.$zebra.'"'.$jump.' title="'.$fakeId_escaped.'">'.NL;
396        if (!$display_namespace) {
397            echo '<a name="h_:'.$item['id'].'" class="'.$class.'">'.hsc($file).'</a> ';
398        } else {
399            echo '<a name="h_:'.$item['id'].'" class="'.$class.'">'.$fakeId_escaped.'</a><br/>';
400        }
401        echo '<span class="info">('.$info.')</span>'.NL;
402
403        // view button
404        $link = ml($fakeId,'',true);
405        echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '.
406            'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>';
407
408        // mediamanager button
409        $link = wl('',array('do'=>'media','image'=>$fakeId,'ns'=>$ns));
410        echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/mediamanager.png" '.
411            'alt="'.$lang['btn_media'].'" title="'.$lang['btn_media'].'" class="btn" /></a>';
412
413        // delete button
414        if($item['writable'] && $auth >= AUTH_DELETE){
415            $link = DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($fakeId).
416                '&amp;sectok='.getSecurityToken();
417            echo ' <a href="'.$link.'" class="btn_media_delete" title="'.$fakeId_escaped.'">'.
418                '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '.
419                'title="'.$lang['btn_delete'].'" class="btn" /></a>';
420        }
421
422        echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">';
423        echo $lang['mediausage'].' <code>{{:'.str_replace(array('{','}'),array('(',')'),$fakeId_escaped).'}}</code>';
424        echo '</div>';
425        if($item['isimg']) media_printimgdetail($item);
426        echo '<div class="clearer"></div>'.NL;
427        echo '</div>'.NL;
428    }
429
430    /**
431     * Formats and prints one file in the list in the thumbnails view
432     *
433     * @see media_printfile_thumbs()
434     */
435    function _mod_media_printfile_thumbs($item,$auth,$jump=false,$display_namespace=false){
436        global $lang;
437        global $conf;
438
439        // Prepare filename
440        $file = $this->_getOriginalFileName($item['id']);
441
442        if ($file === false) {
443            $file = utf8_decodeFN($item['file']);
444        }
445
446        // build fake media id
447        $ns = getNS($item['id']);
448        $fakeId = $ns === false ? $file : "$ns:$file";
449        $fakeId_escaped = hsc($fakeId);
450
451        // output
452        echo '<li><dl title="'.$fakeId_escaped.'">'.NL;
453
454            echo '<dt>';
455        if($item['isimg']) {
456            media_printimgdetail($item, true);
457
458        } else {
459            echo '<a name="d_:'.$item['id'].'" class="image" title="'.$fakeId_escaped.'" href="'.
460                media_managerURL(array('image' => $fakeId, 'ns' => $ns,
461                'tab_details' => 'view')).'">';
462            echo media_printicon($fakeId_escaped);
463            echo '</a>';
464        }
465        echo '</dt>'.NL;
466        if (!$display_namespace) {
467            $name = hsc($file);
468        } else {
469            $name = $fakeId_escaped;
470        }
471        echo '<dd class="name"><a href="'.media_managerURL(array('image' => $fakeId, 'ns' => $ns,
472            'tab_details' => 'view')).'" name="h_:'.$item['id'].'">'.$name.'</a></dd>'.NL;
473
474        if($item['isimg']){
475            $size = '';
476            $size .= (int) $item['meta']->getField('File.Width');
477            $size .= '&#215;';
478            $size .= (int) $item['meta']->getField('File.Height');
479            echo '<dd class="size">'.$size.'</dd>'.NL;
480        } else {
481            echo '<dd class="size">&#160;</dd>'.NL;
482        }
483        $date = dformat($item['mtime']);
484        echo '<dd class="date">'.$date.'</dd>'.NL;
485        $filesize = filesize_h($item['size']);
486        echo '<dd class="filesize">'.$filesize.'</dd>'.NL;
487        echo '</dl></li>'.NL;
488    }
489}
490