]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/avatarsettings.php
Use array_merge instead of array_replace (same effect, and array_merge works with...
[quix0rs-gnu-social.git] / actions / avatarsettings.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Upload an avatar
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Settings
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @author    Zach Copley <zach@status.net>
26  * @copyright 2008-2009 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('STATUSNET') && !defined('LACONICA')) {
32     exit(1);
33 }
34
35
36
37 define('MAX_ORIGINAL', 480);
38
39 /**
40  * Upload an avatar
41  *
42  * We use jCrop plugin for jQuery to crop the image after upload.
43  *
44  * @category Settings
45  * @package  StatusNet
46  * @author   Evan Prodromou <evan@status.net>
47  * @author   Zach Copley <zach@status.net>
48  * @author   Sarven Capadisli <csarven@status.net>
49  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
50  * @link     http://status.net/
51  */
52 class AvatarsettingsAction extends SettingsAction
53 {
54     var $mode = null;
55     var $imagefile = null;
56     var $filename = null;
57
58     /**
59      * Title of the page
60      *
61      * @return string Title of the page
62      */
63     function title()
64     {
65         // TRANS: Title for avatar upload page.
66         return _('Avatar');
67     }
68
69     /**
70      * Instructions for use
71      *
72      * @return instructions for use
73      */
74     function getInstructions()
75     {
76         // TRANS: Instruction for avatar upload page.
77         // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
78         return sprintf(_('You can upload your personal avatar. The maximum file size is %s.'),
79                        ImageFile::maxFileSize());
80     }
81
82     /**
83      * Content area of the page
84      *
85      * Shows a form for uploading an avatar.
86      *
87      * @return void
88      */
89
90     function showContent()
91     {
92         if ($this->mode == 'crop') {
93             $this->showCropForm();
94         } else {
95             $this->showUploadForm();
96         }
97     }
98
99     function showUploadForm()
100     {
101         $user = common_current_user();
102
103         $profile = $user->getProfile();
104
105         if (!$profile) {
106             common_log_db_error($user, 'SELECT', __FILE__);
107             // TRANS: Error message displayed when referring to a user without a profile.
108             $this->serverError(_('User has no profile.'));
109             return;
110         }
111
112         $original = $profile->getOriginalAvatar();
113
114         $this->elementStart('form', array('enctype' => 'multipart/form-data',
115                                           'method' => 'post',
116                                           'id' => 'form_settings_avatar',
117                                           'class' => 'form_settings',
118                                           'action' =>
119                                           common_local_url('avatarsettings')));
120         $this->elementStart('fieldset');
121         // TRANS: Avatar upload page form legend.
122         $this->element('legend', null, _('Avatar settings'));
123         $this->hidden('token', common_session_token());
124
125         if (Event::handle('StartAvatarFormData', array($this))) {
126             $this->elementStart('ul', 'form_data');
127             if ($original) {
128                 $this->elementStart('li', array('id' => 'avatar_original',
129                                                 'class' => 'avatar_view'));
130                 // TRANS: Header on avatar upload page for thumbnail of originally uploaded avatar (h2).
131                 $this->element('h2', null, _("Original"));
132                 $this->elementStart('div', array('id'=>'avatar_original_view'));
133                 $this->element('img', array('src' => $original->url,
134                                             'width' => $original->width,
135                                             'height' => $original->height,
136                                             'alt' => $user->nickname));
137                 $this->elementEnd('div');
138                 $this->elementEnd('li');
139             }
140
141             $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
142
143             if ($avatar) {
144                 $this->elementStart('li', array('id' => 'avatar_preview',
145                                                 'class' => 'avatar_view'));
146                 // TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2).
147                 $this->element('h2', null, _("Preview"));
148                 $this->elementStart('div', array('id'=>'avatar_preview_view'));
149                 $this->element('img', array('src' => $avatar->url,
150                                             'width' => AVATAR_PROFILE_SIZE,
151                                             'height' => AVATAR_PROFILE_SIZE,
152                                             'alt' => $user->nickname));
153                 $this->elementEnd('div');
154                 if (!empty($avatar->filename)) {
155                     // TRANS: Button on avatar upload page to delete current avatar.
156                     $this->submit('delete', _m('BUTTON','Delete'));
157                 }
158                 $this->elementEnd('li');
159             }
160
161             $this->elementStart('li', array ('id' => 'settings_attach'));
162             $this->element('input', array('name' => 'MAX_FILE_SIZE',
163                                           'type' => 'hidden',
164                                           'id' => 'MAX_FILE_SIZE',
165                                           'value' => ImageFile::maxFileSizeInt()));
166             $this->element('input', array('name' => 'avatarfile',
167                                           'type' => 'file',
168                                           'id' => 'avatarfile'));
169             $this->elementEnd('li');
170             $this->elementEnd('ul');
171
172             $this->elementStart('ul', 'form_actions');
173             $this->elementStart('li');
174                 // TRANS: Button on avatar upload page to upload an avatar.
175             $this->submit('upload', _m('BUTTON','Upload'));
176             $this->elementEnd('li');
177             $this->elementEnd('ul');
178         }
179         Event::handle('EndAvatarFormData', array($this));
180
181         $this->elementEnd('fieldset');
182         $this->elementEnd('form');
183     }
184
185     function showCropForm()
186     {
187         $user = common_current_user();
188
189         $profile = $user->getProfile();
190
191         if (!$profile) {
192             common_log_db_error($user, 'SELECT', __FILE__);
193             // TRANS: Error message displayed when referring to a user without a profile.
194             $this->serverError(_('User has no profile.'));
195             return;
196         }
197
198         $original = $profile->getOriginalAvatar();
199
200         $this->elementStart('form', array('method' => 'post',
201                                           'id' => 'form_settings_avatar',
202                                           'class' => 'form_settings',
203                                           'action' =>
204                                           common_local_url('avatarsettings')));
205         $this->elementStart('fieldset');
206         // TRANS: Avatar upload page crop form legend.
207         $this->element('legend', null, _('Avatar settings'));
208         $this->hidden('token', common_session_token());
209
210         $this->elementStart('ul', 'form_data');
211
212         $this->elementStart('li',
213                             array('id' => 'avatar_original',
214                                   'class' => 'avatar_view'));
215         // TRANS: Header on avatar upload crop form for thumbnail of originally uploaded avatar (h2).
216         $this->element('h2', null, _('Original'));
217         $this->elementStart('div', array('id'=>'avatar_original_view'));
218         $this->element('img', array('src' => Avatar::url($this->filedata['filename']),
219                                     'width' => $this->filedata['width'],
220                                     'height' => $this->filedata['height'],
221                                     'alt' => $user->nickname));
222         $this->elementEnd('div');
223         $this->elementEnd('li');
224
225         $this->elementStart('li',
226                             array('id' => 'avatar_preview',
227                                   'class' => 'avatar_view'));
228         // TRANS: Header on avatar upload crop form for thumbnail of to be used rendition of uploaded avatar (h2).
229         $this->element('h2', null, _('Preview'));
230         $this->elementStart('div', array('id'=>'avatar_preview_view'));
231         $this->element('img', array('src' => Avatar::url($this->filedata['filename']),
232                                     'width' => AVATAR_PROFILE_SIZE,
233                                     'height' => AVATAR_PROFILE_SIZE,
234                                     'alt' => $user->nickname));
235         $this->elementEnd('div');
236
237         foreach (array('avatar_crop_x', 'avatar_crop_y',
238                        'avatar_crop_w', 'avatar_crop_h') as $crop_info) {
239             $this->element('input', array('name' => $crop_info,
240                                           'type' => 'hidden',
241                                           'id' => $crop_info));
242         }
243
244         // TRANS: Button on avatar upload crop form to confirm a selected crop as avatar.
245         $this->submit('crop', _m('BUTTON','Crop'));
246
247         $this->elementEnd('li');
248         $this->elementEnd('ul');
249         $this->elementEnd('fieldset');
250         $this->elementEnd('form');
251     }
252
253     /**
254      * Handle a post
255      *
256      * We mux on the button name to figure out what the user actually wanted.
257      *
258      * @return void
259      */
260     function handlePost()
261     {
262         // Workaround for PHP returning empty $_POST and $_FILES when POST
263         // length > post_max_size in php.ini
264
265         if (empty($_FILES)
266             && empty($_POST)
267             && ($_SERVER['CONTENT_LENGTH'] > 0)
268         ) {
269             // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
270             // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
271             $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.',
272                       'The server was unable to handle that much POST data (%s bytes) due to its current configuration.',
273                       intval($_SERVER['CONTENT_LENGTH']));
274             $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
275             return;
276         }
277
278         // CSRF protection
279
280         $token = $this->trimmed('token');
281         if (!$token || $token != common_session_token()) {
282             // TRANS: Client error displayed when the session token does not match or is not given.
283             $this->showForm(_('There was a problem with your session token. '.
284                                'Try again, please.'));
285             return;
286         }
287
288         if (Event::handle('StartAvatarSaveForm', array($this))) {
289             if ($this->arg('upload')) {
290                 $this->uploadAvatar();
291                 } else if ($this->arg('crop')) {
292                     $this->cropAvatar();
293                 } else if ($this->arg('delete')) {
294                     $this->deleteAvatar();
295                 } else {
296                     // TRANS: Unexpected validation error on avatar upload form.
297                     $this->showForm(_('Unexpected form submission.'));
298                 }
299             Event::handle('EndAvatarSaveForm', array($this));
300         }
301     }
302
303     /**
304      * Handle an image upload
305      *
306      * Does all the magic for handling an image upload, and crops the
307      * image by default.
308      *
309      * @return void
310      */
311     function uploadAvatar()
312     {
313         try {
314             $imagefile = ImageFile::fromUpload('avatarfile');
315         } catch (Exception $e) {
316             $this->showForm($e->getMessage());
317             return;
318         }
319         if ($imagefile === null) {
320             // TRANS: Validation error on avatar upload form when no file was uploaded.
321             $this->showForm(_('No file uploaded.'));
322             return;
323         }
324
325         $cur = common_current_user();
326         $type = $imagefile->preferredType();
327         $filename = Avatar::filename($cur->id,
328                                      image_type_to_extension($type),
329                                      null,
330                                      'tmp'.common_timestamp());
331
332         $filepath = Avatar::path($filename);
333         $imagefile->copyTo($filepath);
334
335         $filedata = array('filename' => $filename,
336                           'filepath' => $filepath,
337                           'width' => $imagefile->width,
338                           'height' => $imagefile->height,
339                           'type' => $type);
340
341         $_SESSION['FILEDATA'] = $filedata;
342
343         $this->filedata = $filedata;
344
345         $this->mode = 'crop';
346
347         // TRANS: Avatar upload form instruction after uploading a file.
348         $this->showForm(_('Pick a square area of the image to be your avatar.'),
349                         true);
350     }
351
352     /**
353      * Handle the results of jcrop.
354      *
355      * @return void
356      */
357     function cropAvatar()
358     {
359         $filedata = $_SESSION['FILEDATA'];
360
361         if (!$filedata) {
362             // TRANS: Server error displayed if an avatar upload went wrong somehow server side.
363             $this->serverError(_('Lost our file data.'));
364             return;
365         }
366
367         $file_d = ($filedata['width'] > $filedata['height'])
368                      ? $filedata['height'] : $filedata['width'];
369
370         $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0;
371         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0;
372         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d;
373         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$file_d;
374         $size = min($dest_w, $dest_h, MAX_ORIGINAL);
375
376         $user = common_current_user();
377         $profile = $user->getProfile();
378
379         $imagefile = new ImageFile($user->id, $filedata['filepath']);
380         $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h);
381
382         if ($profile->setOriginal($filename)) {
383             @unlink($filedata['filepath']);
384             unset($_SESSION['FILEDATA']);
385             $this->mode = 'upload';
386             // TRANS: Success message for having updated a user avatar.
387             $this->showForm(_('Avatar updated.'), true);
388             common_broadcast_profile($profile);
389         } else {
390             // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason.
391             $this->showForm(_('Failed updating avatar.'));
392         }
393     }
394
395     /**
396      * Get rid of the current avatar.
397      *
398      * @return void
399      */
400     function deleteAvatar()
401     {
402         $user = common_current_user();
403         $profile = $user->getProfile();
404
405         $avatar = $profile->getOriginalAvatar();
406         if($avatar) $avatar->delete();
407         $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
408         if($avatar) $avatar->delete();
409         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
410         if($avatar) $avatar->delete();
411         $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
412         if($avatar) $avatar->delete();
413
414         // TRANS: Success message for deleting a user avatar.
415         $this->showForm(_('Avatar deleted.'), true);
416     }
417
418     /**
419      * Add the jCrop stylesheet
420      *
421      * @return void
422      */
423
424     function showStylesheets()
425     {
426         parent::showStylesheets();
427         $this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
428     }
429
430     /**
431      * Add the jCrop scripts
432      *
433      * @return void
434      */
435     function showScripts()
436     {
437         parent::showScripts();
438
439         if ($this->mode == 'crop') {
440             $this->script('jcrop/jquery.Jcrop.min.js');
441             $this->script('jcrop/jquery.Jcrop.go.js');
442         }
443
444         $this->autofocus('avatarfile');
445     }
446 }