1<?php
2
3use dokuwiki\Extension\ActionPlugin;
4use dokuwiki\Extension\Event;
5use dokuwiki\Extension\EventHandler;
6use dokuwiki\plugin\imgpaste\Exception as PasteException;
7
8/**
9 * DokuWiki Plugin imgpaste (Action Component)
10 *
11 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
12 * @author  Andreas Gohr <gohr@cosmocode.de>
13 */
14class action_plugin_imgpaste extends ActionPlugin
15{
16
17    protected $tempdir = '';
18    protected $tempfile = '';
19
20    /**
21     * Clean up on destruction
22     */
23    public function __destruct()
24    {
25        $this->clean();
26    }
27
28    /** @inheritdoc */
29    public function register(EventHandler $controller)
30    {
31        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxUpload');
32    }
33
34
35    /**
36     * Creates a new file from the given data URL
37     *
38     * @param Event $event AJAX_CALL_UNKNOWN
39     */
40    public function handleAjaxUpload(Event $event)
41    {
42        if ($event->data != 'plugin_imgpaste') return;
43        $event->preventDefault();
44        $event->stopPropagation();
45
46        global $INPUT;
47        try {
48            if ($INPUT->has('url')) {
49                [$data, $type] = $this->externalUrlToData($INPUT->post->str('url'));
50            } else {
51                [$data, $type] = $this->dataUrlToData($INPUT->post->str('data'));
52            }
53            $result = $this->storeImage($data, $type);
54        } catch (PasteException $e) {
55            $this->clean();
56            http_status($e->getCode(), $e->getMessage());
57            exit;
58        }
59
60        header('Content-Type: application/json');
61        echo json_encode($result);
62    }
63
64    /**
65     * Get the binary data and mime type from a data URL
66     *
67     * @param string $dataUrl
68     * @return array [data, type]
69     * @throws PasteException
70     */
71    protected function dataUrlToData($dataUrl)
72    {
73        list($type, $data) = explode(';', $dataUrl);
74        if (!$data) throw new PasteException($this->getLang('e_nodata'), 400);
75
76        // process data encoding
77        $type = strtolower(substr($type, 5)); // strip 'data:' prefix
78        $data = substr($data, 7); // strip 'base64,' prefix
79        $data = base64_decode($data);
80        return [$data, $type];
81    }
82
83    /**
84     * Download the file from an external URL
85     *
86     * @param string $externalUrl
87     * @return array [data, type]
88     * @throws PasteException
89     */
90    protected function externalUrlToData($externalUrl)
91    {
92        global $lang;
93
94        // download the file
95        $http = new \dokuwiki\HTTP\DokuHTTPClient();
96        $data = $http->get($externalUrl);
97        if (!$data) throw new PasteException($lang['uploadfail'], 500);
98        [$type] = explode(';', $http->resp_headers['content-type']);
99        return [$data, $type];
100    }
101
102    /**
103     * @throws PasteException
104     */
105    protected function storeImage($data, $type)
106    {
107        global $lang;
108        global $INPUT;
109
110        // check for supported mime type
111        $mimetypes = array_flip(getMimeTypes());
112        if (!isset($mimetypes[$type])) throw new PasteException($lang['uploadwrong'], 415);
113
114        // prepare file names
115        $tempname = $this->storetemp($data);
116        $filename = $this->createFileName($INPUT->post->str('id'), $mimetypes[$type], $_SERVER['REMOTE_USER']);
117
118        // check ACLs
119        $auth = auth_quickaclcheck($filename);
120        if ($auth < AUTH_UPLOAD) throw new PasteException($lang['uploadfail'], 403);
121
122        // do the actual saving
123        $result = media_save(
124            [
125                'name' => $tempname,
126                'mime' => $type,
127                'ext' => $mimetypes[$type],
128            ],
129            $filename,
130            false,
131            $auth,
132            'copy'
133        );
134        if (is_array($result)) throw  new PasteException($result[0], 500);
135
136        //Still here? We had a successful upload
137        $this->clean();
138        return [
139            'message' => $lang['uploadsucc'],
140            'id' => $result,
141            'mime' => $type,
142            'ext' => $mimetypes[$type],
143            'url' => ml($result),
144        ];
145    }
146
147    /**
148     * Create the filename for the new file
149     *
150     * @param string $pageid the original page the paste event happend on
151     * @param string $ext the extension of the file
152     * @param string $user the currently logged in user
153     * @return string
154     */
155    protected function createFileName($pageid, $ext, $user)
156    {
157        $unique = '';
158        $filename = $this->getConf('filename');
159        $filename = str_replace(
160            [
161                '@NS@',
162                '@ID@',
163                '@USER@',
164                '@PAGE@',
165            ],
166            [
167                getNS($pageid),
168                $pageid,
169                $user,
170                noNS($pageid),
171            ],
172            $filename
173        );
174        $filename = strftime($filename);
175        $filename = cleanID($filename);
176        while (media_exists($filename . $unique . '.' . $ext)) {
177            $unique = (int)$unique + 1;
178        }
179        return $filename . $unique . '.' . $ext;
180    }
181
182    /**
183     * Create a temporary file from the given data
184     *
185     * exits if an error occurs
186     *
187     * @param $data
188     * @return string
189     */
190    protected function storetemp($data)
191    {
192        // store in temporary file
193        $this->tempdir = io_mktmpdir();
194        if (!$this->tempdir) throw new PasteException('', 500);
195        $this->tempfile = $this->tempdir . '/' . md5($data);
196        if (!io_saveFile($this->tempfile, $data)) throw new PasteException('', 500);
197        return $this->tempfile;
198    }
199
200    /**
201     * remove temporary file and directory
202     */
203    protected function clean()
204    {
205        if ($this->tempfile && file_exists($this->tempfile)) @unlink($this->tempfile);
206        if ($this->tempdir && is_dir($this->tempdir)) @rmdir($this->tempdir);
207        $this->tempfile = '';
208        $this->tempdir = '';
209    }
210
211}
212