avatarsettings.php 16.1 KB
Newer Older
1 2
<?php
/**
3
 * StatusNet, the distributed open-source microblogging tool
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Upload an avatar
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Settings
23
 * @package   StatusNet
24 25
 * @author    Evan Prodromou <evan@status.net>
 * @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 33 34
    exit(1);
}

35

36

37 38
define('MAX_ORIGINAL', 480);

39 40 41
/**
 * Upload an avatar
 *
sarven's avatar
sarven committed
42
 * We use jCrop plugin for jQuery to crop the image after upload.
43 44
 *
 * @category Settings
45
 * @package  StatusNet
46 47 48
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @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 55 56 57
    var $mode = null;
    var $imagefile = null;
    var $filename = null;

58 59 60 61 62 63 64
    /**
     * Title of the page
     *
     * @return string Title of the page
     */
    function title()
    {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
65
        // TRANS: Title for avatar upload page.
66 67 68 69 70 71 72 73 74 75
        return _('Avatar');
    }

    /**
     * Instructions for use
     *
     * @return instructions for use
     */
    function getInstructions()
    {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
76 77 78 79
        // TRANS: Instruction for avatar upload page.
        // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
        return sprintf(_('You can upload your personal avatar. The maximum file size is %s.'),
                       ImageFile::maxFileSize());
80 81 82 83 84 85 86 87 88 89 90
    }

    /**
     * Content area of the page
     *
     * Shows a form for uploading an avatar.
     *
     * @return void
     */

    function showContent()
91 92 93 94 95 96 97 98 99
    {
        if ($this->mode == 'crop') {
            $this->showCropForm();
        } else {
            $this->showUploadForm();
        }
    }

    function showUploadForm()
100 101 102 103 104 105 106
    {
        $user = common_current_user();

        $profile = $user->getProfile();

        if (!$profile) {
            common_log_db_error($user, 'SELECT', __FILE__);
107 108
            // TRANS: Error message displayed when referring to a user without a profile.
            $this->serverError(_('User has no profile.'));
109 110 111 112 113 114
            return;
        }

        $original = $profile->getOriginalAvatar();

        $this->elementStart('form', array('enctype' => 'multipart/form-data',
sarven's avatar
sarven committed
115 116 117
                                          'method' => 'post',
                                          'id' => 'form_settings_avatar',
                                          'class' => 'form_settings',
118 119
                                          'action' =>
                                          common_local_url('avatarsettings')));
sarven's avatar
sarven committed
120
        $this->elementStart('fieldset');
Siebrand Mazeland's avatar
Siebrand Mazeland committed
121
        // TRANS: Avatar upload page form legend.
sarven's avatar
sarven committed
122
        $this->element('legend', null, _('Avatar settings'));
123
        $this->hidden('token', common_session_token());
Siebrand Mazeland's avatar
Siebrand Mazeland committed
124

125 126 127 128 129
        if (Event::handle('StartAvatarFormData', array($this))) {
            $this->elementStart('ul', 'form_data');
            if ($original) {
                $this->elementStart('li', array('id' => 'avatar_original',
                                                'class' => 'avatar_view'));
Siebrand Mazeland's avatar
Siebrand Mazeland committed
130
                // TRANS: Header on avatar upload page for thumbnail of originally uploaded avatar (h2).
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
                $this->element('h2', null, _("Original"));
                $this->elementStart('div', array('id'=>'avatar_original_view'));
                $this->element('img', array('src' => $original->url,
                                            'width' => $original->width,
                                            'height' => $original->height,
                                            'alt' => $user->nickname));
                $this->elementEnd('div');
                $this->elementEnd('li');
            }

            $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);

            if ($avatar) {
                $this->elementStart('li', array('id' => 'avatar_preview',
                                                'class' => 'avatar_view'));
Siebrand Mazeland's avatar
Siebrand Mazeland committed
146
                // TRANS: Header on avatar upload page for thumbnail of to be used rendition of uploaded avatar (h2).
147 148
                $this->element('h2', null, _("Preview"));
                $this->elementStart('div', array('id'=>'avatar_preview_view'));
149
                $this->element('img', array('src' => $avatar->url,
150 151 152 153
                                            'width' => AVATAR_PROFILE_SIZE,
                                            'height' => AVATAR_PROFILE_SIZE,
                                            'alt' => $user->nickname));
                $this->elementEnd('div');
154 155 156 157
                if (!empty($avatar->filename)) {
                    // TRANS: Button on avatar upload page to delete current avatar.
                    $this->submit('delete', _m('BUTTON','Delete'));
                }
158 159 160 161 162 163 164 165
                $this->elementEnd('li');
            }

            $this->elementStart('li', array ('id' => 'settings_attach'));
            $this->element('input', array('name' => 'MAX_FILE_SIZE',
                                          'type' => 'hidden',
                                          'id' => 'MAX_FILE_SIZE',
                                          'value' => ImageFile::maxFileSizeInt()));
166 167 168
            $this->element('input', array('name' => 'avatarfile',
                                          'type' => 'file',
                                          'id' => 'avatarfile'));
sarven's avatar
sarven committed
169
            $this->elementEnd('li');
170
            $this->elementEnd('ul');
171

172 173
            $this->elementStart('ul', 'form_actions');
            $this->elementStart('li');
Siebrand Mazeland's avatar
Siebrand Mazeland committed
174 175
                // TRANS: Button on avatar upload page to upload an avatar.
            $this->submit('upload', _m('BUTTON','Upload'));
sarven's avatar
sarven committed
176
            $this->elementEnd('li');
177
            $this->elementEnd('ul');
178
        }
