1<?php
2/**
3 * Action Plugin ArchiveUpload
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Michael Klier chi@chimeric.de
7 */
8
9/**
10 * DokuWiki Action Plugin Archive Upload
11 *
12 * @author Michael Klier <chi@chimeric.de>
13 */
14class action_plugin_archiveupload extends DokuWiki_Action_Plugin {
15
16    var $tmpdir = '';
17
18    /**
19     * return some info
20     */
21    function getInfo() {
22        return array(
23                'author' => 'Michael Klier',
24                'email'  => 'chi@chimeric.de',
25                'date'   => @file_get_contents(DOKU_PLUGIN.'archiveupload/VERSION'),
26                'name'   => 'ArchiveUpload',
27                'desc'   => 'Allows you to unpack uploaded archives.',
28                'url'    => 'http://dokuwiki.org/plugin:archiveupload'
29            );
30    }
31
32     /**
33      * Registers our callback functions
34      */
35    function register(Doku_Event_Handler $controller) {
36        $controller->register_hook('HTML_UPLOADFORM_OUTPUT', 'BEFORE', $this, 'handle_form_output');
37        $controller->register_hook('MEDIA_UPLOAD_FINISH', 'BEFORE', $this, 'handle_media_upload');
38        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'AFTER', $this, 'metaheaders_after');
39    }
40    /**
41     * Disables new uploader in favor of old
42     *
43     * @author Myron Turner <turnermm02@shaw.ca>
44     */
45     function metaheaders_after(&$event, $param) {
46            ptln( "\n<script type='text/javascript'>\n //<![CDATA[\n");
47            ptln("qq = {};\n //]]>\n</script>");
48     }
49    /**
50     * Adds a checkbox
51     *
52     * @author Michael Klier <chi@chimeric.de>
53     */
54    function handle_form_output(&$event, $param) {
55        global $INFO;
56        if($this->getConf('manageronly')) {
57            if(!$INFO['isadmin'] && !$INFO['ismanager']) return;
58        }
59        $event->data->addElement(form_makeCheckboxField('unpack', 0, $this->getLang('unpack')));
60    }
61
62    /**
63     * MEDIA_UPLOAD_FINISH handler
64     *
65     * @author Michael Klier <chi@chimeric.de>
66     */
67    function handle_media_upload(&$event, $param) {
68        global $INFO;
69
70        // nothing todo
71        if(!isset($_REQUEST['unpack'])) return;
72
73        if($this->getConf('manageronly')) {
74            if(!$INFO['isadmin'] && !$INFO['ismanager']) return;
75        }
76
77        // our turn - prevent default action
78        $event->preventDefault();
79
80        call_user_func_array(array($this,'extract_archive'), $event->data);
81    }
82
83    /**
84     * Uploads an extracts an archive
85     * FIXME add bz and bz2 support
86     *
87     * @author Michael Klier <chi@chimeric.de>
88     */
89    function extract_archive($fn_tmp, $fn, $id, $imime) {
90        global $lang;
91        global $conf;
92
93        $dir = io_mktmpdir();
94        if($dir) {
95            $this->tmpdir = $dir;
96        } else {
97            msg('Failed to create tmp dir, check permissions of cache/ directory', -1);
98            return false;
99        }
100
101        // failed to create tmp dir stop here
102        if(!$this->tmpdir) return false;
103
104        $ext = substr($fn, strrpos($fn,'.')+1);
105
106        if(in_array($ext, array('tar','gz','tgz','zip'))) {
107
108            //prepare directory
109            //FIXME needed? do it later?
110            io_createNamespace($id, 'media');
111
112            if(move_uploaded_file($fn_tmp, $fn)) {
113
114                chmod($fn, $conf['fmode']);
115
116                if($this->decompress($fn, dirname($fn))) {
117                    msg($this->getLang('decompr_succ'), 1);
118                } else {
119                    msg($this->getLang('decompr_err'), -1);
120                }
121
122                // delete archive after decompression
123                // fixme check for success?
124                unlink($fn);
125
126            } else {
127                msg($lang['uploadfail'], -1);
128            }
129
130        } else {
131            msg($this->getLang('unsupported_ftype'), -1);
132            return false;
133        }
134
135        // remove tmpdir in any case
136        rmdir($this->tmpdir);
137
138        // fixme do a sweepNS here, just in case?
139    }
140
141    /**
142     * Decompress an archive (adopted from plugin manager)
143     *
144     * @author Christopher Smith <chris@jalakai.co.uk>
145     * @author Michael Klier <chi@chimeric.de>
146     */
147    function decompress($file, $target) {
148
149        // need to source plugin manager because otherwise the ZipLib doesn't work
150        // FIXME fix ZipLib.class.php
151        require_once(DOKU_INC.'lib/plugins/plugin/admin.php');
152
153        // decompression library doesn't like target folders ending in "/"
154        if(substr($target, -1) == "/") $target = substr($target, 0, -1);
155
156        $ext = substr($file, strrpos($file,'.')+1);
157
158        if(in_array($ext, array('tar','gz','tgz'))) {
159
160            require_once(DOKU_INC."inc/TarLib.class.php");
161
162            if(strpos($ext, 'gz') !== false) $compress_type = COMPRESS_GZIP;
163            //else if (strpos($ext,'bz') !== false) $compress_type = COMPRESS_BZIP; // FIXME bz2 support
164            else $compress_type = COMPRESS_NONE;
165
166            $tar = new TarLib($file, $compress_type);
167            $ok  = $tar->Extract(FULL_ARCHIVE, $this->tmpdir, '', 0777);
168
169            if($ok) {
170                $files = $tar->ListContents();
171                $this->postProcessFiles($target, $files);
172                return true;
173            } else {
174               return false;
175            }
176
177        } else if ($ext == 'zip') {
178
179            require_once(DOKU_INC."inc/ZipLib.class.php");
180
181            $zip = new ZipLib();
182            $ok  = $zip->Extract($file, $this->tmpdir);
183
184            if($ok) {
185                $files = $zip->get_List($file);
186                $this->postProcessFiles($target, $files);
187                return true;
188            } else {
189                return false;
190            }
191
192        }
193
194        // unsupported file type
195        return false;
196    }
197
198    /**
199     * Checks the mime type and fixes the permission and filenames of the
200     * extracted files and sends a notification email per uploaded file
201     *
202     * @author Michael Klier <chi@chimeric.de>
203     */
204    function postProcessFiles($dir, $files) {
205        global $conf;
206        global $lang;
207
208        require_once(DOKU_INC.'inc/media.php');
209        $reldir = preg_replace("#".$conf['mediadir']."#", '/', $dir) . '/';
210
211        // get filetype regexp
212        $types = array_keys(getMimeTypes());
213        $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types);
214        $regex = join('|',$types);
215
216        $dirs     = array();
217        $tmp_dirs = array();
218
219        foreach($files as $file) {
220            $fn_old = $file['filename'];                                // original filename
221            $fn_new = str_replace('/',':',$fn_old);                     // target filename
222            $fn_new = str_replace(':', '/', cleanID($fn_new));
223
224            if(substr($fn_old, -1) == '/') {
225                // given file is a directory
226                io_mkdir_p($dir.'/'.$fn_new);
227                chmod($dir.'/'.$fn_new, $conf['dmode']);
228                array_push($dirs, $dir.'/'.$fn_new);
229                array_push($tmp_dirs, $this->tmpdir.'/'.$fn_old);
230            } else {
231                list($ext, $imime) = mimetype($this->tmpdir.'/'.$fn_old);
232
233                if(preg_match('/\.('.$regex.')$/i',$fn_old)){
234                    // check for overwrite
235                    if(@file_exists($dir.'/'.$fn_new) && (!$_POST['ow'] || $auth < AUTH_DELETE)){
236                        msg($lang['uploadexist'],0);
237                        continue;
238                    }
239
240                    // check for valid content
241                    $ok = media_contentcheck($this->tmpdir.'/'.$fn_old,$imime);
242                    if($ok == -1){
243                        msg(sprintf($lang['uploadbadcontent'],".$ext"),-1);
244                        unlink($this->tmpdir.'/'.$fn_old);
245                        continue;
246                    }elseif($ok == -2){
247                        msg($lang['uploadspam'],-1);
248                        unlink($this->tmpdir.'/'.$fn_old);
249                        continue;
250                    }elseif($ok == -3){
251                        msg($lang['uploadxss'],-1);
252                        unlink($this->tmpdir.'/'.$fn_old);
253                        continue;
254                    }
255
256                    // everything's ok - lets move the file
257                    // FIXME check for success ??
258                    rename($this->tmpdir.'/'.$fn_old, $dir.'/'.$fn_new);
259                    chmod($dir.'/'.$fn_new, $conf['fmode']);
260
261                    // send notification mail
262                    $id = cleanID(str_replace('/',':',$reldir.'/'.$fn_new));
263                    media_notify($id, $dir.'/'.$fn_new, $imime);
264                    msg($lang['uploadsucc'], 1);
265
266                } else {
267                    msg($lang['uploadwrong'],-1);
268                    @unlink($this->tmpdir.'/'.$fn_old);
269                    continue;
270                }
271            }
272        }
273
274        // done - remove eventually left over empty dirs in destination directory
275        natsort($dirs);
276        $dirs = array_reverse($dirs);
277        foreach($dirs as $dir) {
278            @rmdir($dir);
279        }
280
281        // do the same for the tmp dir
282        natsort($tmp_dirs);
283        $dirs = array_reverse($tmp_dirs);
284        foreach($dirs as $dir) {
285            @rmdir($dir);
286        }
287    }
288}
289// vim:ts=4:sw=4:et:enc=utf8:
290