Merge pull request #988 from tobiasd/20200615-lng
[friendica-addons.git/.git] / js_upload / js_upload.php
1 <?php
2 /**
3  * Name: JS Uploader
4  * Description: JavaScript photo/image uploader. Uses Valum 'qq' Uploader.
5  * Version: 1.1
6  * Author: Chris Case <http://friendika.openmindspace.org/profile/chris_case>
7  * Maintainer: Hypolite Petovan <https://friendica.mrpetovan.com/profile/hypolite>
8  */
9
10 use Friendica\App;
11 use Friendica\Core\Hook;
12 use Friendica\Core\Logger;
13 use Friendica\Core\Renderer;
14 use Friendica\DI;
15
16 function js_upload_install()
17 {
18         Hook::register('photo_upload_form', __FILE__, 'js_upload_form');
19         Hook::register('photo_post_init', __FILE__, 'js_upload_post_init');
20         Hook::register('photo_post_file', __FILE__, 'js_upload_post_file');
21         Hook::register('photo_post_end', __FILE__, 'js_upload_post_end');
22 }
23
24 function js_upload_form(App $a, array &$b)
25 {
26         $b['default_upload'] = false;
27
28         DI::page()->registerStylesheet('addon/js_upload/file-uploader/client/fileuploader.css');
29         DI::page()->registerFooterScript('addon/js_upload/file-uploader/client/fileuploader.js');
30
31         $tpl = Renderer::getMarkupTemplate('js_upload.tpl', 'addon/js_upload');
32         $b['addon_text'] .= Renderer::replaceMacros($tpl, [
33                 '$upload_msg' => DI::l10n()->t('Select files for upload'),
34                 '$drop_msg' => DI::l10n()->t('Drop files here to upload'),
35                 '$cancel' => DI::l10n()->t('Cancel'),
36                 '$failed' => DI::l10n()->t('Failed'),
37                 '$post_url' => $b['post_url'],
38                 '$maximagesize' => intval(DI::config()->get('system', 'maximagesize')),
39         ]);
40 }
41
42 function js_upload_post_init(App $a, &$b)
43 {
44         // list of valid extensions
45         $allowedExtensions = ['jpeg', 'gif', 'png', 'jpg'];
46
47         // max file size in bytes
48         $sizeLimit = DI::config()->get('system', 'maximagesize');
49
50         $uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
51
52         $result = $uploader->handleUpload();
53
54         // to pass data through iframe you will need to encode all html tags
55         $a->data['upload_jsonresponse'] = htmlspecialchars(json_encode($result), ENT_NOQUOTES);
56
57         if (isset($result['error'])) {
58                 Logger::log('mod/photos.php: photos_post(): error uploading photo: ' . $result['error'], Logger::DEBUG);
59                 echo json_encode($result);
60                 exit();
61         }
62
63         $a->data['upload_result'] = $result;
64 }
65
66 function js_upload_post_file(App $a, &$b)
67 {
68         $result = $a->data['upload_result'];
69
70         $b['src'] = $result['path'];
71         $b['filename'] = $result['filename'];
72         $b['filesize'] = filesize($b['src']);
73
74 }
75
76 function js_upload_post_end(App $a, &$b)
77 {
78         Logger::log('upload_post_end');
79         if (!empty($a->data['upload_jsonresponse'])) {
80                 echo $a->data['upload_jsonresponse'];
81                 exit();
82         }
83 }
84
85 /**
86  * Handle file uploads via XMLHttpRequest
87  */
88 class qqUploadedFileXhr
89 {
90         private $pathnm = '';
91
92         /**
93          * Save the file in the temp dir.
94          *
95          * @return boolean TRUE on success
96          */
97         function save()
98         {
99                 $input = fopen('php://input', 'r');
100
101                 $upload_dir = DI::config()->get('system', 'tempdir');
102                 if (!$upload_dir)
103                         $upload_dir = sys_get_temp_dir();
104
105                 $this->pathnm = tempnam($upload_dir, 'frn');
106
107                 $temp = fopen($this->pathnm, 'w');
108                 $realSize = stream_copy_to_stream($input, $temp);
109
110                 fclose($input);
111                 fclose($temp);
112
113                 if ($realSize != $this->getSize()) {
114                         return false;
115                 }
116                 return true;
117         }
118
119         function getPath()
120         {
121                 return $this->pathnm;
122         }
123
124         function getName()
125         {
126                 return $_GET['qqfile'];
127         }
128
129         function getSize()
130         {
131                 if (isset($_SERVER['CONTENT_LENGTH'])) {
132                         return (int)$_SERVER['CONTENT_LENGTH'];
133                 } else {
134                         throw new Exception('Getting content length is not supported.');
135                 }
136         }
137 }
138
139 /**
140  * Handle file uploads via regular form post (uses the $_FILES array)
141  */
142 class qqUploadedFileForm
143 {
144         /**
145          * Save the file to the specified path
146          *
147          * @return boolean TRUE on success
148          */
149         function save()
150         {
151                 return true;
152         }
153
154         function getPath()
155         {
156                 return $_FILES['qqfile']['tmp_name'];
157         }
158
159         function getName()
160         {
161                 return $_FILES['qqfile']['name'];
162         }
163
164         function getSize()
165         {
166                 return $_FILES['qqfile']['size'];
167         }
168 }
169
170 class qqFileUploader
171 {
172         private $allowedExtensions = [];
173         private $sizeLimit = 10485760;
174         private $file;
175
176         function __construct(array $allowedExtensions = [], $sizeLimit = 10485760)
177         {
178                 $allowedExtensions = array_map('strtolower', $allowedExtensions);
179
180                 $this->allowedExtensions = $allowedExtensions;
181                 $this->sizeLimit = $sizeLimit;
182
183                 if (isset($_GET['qqfile'])) {
184                         $this->file = new qqUploadedFileXhr();
185                 } elseif (isset($_FILES['qqfile'])) {
186                         $this->file = new qqUploadedFileForm();
187                 } else {
188                         $this->file = false;
189                 }
190
191         }
192
193         private function toBytes($str)
194         {
195                 $val = trim($str);
196                 $last = strtolower($str[strlen($str) - 1]);
197                 switch ($last) {
198                         case 'g':
199                                 $val *= 1024;
200                         case 'm':
201                                 $val *= 1024;
202                         case 'k':
203                                 $val *= 1024;
204                 }
205                 return $val;
206         }
207
208         /**
209          * Returns array('success'=>true) or array('error'=>'error message')
210          */
211         function handleUpload()
212         {
213                 if (!$this->file) {
214                         return ['error' => DI::l10n()->t('No files were uploaded.')];
215                 }
216
217                 $size = $this->file->getSize();
218
219                 if ($size == 0) {
220                         return ['error' => DI::l10n()->t('Uploaded file is empty')];
221                 }
222
223 //              if ($size > $this->sizeLimit) {
224
225 //                      return array('error' => DI::l10n()->t('Uploaded file is too large'));
226 //              }
227
228
229                 $maximagesize = DI::config()->get('system', 'maximagesize');
230
231                 if (($maximagesize) && ($size > $maximagesize)) {
232                         return ['error' => DI::l10n()->t('Image exceeds size limit of ') . $maximagesize];
233
234                 }
235
236                 $pathinfo = pathinfo($this->file->getName());
237                 $filename = $pathinfo['filename'];
238
239                 if (!isset($pathinfo['extension'])) {
240                         Logger::warning('extension isn\'t set.', ['filename' => $filename]);
241                 }
242                 $ext = $pathinfo['extension'] ?? '';
243
244                 if ($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions)) {
245                         $these = implode(', ', $this->allowedExtensions);
246                         return ['error' => DI::l10n()->t('File has an invalid extension, it should be one of ') . $these . '.'];
247                 }
248
249                 if ($this->file->save()) {
250                         return [
251                                 'success' => true,
252                                 'path' => $this->file->getPath(),
253                                 'filename' => $filename . '.' . $ext
254                         ];
255                 } else {
256                         return [
257                                 'error' => DI::l10n()->t('Upload was cancelled, or server error encountered'),
258                                 'path' => $this->file->getPath(),
259                                 'filename' => $filename . '.' . $ext
260                         ];
261                 }
262         }
263 }