179
        Event::handle('EndAvatarFormData', array($this));
sarven's avatar
sarven committed
180 181

        $this->elementEnd('fieldset');
182 183 184
        $this->elementEnd('form');
    }

185 186 187 188 189 190 191 192
    function showCropForm()
    {
        $user = common_current_user();

        $profile = $user->getProfile();

        if (!$profile) {
            common_log_db_error($user, 'SELECT', __FILE__);
193 194
            // TRANS: Error message displayed when referring to a user without a profile.
            $this->serverError(_('User has no profile.'));
195 196 197 198 199 200 201 202 203 204 205
            return;
        }

        $original = $profile->getOriginalAvatar();

        $this->elementStart('form', array('method' => 'post',
                                          'id' => 'form_settings_avatar',
                                          'class' => 'form_settings',
                                          'action' =>
                                          common_local_url('avatarsettings')));
        $this->elementStart('fieldset');
Siebrand Mazeland's avatar
Siebrand Mazeland committed
206
        // TRANS: Avatar upload page crop form legend.
207 208 209 210 211 212 213 214
        $this->element('legend', null, _('Avatar settings'));
        $this->hidden('token', common_session_token());

        $this->elementStart('ul', 'form_data');

        $this->elementStart('li',
                            array('id' => 'avatar_original',
                                  'class' => 'avatar_view'));
Siebrand Mazeland's avatar
Siebrand Mazeland committed
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 220 221 222 223 224 225 226 227
                                    'width' => $this->filedata['width'],
                                    'height' => $this->filedata['height'],
                                    'alt' => $user->nickname));
        $this->elementEnd('div');
        $this->elementEnd('li');

        $this->elementStart('li',
                            array('id' => 'avatar_preview',
                                  'class' => 'avatar_view'));
Siebrand Mazeland's avatar
Siebrand Mazeland committed
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 233 234 235 236 237 238 239 240 241 242
                                    'width' => AVATAR_PROFILE_SIZE,
                                    'height' => AVATAR_PROFILE_SIZE,
                                    'alt' => $user->nickname));
        $this->elementEnd('div');

        foreach (array('avatar_crop_x', 'avatar_crop_y',
                       'avatar_crop_w', 'avatar_crop_h') as $crop_info) {
            $this->element('input', array('name' => $crop_info,
                                          'type' => 'hidden',
                                          'id' => $crop_info));
        }
Siebrand Mazeland's avatar
Siebrand Mazeland committed
243 244 245

        // TRANS: Button on avatar upload crop form to confirm a selected crop as avatar.
        $this->submit('crop', _m('BUTTON','Crop'));
246 247 248 249 250 251 252

        $this->elementEnd('li');
        $this->elementEnd('ul');
        $this->elementEnd('fieldset');
        $this->elementEnd('form');
    }

253 254 255 256 257 258 259 260 261
    /**
     * Handle a post
     *
     * We mux on the button name to figure out what the user actually wanted.
     *
     * @return void
     */
    function handlePost()
    {
262 263 264 265 266 267 268
        // Workaround for PHP returning empty $_POST and $_FILES when POST
        // length > post_max_size in php.ini

        if (empty($_FILES)
            && empty($_POST)
            && ($_SERVER['CONTENT_LENGTH'] > 0)
        ) {
269 270 271 272 273
            // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
            // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
            $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.',
                      'The server was unable to handle that much POST data (%s bytes) due to its current configuration.',
                      intval($_SERVER['CONTENT_LENGTH']));
274 275 276 277
            $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
            return;
        }

