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

Eric Helgeson's avatar
Eric Helgeson committed
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).
Eric Helgeson's avatar
Eric Helgeson committed
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).
Eric Helgeson's avatar
Eric Helgeson committed
147 148
                $this->element('h2', null, _("Preview"));
                $this->elementStart('div', array('id'=>'avatar_preview_view'));
149
                $this->element('img', array('src' => $avatar->url,
Eric Helgeson's avatar
Eric Helgeson committed
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'));
                }
Eric Helgeson's avatar
Eric Helgeson committed
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');
Eric Helgeson's avatar
Eric Helgeson committed
170
            $this->elementEnd('ul');
171

Eric Helgeson's avatar
Eric Helgeson committed
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');
Eric Helgeson's avatar
Eric Helgeson committed
177
            $this->elementEnd('ul');
178
        }
Eric Helgeson's avatar
Eric Helgeson committed
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

Eric Helgeson's avatar
Eric Helgeson committed
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.
Eric Helgeson's avatar
Eric Helgeson committed
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
    }
}