3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Module\Settings\Profile\Photo;
24 use Friendica\Core\Renderer;
25 use Friendica\Database\DBA;
27 use Friendica\Model\Contact;
28 use Friendica\Model\Photo;
29 use Friendica\Model\Profile;
30 use Friendica\Module\BaseSettings;
31 use Friendica\Network\HTTPException;
33 class Crop extends BaseSettings
35 protected function post(array $request = [])
37 if (!DI::userSession()->isAuthenticated()) {
41 $photo_prefix = $this->parameters['guid'];
42 $resource_id = $photo_prefix;
44 if (substr($photo_prefix, -2, 1) == '-') {
45 list($resource_id, $scale) = explode('-', $photo_prefix);
48 self::checkFormSecurityTokenRedirectOnError('settings/profile/photo/crop/' . $photo_prefix, 'settings_profile_photo_crop');
50 $action = $_POST['action'] ?? 'crop';
52 // Image selection origin is top left
53 $selectionX = intval($_POST['xstart'] ?? 0);
54 $selectionY = intval($_POST['ystart'] ?? 0);
55 $selectionW = intval($_POST['width'] ?? 0);
56 $selectionH = intval($_POST['height'] ?? 0);
58 $path = 'profile/' . DI::app()->getLoggedInUserNickname();
60 $base_image = Photo::selectFirst([], ['resource-id' => $resource_id, 'uid' => DI::userSession()->getLocalUserId(), 'scale' => $scale]);
61 if (DBA::isResult($base_image)) {
62 $Image = Photo::getImageForPhoto($base_image);
64 throw new HTTPException\InternalServerErrorException();
67 if ($Image->isValid()) {
68 // If setting for the default profile, unset the profile photo flag from any other photos I own
69 DBA::update('photo', ['profile' => 0], ['uid' => DI::userSession()->getLocalUserId()]);
71 // Normalizing expected square crop parameters
72 $selectionW = $selectionH = min($selectionW, $selectionH);
74 $imageIsSquare = $Image->getWidth() === $Image->getHeight();
75 $selectionIsFullImage = $selectionX === 0 && $selectionY === 0 && $selectionW === $Image->getWidth() && $selectionH === $Image->getHeight();
77 // Bypassed UI with a rectangle image, we force a square cropped image
78 if (!$imageIsSquare && $action == 'skip') {
79 $selectionX = $selectionY = 0;
80 $selectionW = $selectionH = min($Image->getWidth(), $Image->getHeight());
84 // Selective crop if it was asked and the selection isn't the full image
86 && !($imageIsSquare && !$selectionIsFullImage)
88 $Image->crop(300, $selectionX, $selectionY, $selectionW, $selectionH);
89 $resource_id = Photo::newResource();
91 $Image->scaleDown(300);
94 $condition = ['resource-id' => $resource_id, 'uid' => DI::userSession()->getLocalUserId(), 'contact-id' => 0];
98 DI::userSession()->getLocalUserId(),
101 $base_image['filename'],
102 DI::l10n()->t(Photo::PROFILE_PHOTOS),
107 DI::sysmsg()->addNotice(DI::l10n()->t('Image size reduction [%s] failed.', '300'));
109 Photo::update(['profile' => true], array_merge($condition, ['scale' => 4]));
112 $Image->scaleDown(80);
116 DI::userSession()->getLocalUserId(),
119 $base_image['filename'],
120 DI::l10n()->t(Photo::PROFILE_PHOTOS),
125 DI::sysmsg()->addNotice(DI::l10n()->t('Image size reduction [%s] failed.', '80'));
127 Photo::update(['profile' => true], array_merge($condition, ['scale' => 5]));
130 $Image->scaleDown(48);
134 DI::userSession()->getLocalUserId(),
137 $base_image['filename'],
138 DI::l10n()->t(Photo::PROFILE_PHOTOS),
143 DI::sysmsg()->addNotice(DI::l10n()->t('Image size reduction [%s] failed.', '48'));
145 Photo::update(['profile' => true], array_merge($condition, ['scale' => 6]));
148 Contact::updateSelfFromUserID(DI::userSession()->getLocalUserId(), true);
150 DI::sysmsg()->addInfo(DI::l10n()->t('Shift-reload the page or clear browser cache if the new photo does not display immediately.'));
152 // Update global directory in background
153 Profile::publishUpdate(DI::userSession()->getLocalUserId());
155 DI::sysmsg()->addNotice(DI::l10n()->t('Unable to process image'));
159 DI::baseUrl()->redirect($path);
162 protected function content(array $request = []): string
164 if (!DI::userSession()->isAuthenticated()) {
165 throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
170 $resource_id = $this->parameters['guid'];
172 $photos = Photo::selectToArray([], ['resource-id' => $resource_id, 'uid' => DI::userSession()->getLocalUserId()], ['order' => ['scale' => false]]);
173 if (!DBA::isResult($photos)) {
174 throw new HTTPException\NotFoundException(DI::l10n()->t('Photo not found.'));
179 foreach ($photos as $photo) {
180 $smallest = $photo['scale'] == 1 ? 1 : $smallest;
181 $havescale = $havescale || $photo['scale'] == 5;
184 // set an already uploaded photo as profile photo
185 // if photo is in 'Profile Photos', change it in db
186 if ($photos[0]['photo-type'] == Photo::USER_AVATAR && $havescale) {
187 Photo::update(['profile' => false], ['uid' => DI::userSession()->getLocalUserId()]);
189 Photo::update(['profile' => true], ['resource-id' => $resource_id, 'uid' => DI::userSession()->getLocalUserId()]);
191 Contact::updateSelfFromUserID(DI::userSession()->getLocalUserId(), true);
193 // Update global directory in background
194 Profile::publishUpdate(DI::userSession()->getLocalUserId());
196 DI::sysmsg()->addInfo(DI::l10n()->t('Profile picture successfully updated.'));
198 DI::baseUrl()->redirect('profile/' . DI::app()->getLoggedInUserNickname());
201 $Image = Photo::getImageForPhoto($photos[0]);
203 throw new HTTPException\InternalServerErrorException();
207 'resource-id' => $resource_id,
208 'scale' => $smallest,
209 'ext' => $Image->getExt(),
212 $isSquare = $Image->getWidth() === $Image->getHeight();
214 DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/profile/photo/crop_head.tpl'), []);
216 $filename = $imagecrop['resource-id'] . '-' . $imagecrop['scale'] . '.' . $imagecrop['ext'];
217 $tpl = Renderer::getMarkupTemplate('settings/profile/photo/crop.tpl');
218 $o = Renderer::replaceMacros($tpl, [
219 '$filename' => $filename,
220 '$resource' => $imagecrop['resource-id'] . '-' . $imagecrop['scale'],
221 '$image_url' => DI::baseUrl() . '/photo/' . $filename,
222 '$title' => DI::l10n()->t('Crop Image'),
223 '$desc' => DI::l10n()->t('Please adjust the image cropping for optimum viewing.'),
224 '$form_security_token' => self::getFormSecurityToken('settings_profile_photo_crop'),
225 '$skip' => $isSquare ? DI::l10n()->t('Use Image As Is') : '',
226 '$crop' => DI::l10n()->t('Crop Image'),