Merge pull request #910 from tobiasd/20191106-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.0
6  * Author: Chris Case <http://friendika.openmindspace.org/profile/chris_case>
7  */
8
9 /**
10  *
11  * JavaScript Photo/Image Uploader
12  *
13  * Uses Valum 'qq' Uploader.
14  * Module Author: Chris Case
15  *
16  */
17
18 use Friendica\Core\Config;
19 use Friendica\Core\Hook;
20 use Friendica\Core\L10n;
21 use Friendica\Core\Logger;
22
23 function js_upload_install() {
24         Hook::register('photo_upload_form', 'addon/js_upload/js_upload.php', 'js_upload_form');
25         Hook::register('photo_post_init',   'addon/js_upload/js_upload.php', 'js_upload_post_init');
26         Hook::register('photo_post_file',   'addon/js_upload/js_upload.php', 'js_upload_post_file');
27         Hook::register('photo_post_end',    'addon/js_upload/js_upload.php', 'js_upload_post_end');
28 }
29
30
31 function js_upload_uninstall() {
32         Hook::unregister('photo_upload_form', 'addon/js_upload/js_upload.php', 'js_upload_form');
33         Hook::unregister('photo_post_init',   'addon/js_upload/js_upload.php', 'js_upload_post_init');
34         Hook::unregister('photo_post_file',   'addon/js_upload/js_upload.php', 'js_upload_post_file');
35         Hook::unregister('photo_post_end',    'addon/js_upload/js_upload.php', 'js_upload_post_end');
36 }
37
38
39 function js_upload_form(&$a,&$b) {
40
41         $b['default_upload'] = false;
42
43         $b['addon_text'] .= '<link href="' . $a->getBaseURL() . '/addon/js_upload/file-uploader/client/fileuploader.css" rel="stylesheet" type="text/css">';
44         $b['addon_text'] .= '<script src="' . $a->getBaseURL() . '/addon/js_upload/file-uploader/client/fileuploader.js" type="text/javascript"></script>';
45
46         $upload_msg = L10n::t('Select files for upload');
47         $drop_msg = L10n::t('Drop files here to upload');
48         $cancel = L10n::t('Cancel');
49         $failed = L10n::t('Failed');
50
51         $maximagesize = intval(Config::get('system','maximagesize'));
52
53         $b['addon_text'] .= <<< EOT
54
55  <div id="file-uploader-demo1">
56   <noscript>
57    <p>Please enable JavaScript to use file uploader.</p>
58    <!-- or put a simple form for upload here -->
59   </noscript>
60  </div>
61
62 <script type="text/javascript">
63 var uploader = null;
64 function getSelected(opt) {
65                         var selected = new Array();
66                         var index = 0;
67                         for (var intLoop = 0; intLoop < opt.length; intLoop++) {
68                                 if ((opt[intLoop].selected) ||
69                                         (opt[intLoop].checked)) {
70                                         index = selected.length;
71                                         //selected[index] = new Object;
72                                         selected[index] = opt[intLoop].value;
73                                         //selected[index] = intLoop;
74                                 }
75                         }
76                         return selected;
77                  }
78 function createUploader() {
79         uploader = new qq.FileUploader({
80                 element: document.getElementById('file-uploader-demo1'),
81                 action: '{$b['post_url']}',
82
83                 template: '<div class="qq-uploader">' +
84                                 '<div class="qq-upload-drop-area"><span>$drop_msg</span></div>' +
85                                 '<div class="qq-upload-button">$upload_msg</div>' +
86                                 '<ul class="qq-upload-list"></ul>' +
87                          '</div>',
88
89                 // template for one item in file list
90                 fileTemplate: '<li>' +
91                                 '<span class="qq-upload-file"></span>' +
92                                 '<span class="qq-upload-spinner"></span>' +
93                                 '<span class="qq-upload-size"></span>' +
94                                 '<a class="qq-upload-cancel" href="#">$cancel</a>' +
95                                 '<span class="qq-upload-failed-text">$failed</span>' +
96                         '</li>',
97
98                 debug: true,
99                 sizeLimit: $maximagesize,
100                 onSubmit: function(id,filename) {
101                         var newalbumElm = document.getElementById('photos-upload-newalbum');
102                         var albumElm = document.getElementById('photos-upload-album-select');
103
104                         var newalbum = newalbumElm ? newalbumElm.value : "";
105                         var album = albumElm ? albumElm.value : "";
106
107                         if (typeof acl != "undefined"){
108                                 uploader.setParams( {
109                                         newalbum      : newalbum,
110                                         album         : album,
111                                         not_visible   : document.getElementById('photos-upload-noshare').checked,
112                                         group_allow   : acl.allow_gid.join(','),
113                                         contact_allow : acl.allow_cid.join(','),
114                                         group_deny    : acl.deny_gid.join(','),
115                                         contact_deny  : acl.deny_cid.join(',')
116                                 });
117                         } else {
118                                 uploader.setParams( {
119                                         newalbum      : newalbum,
120                                         album         : album,
121                                         not_visible   : document.getElementById('photos-upload-noshare').checked,
122                                         group_allow   : getSelected(document.getElementById('group_allow')).join(','),
123                                         contact_allow : getSelected(document.getElementById('contact_allow')).join(','),
124                                         group_deny    : getSelected(document.getElementById('group_deny')).join(','),
125                                         contact_deny  : getSelected(document.getElementById('contact_deny')).join(',')
126                                 });
127                         }
128                 }
129         });
130 }
131
132
133 // in your app create uploader as soon as the DOM is ready
134 // don't wait for the window to load
135 window.onload = createUploader;
136
137
138 </script>
139
140 EOT;
141
142
143 }
144
145 function js_upload_post_init(&$a,&$b) {
146
147         // list of valid extensions, ex. array("jpeg", "xml", "bmp")
148
149         $allowedExtensions = ["jpeg","gif","png","jpg"];
150
151         // max file size in bytes
152
153         $sizeLimit = Config::get('system','maximagesize'); //6 * 1024 * 1024;
154
155         $uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
156
157         $result = $uploader->handleUpload();
158
159
160         // to pass data through iframe you will need to encode all html tags
161         $a->data['upload_jsonresponse'] =  htmlspecialchars(json_encode($result), ENT_NOQUOTES);
162
163         if(isset($result['error'])) {
164                 Logger::log('mod/photos.php: photos_post(): error uploading photo: ' . $result['error'] , Logger::DEBUG);
165                 echo json_encode($result);
166                 exit();
167         }
168
169         $a->data['upload_result'] = $result;
170
171 }
172
173 function js_upload_post_file(&$a,&$b) {
174
175         $result = $a->data['upload_result'];
176
177         $b['src']       = $result['path'];
178         $b['filename']  = $result['filename'];
179         $b['filesize']  = filesize($b['src']);
180
181 }
182
183
184 function js_upload_post_end(&$a,&$b) {
185
186 Logger::log('upload_post_end');
187         if(!empty($a->data['upload_jsonresponse'])) {
188                 echo $a->data['upload_jsonresponse'];
189                 exit();
190         }
191
192 }
193
194
195 /**
196  * Handle file uploads via XMLHttpRequest
197  */
198 class qqUploadedFileXhr {
199
200         private $pathnm = '';
201
202         /**
203          * Save the file in the temp dir.
204          * @return boolean TRUE on success
205          */
206         function save() {
207                 $input = fopen("php://input", "r");
208
209                 $upload_dir = Config::get('system','tempdir');
210                 if(! $upload_dir)
211                         $upload_dir = sys_get_temp_dir();
212
213                 $this->pathnm = tempnam($upload_dir,'frn');
214
215                 $temp = fopen($this->pathnm,"w");
216                 $realSize = stream_copy_to_stream($input, $temp);
217
218                 fclose($input);
219                 fclose($temp);
220
221                 if ($realSize != $this->getSize()) {
222                         return false;
223                 }
224                 return true;
225         }
226
227         function getPath() {
228                 return $this->pathnm;
229         }
230
231         function getName() {
232                 return $_GET['qqfile'];
233         }
234
235         function getSize() {
236                 if (isset($_SERVER["CONTENT_LENGTH"])){
237                         return (int)$_SERVER["CONTENT_LENGTH"];
238                 } else {
239                         throw new Exception('Getting content length is not supported.');
240                 }
241         }
242 }
243
244 /**
245  * Handle file uploads via regular form post (uses the $_FILES array)
246  */
247
248 class qqUploadedFileForm {
249
250
251         /**
252          * Save the file to the specified path
253          * @return boolean TRUE on success
254          */
255
256
257         function save() {
258                 return true;
259         }
260
261         function getPath() {
262                 return $_FILES['qqfile']['tmp_name'];
263         }
264
265         function getName() {
266                 return $_FILES['qqfile']['name'];
267         }
268         function getSize() {
269                 return $_FILES['qqfile']['size'];
270         }
271 }
272
273 class qqFileUploader {
274         private $allowedExtensions = [];
275         private $sizeLimit = 10485760;
276         private $file;
277
278         function __construct(array $allowedExtensions = [], $sizeLimit = 10485760){
279                 $allowedExtensions = array_map("strtolower", $allowedExtensions);
280
281                 $this->allowedExtensions = $allowedExtensions;
282                 $this->sizeLimit = $sizeLimit;
283
284                 if (isset($_GET['qqfile'])) {
285                         $this->file = new qqUploadedFileXhr();
286                 } elseif (isset($_FILES['qqfile'])) {
287                         $this->file = new qqUploadedFileForm();
288                 } else {
289                         $this->file = false;
290                 }
291
292         }
293
294
295         private function toBytes($str){
296                 $val = trim($str);
297                 $last = strtolower($str[strlen($str)-1]);
298                 switch($last) {
299                         case 'g': $val *= 1024;
300                         case 'm': $val *= 1024;
301                         case 'k': $val *= 1024;
302                 }
303                 return $val;
304         }
305
306         /**
307          * Returns array('success'=>true) or array('error'=>'error message')
308          */
309         function handleUpload(){
310
311                 if (!$this->file) {
312                         return ['error' => L10n::t('No files were uploaded.')];
313                 }
314
315                 $size = $this->file->getSize();
316
317                 if ($size == 0) {
318                         return ['error' => L10n::t('Uploaded file is empty')];
319                 }
320
321 //              if ($size > $this->sizeLimit) {
322
323 //                      return array('error' => L10n::t('Uploaded file is too large'));
324 //              }
325
326
327                 $maximagesize = Config::get('system','maximagesize');
328
329                 if(($maximagesize) && ($size > $maximagesize)) {
330                         return ['error' => L10n::t('Image exceeds size limit of ') . $maximagesize ];
331
332                 }
333
334                 $pathinfo = pathinfo($this->file->getName());
335                 $filename = $pathinfo['filename'];
336
337                 if (!isset($pathinfo['extension'])) {
338                         Logger::warning('extension isn\'t set.', ['filename' => $filename]);
339                 }
340                 $ext = $pathinfo['extension'] ?? '';
341
342                 if($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions)){
343                         $these = implode(', ', $this->allowedExtensions);
344                         return ['error' => L10n::t('File has an invalid extension, it should be one of ') . $these . '.'];
345                 }
346
347                 if ($this->file->save()){
348                         return [
349                                 'success'  => true,
350                                 'path'     => $this->file->getPath(),
351                                 'filename' => $filename . '.' . $ext
352                         ];
353                 } else {
354                         return [
355                                 'error'    => L10n::t('Upload was cancelled, or server error encountered'),
356                                 'path'     => $this->file->getPath(),
357                                 'filename' => $filename . '.' . $ext
358                         ];
359                 }
360
361         }
362 }