4 * Description: JavaScript photo/image uploader. Uses Valum 'qq' Uploader.
6 * Author: Chris Case <http://friendika.openmindspace.org/profile/chris_case>
11 * JavaScript Photo/Image Uploader
13 * Uses Valum 'qq' Uploader.
14 * Module Author: Chris Case
18 use Friendica\Core\Config;
19 use Friendica\Core\Hook;
20 use Friendica\Core\L10n;
21 use Friendica\Core\Logger;
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');
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');
39 function js_upload_form(&$a,&$b) {
41 $b['default_upload'] = false;
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>';
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');
51 $maximagesize = intval(Config::get('system','maximagesize'));
53 $b['addon_text'] .= <<< EOT
55 <div id="file-uploader-demo1">
57 <p>Please enable JavaScript to use file uploader.</p>
58 <!-- or put a simple form for upload here -->
62 <script type="text/javascript">
64 function getSelected(opt) {
65 var selected = new Array();
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;
78 function createUploader() {
79 uploader = new qq.FileUploader({
80 element: document.getElementById('file-uploader-demo1'),
81 action: '{$b['post_url']}',
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>' +
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>' +
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');
104 var newalbum = newalbumElm ? newalbumElm.value : "";
105 var album = albumElm ? albumElm.value : "";
107 if (typeof acl != "undefined"){
108 uploader.setParams( {
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(',')
118 uploader.setParams( {
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(',')
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;
145 function js_upload_post_init(&$a,&$b) {
147 // list of valid extensions, ex. array("jpeg", "xml", "bmp")
149 $allowedExtensions = ["jpeg","gif","png","jpg"];
151 // max file size in bytes
153 $sizeLimit = Config::get('system','maximagesize'); //6 * 1024 * 1024;
155 $uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
157 $result = $uploader->handleUpload();
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);
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);
169 $a->data['upload_result'] = $result;
173 function js_upload_post_file(&$a,&$b) {
175 $result = $a->data['upload_result'];
177 $b['src'] = $result['path'];
178 $b['filename'] = $result['filename'];
179 $b['filesize'] = filesize($b['src']);
184 function js_upload_post_end(&$a,&$b) {
186 Logger::log('upload_post_end');
187 if(!empty($a->data['upload_jsonresponse'])) {
188 echo $a->data['upload_jsonresponse'];
196 * Handle file uploads via XMLHttpRequest
198 class qqUploadedFileXhr {
200 private $pathnm = '';
203 * Save the file in the temp dir.
204 * @return boolean TRUE on success
207 $input = fopen("php://input", "r");
209 $upload_dir = Config::get('system','tempdir');
211 $upload_dir = sys_get_temp_dir();
213 $this->pathnm = tempnam($upload_dir,'frn');
215 $temp = fopen($this->pathnm,"w");
216 $realSize = stream_copy_to_stream($input, $temp);
221 if ($realSize != $this->getSize()) {
228 return $this->pathnm;
232 return $_GET['qqfile'];
236 if (isset($_SERVER["CONTENT_LENGTH"])){
237 return (int)$_SERVER["CONTENT_LENGTH"];
239 throw new Exception('Getting content length is not supported.');
245 * Handle file uploads via regular form post (uses the $_FILES array)
248 class qqUploadedFileForm {
252 * Save the file to the specified path
253 * @return boolean TRUE on success
262 return $_FILES['qqfile']['tmp_name'];
266 return $_FILES['qqfile']['name'];
269 return $_FILES['qqfile']['size'];
273 class qqFileUploader {
274 private $allowedExtensions = [];
275 private $sizeLimit = 10485760;
278 function __construct(array $allowedExtensions = [], $sizeLimit = 10485760){
279 $allowedExtensions = array_map("strtolower", $allowedExtensions);
281 $this->allowedExtensions = $allowedExtensions;
282 $this->sizeLimit = $sizeLimit;
284 if (isset($_GET['qqfile'])) {
285 $this->file = new qqUploadedFileXhr();
286 } elseif (isset($_FILES['qqfile'])) {
287 $this->file = new qqUploadedFileForm();
295 private function toBytes($str){
297 $last = strtolower($str[strlen($str)-1]);
299 case 'g': $val *= 1024;
300 case 'm': $val *= 1024;
301 case 'k': $val *= 1024;
307 * Returns array('success'=>true) or array('error'=>'error message')
309 function handleUpload(){
312 return ['error' => L10n::t('No files were uploaded.')];
315 $size = $this->file->getSize();
318 return ['error' => L10n::t('Uploaded file is empty')];
321 // if ($size > $this->sizeLimit) {
323 // return array('error' => L10n::t('Uploaded file is too large'));
327 $maximagesize = Config::get('system','maximagesize');
329 if(($maximagesize) && ($size > $maximagesize)) {
330 return ['error' => L10n::t('Image exceeds size limit of ') . $maximagesize ];
334 $pathinfo = pathinfo($this->file->getName());
335 $filename = $pathinfo['filename'];
337 if (!isset($pathinfo['extension'])) {
338 Logger::warning('extension isn\'t set.', ['filename' => $filename]);
340 $ext = defaults($pathinfo, 'extension', '');
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 . '.'];
347 if ($this->file->save()){
350 'path' => $this->file->getPath(),
351 'filename' => $filename . '.' . $ext
355 'error' => L10n::t('Upload was cancelled, or server error encountered'),
356 'path' => $this->file->getPath(),
357 'filename' => $filename . '.' . $ext