]> git.mxchange.org Git - friendica.git/blob - src/Model/Group.php
f6700a4566ffcc4ddc1ea61e5a18ad127ba3a18f
[friendica.git] / src / Model / Group.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, 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\Model;
23
24 use Friendica\BaseModule;
25 use Friendica\Core\Logger;
26 use Friendica\Core\Protocol;
27 use Friendica\Core\Renderer;
28 use Friendica\Database\Database;
29 use Friendica\Database\DBA;
30 use Friendica\DI;
31 use Friendica\Network\HTTPException;
32
33 /**
34  * functions for interacting with the group database table
35  */
36 class Group
37 {
38         const FOLLOWERS = '~';
39         const MUTUALS = '&';
40
41         public static function getByUserId($uid, $includesDeleted = false)
42         {
43                 $conditions = ['uid' => $uid];
44
45                 if (!$includesDeleted) {
46                         $conditions['deleted'] = false;
47                 }
48
49                 return DBA::selectToArray('group', [], $conditions);
50         }
51
52         /**
53          * @param int $group_id
54          * @return bool
55          * @throws \Exception
56          */
57         public static function exists($group_id, $uid = null)
58         {
59                 $condition = ['id' => $group_id, 'deleted' => false];
60
61                 if (isset($uid)) {
62                         $condition = [
63                                 'uid' => $uid
64                         ];
65                 }
66
67                 return DBA::exists('group', $condition);
68         }
69
70         /**
71          * Create a new contact group
72          *
73          * Note: If we found a deleted group with the same name, we restore it
74          *
75          * @param int    $uid
76          * @param string $name
77          * @return boolean
78          * @throws \Exception
79          */
80         public static function create($uid, $name)
81         {
82                 $return = false;
83                 if (!empty($uid) && !empty($name)) {
84                         $gid = self::getIdByName($uid, $name); // check for dupes
85                         if ($gid !== false) {
86                                 // This could be a problem.
87                                 // Let's assume we've just created a group which we once deleted
88                                 // all the old members are gone, but the group remains so we don't break any security
89                                 // access lists. What we're doing here is reviving the dead group, but old content which
90                                 // was restricted to this group may now be seen by the new group members.
91                                 $group = DBA::selectFirst('group', ['deleted'], ['id' => $gid]);
92                                 if (DBA::isResult($group) && $group['deleted']) {
93                                         DBA::update('group', ['deleted' => 0], ['id' => $gid]);
94                                         notice(DI::l10n()->t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.'));
95                                 }
96                                 return true;
97                         }
98
99                         $return = DBA::insert('group', ['uid' => $uid, 'name' => $name]);
100                         if ($return) {
101                                 $return = DBA::lastInsertId();
102                         }
103                 }
104                 return $return;
105         }
106
107         /**
108          * Update group information.
109          *
110          * @param int    $id   Group ID
111          * @param string $name Group name
112          *
113          * @return bool Was the update successful?
114          * @throws \Exception
115          */
116         public static function update($id, $name)
117         {
118                 return DBA::update('group', ['name' => $name], ['id' => $id]);
119         }
120
121         /**
122          * Get a list of group ids a contact belongs to
123          *
124          * @param int $cid
125          * @return array
126          * @throws \Exception
127          */
128         public static function getIdsByContactId($cid)
129         {
130                 $return = [];
131
132                 $stmt = DBA::select('group_member', ['gid'], ['contact-id' => $cid]);
133                 while ($group = DBA::fetch($stmt)) {
134                         $return[] = $group['gid'];
135                 }
136                 DBA::close($stmt);
137
138                 // Meta-groups
139                 $contact = Contact::getById($cid, ['rel']);
140                 if ($contact['rel'] == Contact::FOLLOWER || $contact['rel'] == Contact::FRIEND) {
141                         $return[] = self::FOLLOWERS;
142                 }
143
144                 if ($contact['rel'] == Contact::FRIEND) {
145                         $return[] = self::MUTUALS;
146                 }
147
148                 return $return;
149         }
150
151         /**
152          * count unread group items
153          *
154          * Count unread items of each groups of the local user
155          *
156          * @return array
157          *    'id' => group id
158          *    'name' => group name
159          *    'count' => counted unseen group items
160          * @throws \Exception
161          */
162         public static function countUnseen()
163         {
164                 $stmt = DBA::p("SELECT `group`.`id`, `group`.`name`,
165                                 (SELECT COUNT(*) FROM `post-user`
166                                         WHERE `uid` = ?
167                                         AND `unseen`
168                                         AND `contact-id` IN
169                                                 (SELECT `contact-id`
170                                                 FROM `group_member`
171                                                 WHERE `group_member`.`gid` = `group`.`id`)
172                                         ) AS `count`
173                                 FROM `group`
174                                 WHERE `group`.`uid` = ?;",
175                         local_user(),
176                         local_user()
177                 );
178
179                 return DBA::toArray($stmt);
180         }
181
182         /**
183          * Get the group id for a user/name couple
184          *
185          * Returns false if no group has been found.
186          *
187          * @param int    $uid
188          * @param string $name
189          * @return int|boolean
190          * @throws \Exception
191          */
192         public static function getIdByName($uid, $name)
193         {
194                 if (!$uid || !strlen($name)) {
195                         return false;
196                 }
197
198                 $group = DBA::selectFirst('group', ['id'], ['uid' => $uid, 'name' => $name]);
199                 if (DBA::isResult($group)) {
200                         return $group['id'];
201                 }
202
203                 return false;
204         }
205
206         /**
207          * Mark a group as deleted
208          *
209          * @param int $gid
210          * @return boolean
211          * @throws \Exception
212          */
213         public static function remove($gid)
214         {
215                 if (!$gid) {
216                         return false;
217                 }
218
219                 $group = DBA::selectFirst('group', ['uid'], ['id' => $gid]);
220                 if (!DBA::isResult($group)) {
221                         return false;
222                 }
223
224                 // remove group from default posting lists
225                 $user = DBA::selectFirst('user', ['def_gid', 'allow_gid', 'deny_gid'], ['uid' => $group['uid']]);
226                 if (DBA::isResult($user)) {
227                         $change = false;
228
229                         if ($user['def_gid'] == $gid) {
230                                 $user['def_gid'] = 0;
231                                 $change = true;
232                         }
233                         if (strpos($user['allow_gid'], '<' . $gid . '>') !== false) {
234                                 $user['allow_gid'] = str_replace('<' . $gid . '>', '', $user['allow_gid']);
235                                 $change = true;
236                         }
237                         if (strpos($user['deny_gid'], '<' . $gid . '>') !== false) {
238                                 $user['deny_gid'] = str_replace('<' . $gid . '>', '', $user['deny_gid']);
239                                 $change = true;
240                         }
241
242                         if ($change) {
243                                 DBA::update('user', $user, ['uid' => $group['uid']]);
244                         }
245                 }
246
247                 // remove all members
248                 DBA::delete('group_member', ['gid' => $gid]);
249
250                 // remove group
251                 $return = DBA::update('group', ['deleted' => 1], ['id' => $gid]);
252
253                 return $return;
254         }
255
256         /**
257          * Adds a contact to a group
258          *
259          * @param int $gid
260          * @param int $cid
261          * @return boolean
262          * @throws \Exception
263          */
264         public static function addMember(int $gid, int $cid): bool
265         {
266                 if (!$gid || !$cid) {
267                         return false;
268                 }
269
270                 // @TODO Backward compatibility with user contacts, remove by version 2022.03
271                 $group = DBA::selectFirst('group', ['uid'], ['id' => $gid]);
272                 if (empty($group)) {
273                         throw new HTTPException\NotFoundException('Group not found.');
274                 }
275
276                 $cdata = Contact::getPublicAndUserContactID($cid, $group['uid']);
277                 if (empty($cdata['user'])) {
278                         throw new HTTPException\NotFoundException('Invalid contact.');
279                 }
280
281                 return DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
282         }
283
284         /**
285          * Removes a contact from a group
286          *
287          * @param int $gid
288          * @param int $cid
289          * @return boolean
290          * @throws \Exception
291          */
292         public static function removeMember(int $gid, int $cid): bool
293         {
294                 if (!$gid || !$cid) {
295                         return false;
296                 }
297
298                 // @TODO Backward compatibility with user contacts, remove by version 2022.03
299                 $group = DBA::selectFirst('group', ['uid'], ['id' => $gid]);
300                 if (empty($group)) {
301                         throw new HTTPException\NotFoundException('Group not found.');
302                 }
303
304                 $cdata = Contact::getPublicAndUserContactID($cid, $group['uid']);
305                 if (empty($cdata['user'])) {
306                         throw new HTTPException\NotFoundException('Invalid contact.');
307                 }
308
309                 return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $cid]);
310         }
311
312         /**
313          * Returns the combined list of contact ids from a group id list
314          *
315          * @param int     $uid
316          * @param array   $group_ids
317          * @param boolean $check_dead
318          * @return array
319          * @throws \Exception
320          */
321         public static function expand($uid, array $group_ids, $check_dead = false)
322         {
323                 if (!is_array($group_ids) || !count($group_ids)) {
324                         return [];
325                 }
326
327                 $return = [];
328                 $pubmail = false;
329                 $networks = Protocol::SUPPORT_PRIVATE;
330
331                 $mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', $uid]);
332                 if (DBA::isResult($mailacct)) {
333                         $pubmail = $mailacct['pubmail'];
334                 }
335
336                 if (!$pubmail) {
337                         $networks = array_diff($networks, [Protocol::MAIL]);
338                 }
339
340                 $key = array_search(self::FOLLOWERS, $group_ids);
341                 if ($key !== false) {
342                         $followers = Contact::selectToArray(['id'], [
343                                 'uid' => $uid,
344                                 'rel' => [Contact::FOLLOWER, Contact::FRIEND],
345                                 'network' => $networks,
346                                 'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON],
347                                 'archive' => false,
348                                 'pending' => false,
349                                 'blocked' => false,
350                         ]);
351
352                         foreach ($followers as $follower) {
353                                 $return[] = $follower['id'];
354                         }
355
356                         unset($group_ids[$key]);
357                 }
358
359                 $key = array_search(self::MUTUALS, $group_ids);
360                 if ($key !== false) {
361                         $mutuals = Contact::selectToArray(['id'], [
362                                 'uid' => $uid,
363                                 'rel' => [Contact::FRIEND],
364                                 'network' => $networks,
365                                 'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON],
366                                 'archive' => false,
367                                 'pending' => false,
368                                 'blocked' => false,
369                         ]);
370
371                         foreach ($mutuals as $mutual) {
372                                 $return[] = $mutual['id'];
373                         }
374
375                         unset($group_ids[$key]);
376                 }
377
378                 $stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]);
379                 while ($group_member = DBA::fetch($stmt)) {
380                         $return[] = $group_member['contact-id'];
381                 }
382                 DBA::close($stmt);
383
384                 if ($check_dead) {
385                         $return = Contact::pruneUnavailable($return);
386                 }
387
388                 return $return;
389         }
390
391         /**
392          * Returns a templated group selection list
393          *
394          * @param int    $uid
395          * @param int    $gid   An optional pre-selected group
396          * @param string $label An optional label of the list
397          * @return string
398          * @throws \Exception
399          */
400         public static function displayGroupSelection($uid, $gid = 0, $label = '')
401         {
402                 $display_groups = [
403                         [
404                                 'name' => '',
405                                 'id' => '0',
406                                 'selected' => ''
407                         ]
408                 ];
409
410                 $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
411                 while ($group = DBA::fetch($stmt)) {
412                         $display_groups[] = [
413                                 'name' => $group['name'],
414                                 'id' => $group['id'],
415                                 'selected' => $gid == $group['id'] ? 'true' : ''
416                         ];
417                 }
418                 DBA::close($stmt);
419
420                 Logger::info('Got groups', $display_groups);
421
422                 if ($label == '') {
423                         $label = DI::l10n()->t('Default privacy group for new contacts');
424                 }
425
426                 $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('group_selection.tpl'), [
427                         '$label' => $label,
428                         '$groups' => $display_groups
429                 ]);
430                 return $o;
431         }
432
433         /**
434          * Create group sidebar widget
435          *
436          * @param string $every
437          * @param string $each
438          * @param string $editmode
439          *    'standard' => include link 'Edit groups'
440          *    'extended' => include link 'Create new group'
441          *    'full' => include link 'Create new group' and provide for each group a link to edit this group
442          * @param string $group_id
443          * @param int    $cid
444          * @return string
445          * @throws \Exception
446          */
447         public static function sidebarWidget($every = 'contact', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0)
448         {
449                 if (!local_user()) {
450                         return '';
451                 }
452
453                 $display_groups = [
454                         [
455                                 'text' => DI::l10n()->t('Everybody'),
456                                 'id' => 0,
457                                 'selected' => (($group_id === 'everyone') ? 'group-selected' : ''),
458                                 'href' => $every,
459                         ]
460                 ];
461
462                 $member_of = [];
463                 if ($cid) {
464                         $member_of = self::getIdsByContactId($cid);
465                 }
466
467                 $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]);
468                 while ($group = DBA::fetch($stmt)) {
469                         $selected = (($group_id == $group['id']) ? ' group-selected' : '');
470
471                         if ($editmode == 'full') {
472                                 $groupedit = [
473                                         'href' => 'group/' . $group['id'],
474                                         'title' => DI::l10n()->t('edit'),
475                                 ];
476                         } else {
477                                 $groupedit = null;
478                         }
479
480                         if ($each == 'group') {
481                                 $count = DBA::count('group_member', ['gid' => $group['id']]);
482                                 $group_name = sprintf('%s (%d)', $group['name'], $count);
483                         } else {
484                                 $group_name = $group['name'];
485                         }
486
487                         $display_groups[] = [
488                                 'id'   => $group['id'],
489                                 'cid'  => $cid,
490                                 'text' => $group_name,
491                                 'href' => $each . '/' . $group['id'],
492                                 'edit' => $groupedit,
493                                 'selected' => $selected,
494                                 'ismember' => in_array($group['id'], $member_of),
495                         ];
496                 }
497                 DBA::close($stmt);
498
499                 // Don't show the groups on the network page when there is only one
500                 if ((count($display_groups) <= 2) && ($each == 'network')) {
501                         return '';
502                 }
503
504                 $tpl = Renderer::getMarkupTemplate('group_side.tpl');
505                 $o = Renderer::replaceMacros($tpl, [
506                         '$add' => DI::l10n()->t('add'),
507                         '$title' => DI::l10n()->t('Groups'),
508                         '$groups' => $display_groups,
509                         'newgroup' => $editmode == 'extended' || $editmode == 'full' ? 1 : '',
510                         'grouppage' => 'group/',
511                         '$edittext' => DI::l10n()->t('Edit group'),
512                         '$ungrouped' => $every === 'contact' ? DI::l10n()->t('Contacts not in any group') : '',
513                         '$ungrouped_selected' => (($group_id === 'none') ? 'group-selected' : ''),
514                         '$createtext' => DI::l10n()->t('Create a new group'),
515                         '$creategroup' => DI::l10n()->t('Group Name: '),
516                         '$editgroupstext' => DI::l10n()->t('Edit groups'),
517                         '$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
518                 ]);
519
520                 return $o;
521         }
522 }