]> git.mxchange.org Git - friendica.git/blob - src/Module/Settings/Profile/Index.php
Merge pull request #13724 from Raroun/Fix-for-Issue-#13637---Photo-caption-prevents...
[friendica.git] / src / Module / Settings / Profile / Index.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 namespace Friendica\Module\Settings\Profile;
23
24 use Friendica\App;
25 use Friendica\Core\ACL;
26 use Friendica\Core\Hook;
27 use Friendica\Core\L10n;
28 use Friendica\Core\Protocol;
29 use Friendica\Core\Renderer;
30 use Friendica\Core\Session\Capability\IHandleUserSessions;
31 use Friendica\Core\Theme;
32 use Friendica\Database\DBA;
33 use Friendica\Model\Contact;
34 use Friendica\Model\Profile;
35 use Friendica\Module\Response;
36 use Friendica\Navigation\SystemMessages;
37 use Friendica\Profile\ProfileField;
38 use Friendica\Model\User;
39 use Friendica\Module\BaseSettings;
40 use Friendica\Module\Security\Login;
41 use Friendica\Network\HTTPException;
42 use Friendica\Security\PermissionSet;
43 use Friendica\Util\ACLFormatter;
44 use Friendica\Util\DateTimeFormat;
45 use Friendica\Util\Profiler;
46 use Friendica\Util\Temporal;
47 use Friendica\Core\Worker;
48 use Psr\Log\LoggerInterface;
49
50 class Index extends BaseSettings
51 {
52         /** @var ProfileField\Repository\ProfileField */
53         private $profileFieldRepo;
54         /** @var ProfileField\Factory\ProfileField */
55         private $profileFieldFactory;
56         /** @var SystemMessages */
57         private $systemMessages;
58         /** @var PermissionSet\Repository\PermissionSet */
59         private $permissionSetRepo;
60         /** @var PermissionSet\Factory\PermissionSet */
61         private $permissionSetFactory;
62         /** @var ACLFormatter */
63         private $aclFormatter;
64
65         public function __construct(ACLFormatter $aclFormatter, PermissionSet\Factory\PermissionSet $permissionSetFactory, PermissionSet\Repository\PermissionSet $permissionSetRepo, SystemMessages $systemMessages, ProfileField\Factory\ProfileField $profileFieldFactory, ProfileField\Repository\ProfileField $profileFieldRepo, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
66         {
67                 parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
68
69                 $this->profileFieldRepo     = $profileFieldRepo;
70                 $this->profileFieldFactory  = $profileFieldFactory;
71                 $this->systemMessages       = $systemMessages;
72                 $this->permissionSetRepo    = $permissionSetRepo;
73                 $this->permissionSetFactory = $permissionSetFactory;
74                 $this->aclFormatter         = $aclFormatter;
75         }
76
77         protected function post(array $request = [])
78         {
79                 if (!$this->session->getLocalUserId()) {
80                         return;
81                 }
82
83                 $profile = Profile::getByUID($this->session->getLocalUserId());
84                 if (!$profile) {
85                         return;
86                 }
87
88                 self::checkFormSecurityTokenRedirectOnError('/settings/profile', 'settings_profile');
89
90                 Hook::callAll('profile_post', $request);
91
92                 $dob = trim($request['dob'] ?? '');
93
94                 if ($dob && !in_array($dob, ['0000-00-00', DBA::NULL_DATE])) {
95                         $y = substr($dob, 0, 4);
96                         if ((!ctype_digit($y)) || ($y < 1900)) {
97                                 $ignore_year = true;
98                         } else {
99                                 $ignore_year = false;
100                         }
101
102                         if (strpos($dob, '0000-') === 0 || strpos($dob, '0001-') === 0) {
103                                 $ignore_year = true;
104                                 $dob = substr($dob, 5);
105                         }
106
107                         if ($ignore_year) {
108                                 $dob = '0000-' . DateTimeFormat::utc('1900-' . $dob, 'm-d');
109                         } else {
110                                 $dob = DateTimeFormat::utc($dob, 'Y-m-d');
111                         }
112                 }
113
114                 $username = trim($request['username'] ?? '');
115                 if (!$username) {
116                         $this->systemMessages->addNotice($this->t('Display Name is required.'));
117                         return;
118                 }
119
120                 $about        = trim($request['about']);
121                 $address      = trim($request['address']);
122                 $locality     = trim($request['locality']);
123                 $region       = trim($request['region']);
124                 $postal_code  = trim($request['postal_code']);
125                 $country_name = trim($request['country_name']);
126                 $pub_keywords = self::cleanKeywords(trim($request['pub_keywords']));
127                 $prv_keywords = self::cleanKeywords(trim($request['prv_keywords']));
128                 $xmpp         = trim($request['xmpp']);
129                 $matrix       = trim($request['matrix']);
130                 $homepage     = trim($request['homepage']);
131                 if ((strpos($homepage, 'http') !== 0) && (strlen($homepage))) {
132                         // neither http nor https in URL, add them
133                         $homepage = 'http://' . $homepage;
134                 }
135
136                 $profileFieldsNew = $this->getProfileFieldsFromInput(
137                         $this->session->getLocalUserId(),
138                         $request['profile_field'],
139                         $request['profile_field_order']
140                 );
141
142                 $this->profileFieldRepo->saveCollectionForUser($this->session->getLocalUserId(), $profileFieldsNew);
143
144                 User::update(['username' => $username], $this->session->getLocalUserId());
145
146                 $result = Profile::update(
147                         [
148                                 'about'        => $about,
149                                 'dob'          => $dob,
150                                 'address'      => $address,
151                                 'locality'     => $locality,
152                                 'region'       => $region,
153                                 'postal-code'  => $postal_code,
154                                 'country-name' => $country_name,
155                                 'xmpp'         => $xmpp,
156                                 'matrix'       => $matrix,
157                                 'homepage'     => $homepage,
158                                 'pub_keywords' => $pub_keywords,
159                                 'prv_keywords' => $prv_keywords,
160                         ],
161                         $this->session->getLocalUserId()
162                 );
163
164                 Worker::add(Worker::PRIORITY_MEDIUM, 'CheckRelMeProfileLink', $this->session->getLocalUserId());
165
166                 if (!$result) {
167                         $this->systemMessages->addNotice($this->t("Profile couldn't be updated."));
168                         return;
169                 }
170
171                 $this->baseUrl->redirect('settings/profile');
172         }
173
174         protected function content(array $request = []): string
175         {
176                 if (!$this->session->getLocalUserId()) {
177                         $this->systemMessages->addNotice($this->t('You must be logged in to use this module'));
178                         return Login::form();
179                 }
180
181                 parent::content();
182
183                 $o = '';
184
185                 $owner = User::getOwnerDataById($this->session->getLocalUserId());
186                 if (!$owner) {
187                         throw new HTTPException\NotFoundException();
188                 }
189
190                 $this->page->registerFooterScript('view/asset/es-jquery-sortable/source/js/jquery-sortable-min.js');
191                 $this->page->registerFooterScript(Theme::getPathForFile('js/module/settings/profile/index.js'));
192
193                 $custom_fields = [];
194
195                 $profileFields = $this->profileFieldRepo->selectByUserId($this->session->getLocalUserId());
196                 foreach ($profileFields as $profileField) {
197                         $defaultPermissions = $profileField->permissionSet->withAllowedContacts(
198                                 Contact::pruneUnavailable($profileField->permissionSet->allow_cid)
199                         );
200
201                         $custom_fields[] = [
202                                 'id'     => $profileField->id,
203                                 'legend' => $profileField->label,
204                                 'fields' => [
205                                         'label' => ['profile_field[' . $profileField->id . '][label]', $this->t('Label:'), $profileField->label],
206                                         'value' => ['profile_field[' . $profileField->id . '][value]', $this->t('Value:'), $profileField->value],
207                                         'acl'   => ACL::getFullSelectorHTML(
208                                                 $this->page,
209                                                 $this->session->getLocalUserId(),
210                                                 false,
211                                                 $defaultPermissions->toArray(),
212                                                 ['network' => Protocol::DFRN],
213                                                 'profile_field[' . $profileField->id . ']'
214                                         ),
215                                 ],
216
217                                 'permissions' => $this->t('Field Permissions'),
218                                 'permdesc'    => $this->t("(click to open/close)"),
219                         ];
220                 }
221
222                 $custom_fields[] = [
223                         'id'     => 'new',
224                         'legend' => $this->t('Add a new profile field'),
225                         'fields' => [
226                                 'label' => ['profile_field[new][label]', $this->t('Label:')],
227                                 'value' => ['profile_field[new][value]', $this->t('Value:')],
228                                 'acl'   => ACL::getFullSelectorHTML(
229                                         $this->page,
230                                         $this->session->getLocalUserId(),
231                                         false,
232                                         ['allow_cid' => []],
233                                         ['network' => Protocol::DFRN],
234                                         'profile_field[new]'
235                                 ),
236                         ],
237
238                         'permissions' => $this->t('Field Permissions'),
239                         'permdesc'    => $this->t("(click to open/close)"),
240                 ];
241
242                 $this->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/profile/index_head.tpl'));
243
244                 $personal_account = ($owner['account-type'] != User::ACCOUNT_TYPE_COMMUNITY);
245
246                 if ($owner['homepage_verified']) {
247                         $homepage_help_text = $this->t('The homepage is verified. A rel="me" link back to your Friendica profile page was found on the homepage.');
248                 } else {
249                         $homepage_help_text = $this->t('To verify your homepage, add a rel="me" link to it, pointing to your profile URL (%s).', $owner['url']);
250                 }
251
252                 $tpl = Renderer::getMarkupTemplate('settings/profile/index.tpl');
253                 $o .= Renderer::replaceMacros($tpl, [
254                         '$l10n' => [
255                                 'profile_action'            => $this->t('Profile Actions'),
256                                 'banner'                    => $this->t('Edit Profile Details'),
257                                 'submit'                    => $this->t('Submit'),
258                                 'profpic'                   => $this->t('Change Profile Photo'),
259                                 'viewprof'                  => $this->t('View Profile'),
260                                 'personal_section'          => $this->t('Personal'),
261                                 'picture_section'           => $this->t('Profile picture'),
262                                 'location_section'          => $this->t('Location'),
263                                 'miscellaneous_section'     => $this->t('Miscellaneous'),
264                                 'custom_fields_section'     => $this->t('Custom Profile Fields'),
265                                 'profile_photo'             => $this->t('Upload Profile Photo'),
266                                 'custom_fields_description' => $this->t('<p>Custom fields appear on <a href="%s">your profile page</a>.</p>
267                                 <p>You can use BBCodes in the field values.</p>
268                                 <p>Reorder by dragging the field title.</p>
269                                 <p>Empty the label field to remove a custom field.</p>
270                                 <p>Non-public fields can only be seen by the selected Friendica contacts or the Friendica contacts in the selected circles.</p>',
271                                         'profile/' . $owner['nickname'] . '/profile'
272                                 ),
273                         ],
274
275                         '$personal_account' => $personal_account,
276
277                         '$form_security_token'       => self::getFormSecurityToken('settings_profile'),
278                         '$form_security_token_photo' => self::getFormSecurityToken('settings_profile_photo'),
279
280                         '$profpiclink' => '/profile/' . $owner['nickname'] . '/photos',
281
282                         '$nickname'      => $owner['nickname'],
283                         '$username'      => ['username', $this->t('Display name:'), $owner['name']],
284                         '$about'         => ['about', $this->t('Description:'), $owner['about']],
285                         '$dob'           => Temporal::getDateofBirthField($owner['dob'], $owner['timezone']),
286                         '$address'       => ['address', $this->t('Street Address:'), $owner['address']],
287                         '$locality'      => ['locality', $this->t('Locality/City:'), $owner['locality']],
288                         '$region'        => ['region', $this->t('Region/State:'), $owner['region']],
289                         '$postal_code'   => ['postal_code', $this->t('Postal/Zip Code:'), $owner['postal-code']],
290                         '$country_name'  => ['country_name', $this->t('Country:'), $owner['country-name']],
291                         '$age'           => ((intval($owner['dob'])) ? '(' . $this->t('Age: ') . $this->tt('%d year old', '%d years old', Temporal::getAgeByTimezone($owner['dob'], $owner['timezone'])) . ')' : ''),
292                         '$xmpp'          => ['xmpp', $this->t('XMPP (Jabber) address:'), $owner['xmpp'], $this->t('The XMPP address will be published so that people can follow you there.')],
293                         '$matrix'        => ['matrix', $this->t('Matrix (Element) address:'), $owner['matrix'], $this->t('The Matrix address will be published so that people can follow you there.')],
294                         '$homepage'      => ['homepage', $this->t('Homepage URL:'), $owner['homepage'], $homepage_help_text],
295                         '$pub_keywords'  => ['pub_keywords', $this->t('Public Keywords:'), $owner['pub_keywords'], $this->t('(Used for suggesting potential friends, can be seen by others)')],
296                         '$prv_keywords'  => ['prv_keywords', $this->t('Private Keywords:'), $owner['prv_keywords'], $this->t('(Used for searching profiles, never shown to others)')],
297                         '$custom_fields' => $custom_fields,
298                 ]);
299
300                 $arr = ['profile' => $owner, 'entry' => $o];
301                 Hook::callAll('profile_edit', $arr);
302
303                 return $o;
304         }
305
306         private function getProfileFieldsFromInput(int $uid, array $profileFieldInputs, array $profileFieldOrder): ProfileField\Collection\ProfileFields
307         {
308                 $profileFields = new ProfileField\Collection\ProfileFields();
309
310                 // Returns an associative array of id => order values
311                 $profileFieldOrder = array_flip($profileFieldOrder);
312
313                 // Creation of the new field
314                 if (!empty($profileFieldInputs['new']['label'])) {
315                         $permissionSet = $this->permissionSetRepo->selectOrCreate($this->permissionSetFactory->createFromString(
316                                 $uid,
317                                 $this->aclFormatter->toString($profileFieldInputs['new']['contact_allow'] ?? ''),
318                                 $this->aclFormatter->toString($profileFieldInputs['new']['circle_allow'] ?? ''),
319                                 $this->aclFormatter->toString($profileFieldInputs['new']['contact_deny'] ?? ''),
320                                 $this->aclFormatter->toString($profileFieldInputs['new']['circle_deny'] ?? '')
321                         ));
322
323                         $profileFields->append($this->profileFieldFactory->createFromValues(
324                                 $uid,
325                                 $profileFieldOrder['new'],
326                                 $profileFieldInputs['new']['label'],
327                                 $profileFieldInputs['new']['value'],
328                                 $permissionSet
329                         ));
330                 }
331
332                 unset($profileFieldInputs['new']);
333                 unset($profileFieldOrder['new']);
334
335                 foreach ($profileFieldInputs as $id => $profileFieldInput) {
336                         $permissionSet = $this->permissionSetRepo->selectOrCreate($this->permissionSetFactory->createFromString(
337                                 $uid,
338                                 $this->aclFormatter->toString($profileFieldInput['contact_allow'] ?? ''),
339                                 $this->aclFormatter->toString($profileFieldInput['circle_allow'] ?? ''),
340                                 $this->aclFormatter->toString($profileFieldInput['contact_deny'] ?? ''),
341                                 $this->aclFormatter->toString($profileFieldInput['circle_deny'] ?? '')
342                         ));
343
344                         $profileFields->append($this->profileFieldFactory->createFromValues(
345                                 $uid,
346                                 $profileFieldOrder[$id],
347                                 $profileFieldInput['label'],
348                                 $profileFieldInput['value'],
349                                 $permissionSet
350                         ));
351                 }
352
353                 return $profileFields;
354         }
355
356         private static function cleanKeywords($keywords): string
357         {
358                 $keywords = str_replace(',', ' ', $keywords);
359                 $keywords = explode(' ', $keywords);
360
361                 $cleaned = [];
362                 foreach ($keywords as $keyword) {
363                         $keyword = trim($keyword);
364                         $keyword = trim($keyword, '#');
365                         if ($keyword != '') {
366                                 $cleaned[] = $keyword;
367                         }
368                 }
369
370                 return implode(', ', $cleaned);
371         }
372 }