278 279 280 281
        // CSRF protection

        $token = $this->trimmed('token');
        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 285 286
                               'Try again, please.'));
            return;
        }
Siebrand Mazeland's avatar
Siebrand Mazeland committed
287

288 289 290 291 292 293 294 295
        if (Event::handle('StartAvatarSaveForm', array($this))) {
            if ($this->arg('upload')) {
                $this->uploadAvatar();
                } else if ($this->arg('crop')) {
                    $this->cropAvatar();
                } else if ($this->arg('delete')) {
                    $this->deleteAvatar();
                } else {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
296
                    // TRANS: Unexpected validation error on avatar upload form.
297 298 299
                    $this->showForm(_('Unexpected form submission.'));
                }
            Event::handle('EndAvatarSaveForm', array($this));
300 301 302 303 304 305 306 307 308 309 310 311 312
        }
    }

    /**
     * Handle an image upload
     *
     * Does all the magic for handling an image upload, and crops the
     * image by default.
     *
     * @return void
     */
    function uploadAvatar()
    {
313 314 315 316
        try {
            $imagefile = ImageFile::fromUpload('avatarfile');
        } catch (Exception $e) {
            $this->showForm($e->getMessage());
317 318
            return;
        }
319
        if ($imagefile === null) {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
320
            // TRANS: Validation error on avatar upload form when no file was uploaded.
321 322 323
            $this->showForm(_('No file uploaded.'));
            return;
        }
324

325
        $cur = common_current_user();
326
        $type = $imagefile->preferredType();
327
        $filename = Avatar::filename($cur->id,
328
                                     image_type_to_extension($type),
329 330
                                     null,
                                     'tmp'.common_timestamp());
331

332
        $filepath = Avatar::path($filename);
333
        $imagefile->copyTo($filepath);
334 335 336 337 338

        $filedata = array('filename' => $filename,
                          'filepath' => $filepath,
                          'width' => $imagefile->width,
                          'height' => $imagefile->height,
339
                          'type' => $type);
340 341 342 343 344 345 346

        $_SESSION['FILEDATA'] = $filedata;

        $this->filedata = $filedata;

        $this->mode = 'crop';

347 348
        // TRANS: Avatar upload form instruction after uploading a file.
        $this->showForm(_('Pick a square area of the image to be your avatar.'),
349
                        true);
350 351 352 353 354 355 356 357 358
    }

    /**
     * Handle the results of jcrop.
     *
     * @return void
     */
    function cropAvatar()
    {
359 360 361
        $filedata = $_SESSION['FILEDATA'];

        if (!$filedata) {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
362
            // TRANS: Server error displayed if an avatar upload went wrong somehow server side.
363 364 365 366
            $this->serverError(_('Lost our file data.'));
            return;
        }

367 368 369
        $file_d = ($filedata['width'] > $filedata['height'])
                     ? $filedata['height'] : $filedata['width'];

370 371
        $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0;
        $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0;
372 373
        $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d;
        $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 380
        $imagefile = new ImageFile($user->id, $filedata['filepath']);
        $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h);
381

382 383
        if ($profile->setOriginal($filename)) {
            @unlink($filedata['filepath']);
384 385
            unset($_SESSION['FILEDATA']);
            $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 {
Siebrand Mazeland's avatar
Siebrand Mazeland committed
390
            // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason.
391 392 393
            $this->showForm(_('Failed updating avatar.'));
        }
    }
Siebrand Mazeland's avatar
Siebrand Mazeland committed
394

395 396 397 398 399 400 401 402 403
    /**
     * Get rid of the current avatar.
     *
     * @return void
     */
    function deleteAvatar()
    {
        $user = common_current_user();
        $profile = $user->getProfile();
Siebrand Mazeland's avatar
Siebrand Mazeland committed
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

Siebrand Mazeland's avatar
Siebrand Mazeland committed
414
        // TRANS: Success message for deleting a user avatar.
415 416
        $this->showForm(_('Avatar deleted.'), true);
    }
417 418 419 420 421 422 423 424 425 426

    /**
     * Add the jCrop stylesheet
     *
     * @return void
     */

    function showStylesheets()
    {
        parent::showStylesheets();
427
        $this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
428 429 430 431 432 433 434 435 436 437 438
    }

    /**
     * Add the jCrop scripts
     *
     * @return void
     */
    function showScripts()
    {
        parent::showScripts();

439
        if ($this->mode == 'crop') {
440 441
            $this->script('jcrop/jquery.Jcrop.min.js');
            $this->script('jcrop/jquery.Jcrop.go.js');
442
        }
443 444

        $this->autofocus('avatarfile');
445 446
    }
}