diff --git a/mod/photos.php b/mod/photos.php index c2fb825696..5b8d22b655 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -1236,7 +1236,7 @@ function photos_content(App $a) } else { $tools['edit'] = ['photos/' . $a->data['user']['nickname'] . '/image/' . $datum . '/edit', DI::l10n()->t('Edit photo')]; $tools['delete'] = ['photos/' . $a->data['user']['nickname'] . '/image/' . $datum . '/drop', DI::l10n()->t('Delete photo')]; - $tools['profile'] = ['profile_photo/use/'.$ph[0]['resource-id'], DI::l10n()->t('Use as profile photo')]; + $tools['profile'] = ['settings/profile/photo/crop/' . $ph[0]['resource-id'], DI::l10n()->t('Use as profile photo')]; } if ( diff --git a/mod/profile_photo.php b/mod/profile_photo.php deleted file mode 100644 index d34c78363c..0000000000 --- a/mod/profile_photo.php +++ /dev/null @@ -1,324 +0,0 @@ -user['nickname']); -} - -function profile_photo_post(App $a) -{ - if (!local_user()) { - notice(DI::l10n()->t('Permission denied.') . EOL); - return; - } - - BaseModule::checkFormSecurityTokenRedirectOnError('/profile_photo', 'profile_photo'); - - if (!empty($_POST['cropfinal']) && $_POST['cropfinal'] == 1) { - - // unless proven otherwise - $is_default_profile = 1; - - if ($_REQUEST['profile']) { - $r = q("select id, `is-default` from profile where id = %d and uid = %d limit 1", intval($_REQUEST['profile']), - intval(local_user()) - ); - - if (DBA::isResult($r) && (!intval($r[0]['is-default']))) { - $is_default_profile = 0; - } - } - - - - // phase 2 - we have finished cropping - - if ($a->argc != 2) { - notice(DI::l10n()->t('Image uploaded but image cropping failed.') . EOL); - return; - } - - $image_id = $a->argv[1]; - - if (substr($image_id, -2, 1) == '-') { - $scale = substr($image_id, -1, 1); - $image_id = substr($image_id, 0, -2); - } - - - $srcX = $_POST['xstart']; - $srcY = $_POST['ystart']; - $srcW = $_POST['xfinal'] - $srcX; - $srcH = $_POST['yfinal'] - $srcY; - - $base_image = Photo::selectFirst([], ['resource-id' => $image_id, 'uid' => local_user(), 'scale' => $scale]); - - $path = 'profile/' . $a->user['nickname']; - if (DBA::isResult($base_image)) { - - $Image = Photo::getImageForPhoto($base_image); - if ($Image->isValid()) { - $Image->crop(300, $srcX, $srcY, $srcW, $srcH); - - $r = Photo::store($Image, local_user(), 0, $base_image['resource-id'], $base_image['filename'], - DI::l10n()->t('Profile Photos'), 4, $is_default_profile); - - if ($r === false) { - notice(DI::l10n()->t('Image size reduction [%s] failed.', "300") . EOL); - } - - $Image->scaleDown(80); - - $r = Photo::store($Image, local_user(), 0, $base_image['resource-id'], $base_image['filename'], - DI::l10n()->t('Profile Photos'), 5, $is_default_profile); - - if ($r === false) { - notice(DI::l10n()->t('Image size reduction [%s] failed.', "80") . EOL); - } - - $Image->scaleDown(48); - - $r = Photo::store($Image, local_user(), 0, $base_image['resource-id'], $base_image['filename'], - DI::l10n()->t('Profile Photos'), 6, $is_default_profile); - - if ($r === false) { - notice(DI::l10n()->t('Image size reduction [%s] failed.', "48") . EOL); - } - - // If setting for the default profile, unset the profile photo flag from any other photos I own - - if ($is_default_profile) { - q("UPDATE `photo` SET `profile` = 0 WHERE `profile` = 1 AND `resource-id` != '%s' AND `uid` = %d", - DBA::escape($base_image['resource-id']), intval(local_user()) - ); - } else { - q("update profile set photo = '%s', thumb = '%s' where id = %d and uid = %d", - DBA::escape(DI::baseUrl() . '/photo/' . $base_image['resource-id'] . '-4.' . $Image->getExt()), - DBA::escape(DI::baseUrl() . '/photo/' . $base_image['resource-id'] . '-5.' . $Image->getExt()), - intval($_REQUEST['profile']), intval(local_user()) - ); - } - - Contact::updateSelfFromUserID(local_user(), true); - - info(DI::l10n()->t('Shift-reload the page or clear browser cache if the new photo does not display immediately.') . EOL); - // Update global directory in background - if ($path && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, "Directory", DI::baseUrl()->get() . '/' . $path); - } - - Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user()); - } else { - notice(DI::l10n()->t('Unable to process image') . EOL); - } - } - - DI::baseUrl()->redirect($path); - return; // NOTREACHED - } - - $src = $_FILES['userfile']['tmp_name']; - $filename = basename($_FILES['userfile']['name']); - $filesize = intval($_FILES['userfile']['size']); - $filetype = $_FILES['userfile']['type']; - if ($filetype == "") { - $filetype = Image::guessType($filename); - } - - $maximagesize = DI::config()->get('system', 'maximagesize'); - - if (($maximagesize) && ($filesize > $maximagesize)) { - notice(DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize)) . EOL); - @unlink($src); - return; - } - - $imagedata = @file_get_contents($src); - $ph = new Image($imagedata, $filetype); - - if (!$ph->isValid()) { - notice(DI::l10n()->t('Unable to process image.') . EOL); - @unlink($src); - return; - } - - $ph->orient($src); - @unlink($src); - - $imagecrop = profile_photo_crop_ui_head($ph); - DI::baseUrl()->redirect('profile_photo/use/' . $imagecrop['hash']); -} - -function profile_photo_content(App $a) -{ - - if (!local_user()) { - notice(DI::l10n()->t('Permission denied.') . EOL); - return; - } - - $newuser = false; - - if ($a->argc == 2 && $a->argv[1] === 'new') { - $newuser = true; - } - - $imagecrop = []; - - if (isset($a->argv[1]) && $a->argv[1] == 'use' && $a->argc >= 3) { - // BaseModule::checkFormSecurityTokenRedirectOnError('/profile_photo', 'profile_photo'); - - $resource_id = $a->argv[2]; - //die(":".local_user()); - - $r = Photo::selectToArray([], ["resource-id" => $resource_id, "uid" => local_user()], ["order" => ["scale" => false]]); - if (!DBA::isResult($r)) { - notice(DI::l10n()->t('Permission denied.') . EOL); - return; - } - - $havescale = false; - foreach ($r as $rr) { - if ($rr['scale'] == 5) { - $havescale = true; - } - } - - // set an already uloaded photo as profile photo - // if photo is in 'Profile Photos', change it in db - if (($r[0]['album'] == DI::l10n()->t('Profile Photos')) && ($havescale)) { - q("UPDATE `photo` SET `profile`=0 WHERE `profile`=1 AND `uid`=%d", intval(local_user())); - - q("UPDATE `photo` SET `profile`=1 WHERE `uid` = %d AND `resource-id` = '%s'", intval(local_user()), - DBA::escape($resource_id) - ); - - Contact::updateSelfFromUserID(local_user(), true); - - // Update global directory in background - $url = $_SESSION['my_url']; - if ($url && strlen(DI::config()->get('system', 'directory'))) { - Worker::add(PRIORITY_LOW, "Directory", $url); - } - - DI::baseUrl()->redirect('profile/' . $a->user['nickname']); - return; // NOTREACHED - } - $ph = Photo::getImageForPhoto($r[0]); - - $imagecrop = profile_photo_crop_ui_head($ph); - // go ahead as we have jus uploaded a new photo to crop - } - - $profiles = q("select `id`,`profile-name` as `name`,`is-default` as `default` from profile where uid = %d", - intval(local_user()) - ); - - if (empty($imagecrop)) { - $tpl = Renderer::getMarkupTemplate('profile_photo.tpl'); - - $o = Renderer::replaceMacros($tpl, - [ - '$user' => $a->user['nickname'], - '$lbl_upfile' => DI::l10n()->t('Upload File:'), - '$lbl_profiles' => DI::l10n()->t('Select a profile:'), - '$title' => DI::l10n()->t('Upload Profile Photo'), - '$submit' => DI::l10n()->t('Upload'), - '$profiles' => $profiles, - '$form_security_token' => BaseModule::getFormSecurityToken("profile_photo"), - '$select' => sprintf('%s %s', DI::l10n()->t('or'), - ($newuser) ? '' . DI::l10n()->t('skip this step') . '' : '' . DI::l10n()->t('select a photo from your photo albums') . '') - ]); - - return $o; - } else { - $filename = $imagecrop['hash'] . '-' . $imagecrop['resolution'] . '.' . $imagecrop['ext']; - $tpl = Renderer::getMarkupTemplate("cropbody.tpl"); - $o = Renderer::replaceMacros($tpl, - [ - '$filename' => $filename, - '$profile' => (isset($_REQUEST['profile']) ? intval($_REQUEST['profile']) : 0), - '$resource' => $imagecrop['hash'] . '-' . $imagecrop['resolution'], - '$image_url' => DI::baseUrl() . '/photo/' . $filename, - '$title' => DI::l10n()->t('Crop Image'), - '$desc' => DI::l10n()->t('Please adjust the image cropping for optimum viewing.'), - '$form_security_token' => BaseModule::getFormSecurityToken("profile_photo"), - '$done' => DI::l10n()->t('Done Editing') - ]); - return $o; - } -} - -function profile_photo_crop_ui_head(Image $image) -{ - $max_length = DI::config()->get('system', 'max_image_length'); - if (!$max_length) { - $max_length = MAX_IMAGE_LENGTH; - } - if ($max_length > 0) { - $image->scaleDown($max_length); - } - - $width = $image->getWidth(); - $height = $image->getHeight(); - - if ($width < 175 || $height < 175) { - $image->scaleUp(300); - $width = $image->getWidth(); - $height = $image->getHeight(); - } - - $hash = Photo::newResource(); - - - $smallest = 0; - $filename = ''; - - $r = Photo::store($image, local_user(), 0, $hash, $filename, DI::l10n()->t('Profile Photos'), 0); - - if ($r) { - info(DI::l10n()->t('Image uploaded successfully.') . EOL); - } else { - notice(DI::l10n()->t('Image upload failed.') . EOL); - } - - if ($width > 640 || $height > 640) { - $image->scaleDown(640); - $r = Photo::store($image, local_user(), 0, $hash, $filename, DI::l10n()->t('Profile Photos'), 1); - - if ($r === false) { - notice(DI::l10n()->t('Image size reduction [%s] failed.', "640") . EOL); - } else { - $smallest = 1; - } - } - - DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate("crophead.tpl"), []); - - $imagecrop = [ - 'hash' => $hash, - 'resolution' => $smallest, - 'ext' => $image->getExt(), - ]; - - return $imagecrop; -} diff --git a/src/App/Authentication.php b/src/App/Authentication.php index c0408a8111..2e1a823c71 100644 --- a/src/App/Authentication.php +++ b/src/App/Authentication.php @@ -373,7 +373,7 @@ class Authentication if ($user_record['login_date'] <= DBA::NULL_DATETIME) { info($this->l10n->t('Welcome %s', $user_record['username'])); info($this->l10n->t('Please upload a profile photo.')); - $this->baseUrl->redirect('profile_photo/new'); + $this->baseUrl->redirect('settings/profile/photo/new'); } else { info($this->l10n->t("Welcome back %s", $user_record['username'])); } diff --git a/src/Module/Settings/Profile/Photo/Crop.php b/src/Module/Settings/Profile/Photo/Crop.php new file mode 100644 index 0000000000..738e3fcb2a --- /dev/null +++ b/src/Module/Settings/Profile/Photo/Crop.php @@ -0,0 +1,203 @@ +user['nickname']; + + $base_image = Photo::selectFirst([], ['resource-id' => $resource_id, 'uid' => local_user(), 'scale' => $scale]); + if (DBA::isResult($base_image)) { + $Image = Photo::getImageForPhoto($base_image); + if ($Image->isValid()) { + // If setting for the default profile, unset the profile photo flag from any other photos I own + DBA::update('photo', ['profile' => 0], ['uid' => local_user()]); + + // Normalizing expected square crop parameters + $selectionW = $selectionH = min($selectionW, $selectionH); + + $imageIsSquare = $Image->getWidth() === $Image->getHeight(); + $selectionIsFullImage = $selectionX === 0 && $selectionY === 0 && $selectionW === $Image->getWidth() && $selectionH === $Image->getHeight(); + + // Bypassed UI with a rectangle image, we force a square cropped image + if (!$imageIsSquare && $action == 'skip') { + $selectionX = $selectionY = 0; + $selectionW = $selectionH = min($Image->getWidth(), $Image->getHeight()); + $action = 'crop'; + } + + // Selective crop if it was asked and the selection isn't the full image + if ($action == 'crop' + && !($imageIsSquare && !$selectionIsFullImage) + ) { + $Image->crop(300, $selectionX, $selectionY, $selectionW, $selectionH); + $resource_id = Photo::newResource(); + } else { + $Image->scaleDown(300); + } + + $r = Photo::store( + $Image, + local_user(), + 0, + $resource_id, + $base_image['filename'], + DI::l10n()->t('Profile Photos'), + 4, + 1 + ); + if ($r === false) { + notice(DI::l10n()->t('Image size reduction [%s] failed.', '300')); + } + + $Image->scaleDown(80); + + $r = Photo::store( + $Image, + local_user(), + 0, + $resource_id, + $base_image['filename'], + DI::l10n()->t('Profile Photos'), + 5, + 1 + ); + if ($r === false) { + notice(DI::l10n()->t('Image size reduction [%s] failed.', '80')); + } + + $Image->scaleDown(48); + + $r = Photo::store( + $Image, + local_user(), + 0, + $resource_id, + $base_image['filename'], + DI::l10n()->t('Profile Photos'), + 6, + 1 + ); + if ($r === false) { + notice(DI::l10n()->t('Image size reduction [%s] failed.', '48')); + } + + Contact::updateSelfFromUserID(local_user(), true); + + info(DI::l10n()->t('Shift-reload the page or clear browser cache if the new photo does not display immediately.')); + // Update global directory in background + if ($path && strlen(DI::config()->get('system', 'directory'))) { + Worker::add(PRIORITY_LOW, 'Directory', DI::baseUrl()->get() . '/' . $path); + } + + Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user()); + } else { + notice(DI::l10n()->t('Unable to process image')); + } + } + + DI::baseUrl()->redirect($path); + } + + public static function content(array $parameters = []) + { + if (!Session::isAuthenticated()) { + throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.')); + } + + parent::content(); + + $resource_id = $parameters['guid']; + + $photos = Photo::selectToArray([], ['resource-id' => $resource_id, 'uid' => local_user()], ['order' => ['scale' => false]]); + if (!DBA::isResult($photos)) { + throw new HTTPException\NotFoundException(DI::l10n()->t('Photo not found.')); + } + + $havescale = false; + $smallest = 0; + foreach ($photos as $photo) { + $smallest = $photo['scale'] == 1 ? 1 : $smallest; + $havescale = $havescale || $photo['scale'] == 5; + } + + // set an already uloaded photo as profile photo + // if photo is in 'Profile Photos', change it in db + if ($photos[0]['album'] == DI::l10n()->t('Profile Photos') && $havescale) { + Photo::update(['profile' => false], ['uid' => local_user()]); + + Photo::update(['profile' => true], ['resource-id' => $resource_id, 'uid' => local_user()]); + + Contact::updateSelfFromUserID(local_user(), true); + + // Update global directory in background + if (Session::get('my_url') && strlen(DI::config()->get('system', 'directory'))) { + Worker::add(PRIORITY_LOW, 'Directory', Session::get('my_url')); + } + + notice(DI::l10n()->t('Profile picture successfully updated.')); + + DI::baseUrl()->redirect('profile/' . DI::app()->user['nickname']); + } + + $Image = Photo::getImageForPhoto($photos[0]); + + $imagecrop = [ + 'resource-id' => $resource_id, + 'scale' => $smallest, + 'ext' => $Image->getExt(), + ]; + + $isSquare = $Image->getWidth() === $Image->getHeight(); + + DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/profile/photo/crop_head.tpl'), []); + + $filename = $imagecrop['resource-id'] . '-' . $imagecrop['scale'] . '.' . $imagecrop['ext']; + $tpl = Renderer::getMarkupTemplate('settings/profile/photo/crop.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$filename' => $filename, + '$resource' => $imagecrop['resource-id'] . '-' . $imagecrop['scale'], + '$image_url' => DI::baseUrl() . '/photo/' . $filename, + '$title' => DI::l10n()->t('Crop Image'), + '$desc' => DI::l10n()->t('Please adjust the image cropping for optimum viewing.'), + '$form_security_token' => self::getFormSecurityToken('settings_profile_photo_crop'), + '$skip' => $isSquare ? DI::l10n()->t('Use Image As Is') : '', + '$crop' => DI::l10n()->t('Crop Image'), + ]); + + return $o; + } +} diff --git a/src/Module/Settings/Profile/Photo/Index.php b/src/Module/Settings/Profile/Photo/Index.php new file mode 100644 index 0000000000..9cb5644b2a --- /dev/null +++ b/src/Module/Settings/Profile/Photo/Index.php @@ -0,0 +1,129 @@ +t('Missing uploaded image.')); + return; + } + + $src = $_FILES['userfile']['tmp_name']; + $filename = basename($_FILES['userfile']['name']); + $filesize = intval($_FILES['userfile']['size']); + $filetype = $_FILES['userfile']['type']; + if ($filetype == '') { + $filetype = Images::guessType($filename); + } + + $maximagesize = DI::config()->get('system', 'maximagesize', 0); + + if ($maximagesize && $filesize > $maximagesize) { + notice(DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize))); + @unlink($src); + return; + } + + $imagedata = @file_get_contents($src); + $Image = new Image($imagedata, $filetype); + + if (!$Image->isValid()) { + notice(DI::l10n()->t('Unable to process image.')); + @unlink($src); + return; + } + + $Image->orient($src); + @unlink($src); + + $max_length = DI::config()->get('system', 'max_image_length', 0); + if ($max_length > 0) { + $Image->scaleDown($max_length); + } + + $width = $Image->getWidth(); + $height = $Image->getHeight(); + + if ($width < 175 || $height < 175) { + $Image->scaleUp(300); + $width = $Image->getWidth(); + $height = $Image->getHeight(); + } + + $resource_id = Photo::newResource(); + + $filename = ''; + + if (Photo::store($Image, local_user(), 0, $resource_id, $filename, DI::l10n()->t('Profile Photos'), 0)) { + info(DI::l10n()->t('Image uploaded successfully.')); + } else { + notice(DI::l10n()->t('Image upload failed.')); + } + + if ($width > 640 || $height > 640) { + $Image->scaleDown(640); + if (!Photo::store($Image, local_user(), 0, $resource_id, $filename, DI::l10n()->t('Profile Photos'), 1)) { + notice(DI::l10n()->t('Image size reduction [%s] failed.', '640')); + } + } + + DI::baseUrl()->redirect('settings/profile/photo/crop/' . $resource_id); + } + + public static function content(array $parameters = []) + { + if (!Session::isAuthenticated()) { + throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.')); + } + + parent::content(); + + /** @var Arguments $args */ + $args = DI::args(); + + $newuser = $args->get($args->getArgc() - 1) === 'new'; + + $contact = Contact::selectFirst(['avatar'], ['uid' => local_user(), 'self' => true]); + + $tpl = Renderer::getMarkupTemplate('settings/profile/photo/index.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$title' => DI::l10n()->t('Profile Picture Settings'), + '$current_picture' => DI::l10n()->t('Current Profile Picture'), + '$upload_picture' => DI::l10n()->t('Upload Profile Picture'), + '$lbl_upfile' => DI::l10n()->t('Upload Picture:'), + '$submit' => DI::l10n()->t('Upload'), + '$avatar' => $contact['avatar'], + '$form_security_token' => self::getFormSecurityToken('settings_profile_photo'), + '$select' => sprintf('%s %s', + DI::l10n()->t('or'), + ($newuser) ? + '' . DI::l10n()->t('skip this step') . '' + : '' + . DI::l10n()->t('select a photo from your photo albums') . '' + ), + ]); + + return $o; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 115b8f5f6d..05f2599561 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -228,6 +228,10 @@ return [ '/verify' => [Module\Settings\TwoFactor\Verify::class, [R::GET, R::POST]], ], '/delegation[/{action}/{user_id}]' => [Module\Settings\Delegation::class, [R::GET, R::POST]], + '/profile' => [ + '/photo[/new]' => [Module\Settings\Profile\Photo\Index::class, [R::GET, R::POST]], + '/photo/crop/{guid}' => [Module\Settings\Profile\Photo\Crop::class, [R::GET, R::POST]], + ], '/userexport[/{action}]' => [Module\Settings\UserExport::class, [R::GET, R::POST]], ], diff --git a/view/templates/cropbody.tpl b/view/templates/cropbody.tpl deleted file mode 100644 index 8378e5067e..0000000000 --- a/view/templates/cropbody.tpl +++ /dev/null @@ -1,54 +0,0 @@ -
- {{$desc nofilter}} -
- -+ {{$desc nofilter}} +
+ ++ {{$select nofilter}} +
+