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