3 * @copyright Copyright (C) 2020, Friendica
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\Repository;
24 use Friendica\BaseModel;
25 use Friendica\BaseRepository;
26 use Friendica\Collection;
27 use Friendica\Core\L10n;
28 use Friendica\Database\Database;
29 use Friendica\Database\DBA;
31 use Friendica\Util\ACLFormatter;
32 use Friendica\Util\DateTimeFormat;
33 use Psr\Log\LoggerInterface;
35 class ProfileField extends BaseRepository
37 protected static $table_name = 'profile_field';
39 protected static $model_class = Model\ProfileField::class;
41 protected static $collection_class = Collection\ProfileFields::class;
43 /** @var PermissionSet */
44 private $permissionSet;
45 /** @var ACLFormatter */
46 private $aclFormatter;
50 public function __construct(Database $dba, LoggerInterface $logger, PermissionSet $permissionSet, ACLFormatter $aclFormatter, L10n $l10n)
52 parent::__construct($dba, $logger);
54 $this->permissionSet = $permissionSet;
55 $this->aclFormatter = $aclFormatter;
61 * @return Model\ProfileField
63 protected function create(array $data)
65 return new Model\ProfileField($this->dba, $this->logger, $this->permissionSet, $data);
69 * @param array $condition
70 * @return Model\ProfileField
71 * @throws \Friendica\Network\HTTPException\NotFoundException
73 public function selectFirst(array $condition)
75 return parent::selectFirst($condition);
79 * @param array $condition
80 * @param array $params
81 * @return Collection\ProfileFields
84 public function select(array $condition = [], array $params = [])
86 return parent::select($condition, $params);
90 * @param array $condition
91 * @param array $params
92 * @param int|null $max_id
93 * @param int|null $since_id
95 * @return Collection\ProfileFields
98 public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT)
100 return parent::selectByBoundaries($condition, $params, $max_id, $since_id, $limit);
104 * @param int $uid Field owner user Id
105 * @return Collection\ProfileFields
108 public function selectByUserId(int $uid)
110 return $this->select(
112 ['order' => ['order']]
117 * Retrieve all custom profile field a given contact is able to access to, including public profile fields.
119 * @param int $cid Private contact id, must be owned by $uid
120 * @param int $uid Field owner user id
121 * @return Collection\ProfileFields
124 public function selectByContactId(int $cid, int $uid)
126 $permissionSets = $this->permissionSet->selectByContactId($cid, $uid);
128 $psids = $permissionSets->column('id');
130 // Includes public custom fields
133 return $this->select(
134 ['uid' => $uid, 'psid' => $psids],
135 ['order' => ['order']]
140 * @param array $fields
141 * @return Model\ProfileField|bool
144 public function insert(array $fields)
146 $fields['created'] = DateTimeFormat::utcNow();
147 $fields['edited'] = DateTimeFormat::utcNow();
149 return parent::insert($fields);
153 * @param Model\ProfileField $model
157 public function update(BaseModel $model)
159 $model->edited = DateTimeFormat::utcNow();
161 return parent::update($model);
165 * @param int $uid User Id
166 * @param Collection\ProfileFields $profileFields Collection of existing profile fields
167 * @param array $profileFieldInputs Array of profile field form inputs indexed by profile field id
168 * @param array $profileFieldOrder List of profile field id in order
169 * @return Collection\ProfileFields
172 public function updateCollectionFromForm(int $uid, Collection\ProfileFields $profileFields, array $profileFieldInputs, array $profileFieldOrder)
174 // Returns an associative array of id => order values
175 $profileFieldOrder = array_flip($profileFieldOrder);
177 // Creation of the new field
178 if (!empty($profileFieldInputs['new']['label'])) {
179 $psid = $this->permissionSet->getIdFromACL(
181 $this->aclFormatter->toString($profileFieldInputs['new']['contact_allow'] ?? ''),
182 $this->aclFormatter->toString($profileFieldInputs['new']['group_allow'] ?? ''),
183 $this->aclFormatter->toString($profileFieldInputs['new']['contact_deny'] ?? ''),
184 $this->aclFormatter->toString($profileFieldInputs['new']['group_deny'] ?? '')
187 $newProfileField = $this->insert([
189 'label' => $profileFieldInputs['new']['label'],
190 'value' => $profileFieldInputs['new']['value'],
192 'order' => $profileFieldOrder['new'],
195 $profileFieldInputs[$newProfileField->id] = $profileFieldInputs['new'];
196 $profileFieldOrder[$newProfileField->id] = $profileFieldOrder['new'];
198 $profileFields[] = $newProfileField;
201 unset($profileFieldInputs['new']);
202 unset($profileFieldOrder['new']);
204 // Prunes profile field whose label has been emptied
205 $profileFields = $profileFields->filter(function (Model\ProfileField $profileField) use (&$profileFieldInputs, &$profileFieldOrder) {
206 $keepModel = !isset($profileFieldInputs[$profileField->id]) || !empty($profileFieldInputs[$profileField->id]['label']);
209 unset($profileFieldInputs[$profileField->id]);
210 unset($profileFieldOrder[$profileField->id]);
211 $this->delete($profileField);
217 // Regenerates the order values if items were deleted
218 $profileFieldOrder = array_flip(array_keys($profileFieldOrder));
220 // Update existing profile fields from form values
221 $profileFields = $profileFields->map(function (Model\ProfileField $profileField) use ($uid, &$profileFieldInputs, &$profileFieldOrder) {
222 if (isset($profileFieldInputs[$profileField->id]) && isset($profileFieldOrder[$profileField->id])) {
223 $psid = $this->permissionSet->getIdFromACL(
225 $this->aclFormatter->toString($profileFieldInputs[$profileField->id]['contact_allow'] ?? ''),
226 $this->aclFormatter->toString($profileFieldInputs[$profileField->id]['group_allow'] ?? ''),
227 $this->aclFormatter->toString($profileFieldInputs[$profileField->id]['contact_deny'] ?? ''),
228 $this->aclFormatter->toString($profileFieldInputs[$profileField->id]['group_deny'] ?? '')
231 $profileField->psid = $psid;
232 $profileField->label = $profileFieldInputs[$profileField->id]['label'];
233 $profileField->value = $profileFieldInputs[$profileField->id]['value'];
234 $profileField->order = $profileFieldOrder[$profileField->id];
236 unset($profileFieldInputs[$profileField->id]);
237 unset($profileFieldOrder[$profileField->id]);
240 return $profileField;
243 return $profileFields;
247 * Migrates a legacy profile to the new slimmer profile with extra custom fields.
248 * Multi profiles are converted to ACl-protected custom fields and deleted.
250 * @param array $profile Profile table row
253 public function migrateFromLegacyProfile(array $profile)
255 // Already processed, aborting
256 if ($profile['is-default'] === null) {
260 if (!$profile['is-default']) {
261 $contacts = Model\Contact::selectToArray(['id'], ['uid' => $profile['uid'], 'profile-id' => $profile['id']]);
262 if (!count($contacts)) {
263 // No contact visibility selected defaults to user-only permission
264 $contacts = Model\Contact::selectToArray(['id'], ['uid' => $profile['uid'], 'self' => true]);
267 $allow_cid = $this->aclFormatter->toString(array_column($contacts, 'id'));
270 $psid = $this->permissionSet->getIdFromACL($profile['uid'], $allow_cid ?? '');
275 'hometown' => $this->l10n->t('Hometown:'),
276 'marital' => $this->l10n->t('Marital Status:'),
277 'with' => $this->l10n->t('With:'),
278 'howlong' => $this->l10n->t('Since:'),
279 'sexual' => $this->l10n->t('Sexual Preference:'),
280 'politic' => $this->l10n->t('Political Views:'),
281 'religion' => $this->l10n->t('Religious Views:'),
282 'likes' => $this->l10n->t('Likes:'),
283 'dislikes' => $this->l10n->t('Dislikes:'),
284 'pdesc' => $this->l10n->t('Title/Description:'),
285 'summary' => $this->l10n->t('Summary'),
286 'music' => $this->l10n->t('Musical interests'),
287 'book' => $this->l10n->t('Books, literature'),
288 'tv' => $this->l10n->t('Television'),
289 'film' => $this->l10n->t('Film/dance/culture/entertainment'),
290 'interest' => $this->l10n->t('Hobbies/Interests'),
291 'romance' => $this->l10n->t('Love/romance'),
292 'work' => $this->l10n->t('Work/employment'),
293 'education' => $this->l10n->t('School/education'),
294 'contact' => $this->l10n->t('Contact information and Social Networks'),
297 foreach ($custom_fields as $field => $label) {
298 if (!empty($profile[$field]) && $profile[$field] > DBA::NULL_DATE && $profile[$field] > DBA::NULL_DATETIME) {
300 'uid' => $profile['uid'],
303 'label' => trim($label, ':'),
304 'value' => $profile[$field],
308 $profile[$field] = null;
311 if ($profile['is-default']) {
312 $profile['profile-name'] = null;
313 $profile['is-default'] = null;
314 $this->dba->update('profile', $profile, ['id' => $profile['id']]);
315 } elseif (!empty($profile['id'])) {
316 $this->dba->delete('profile', ['id' => $profile['id']]);