]> git.mxchange.org Git - friendica.git/blob - src/Core/ACL.php
Merge remote-tracking branch 'upstream/develop' into remote-rework
[friendica.git] / src / Core / ACL.php
1 <?php
2
3 /**
4  * @file src/Core/Acl.php
5  */
6
7 namespace Friendica\Core;
8
9 use Friendica\BaseObject;
10 use Friendica\Content\Feature;
11 use Friendica\Database\DBA;
12 use Friendica\Model\Contact;
13 use Friendica\Model\GContact;
14 use Friendica\Core\Session;
15 use Friendica\Util\Network;
16
17 /**
18  * Handle ACL management and display
19  *
20  * @author Hypolite Petovan <hypolite@mrpetovan.com>
21  */
22 class ACL extends BaseObject
23 {
24         /**
25          * Returns a select input tag with all the contact of the local user
26          *
27          * @param string $selname     Name attribute of the select input tag
28          * @param string $selclass    Class attribute of the select input tag
29          * @param array  $options     Available options:
30          *                            - size: length of the select box
31          *                            - mutual_friends: Only used for the hook
32          *                            - single: Only used for the hook
33          *                            - exclude: Only used for the hook
34          * @param array  $preselected Contact ID that should be already selected
35          * @return string
36          * @throws \Exception
37          */
38         public static function getSuggestContactSelectHTML($selname, $selclass, array $options = [], array $preselected = [])
39         {
40                 $a = self::getApp();
41
42                 $networks = null;
43
44                 $size = defaults($options, 'size', 4);
45                 $mutual = !empty($options['mutual_friends']);
46                 $single = !empty($options['single']) && empty($options['multiple']);
47                 $exclude = defaults($options, 'exclude', false);
48
49                 switch (defaults($options, 'networks', Protocol::PHANTOM)) {
50                         case 'DFRN_ONLY':
51                                 $networks = [Protocol::DFRN];
52                                 break;
53
54                         case 'PRIVATE':
55                                 $networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
56                                 break;
57
58                         case 'TWO_WAY':
59                                 if (!empty($a->user['prvnets'])) {
60                                         $networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
61                                 } else {
62                                         $networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA, Protocol::OSTATUS];
63                                 }
64                                 break;
65
66                         default: /// @TODO Maybe log this call?
67                                 break;
68                 }
69
70                 $x = ['options' => $options, 'size' => $size, 'single' => $single, 'mutual' => $mutual, 'exclude' => $exclude, 'networks' => $networks];
71
72                 Hook::callAll('contact_select_options', $x);
73
74                 $o = '';
75
76                 $sql_extra = '';
77
78                 if (!empty($x['mutual'])) {
79                         $sql_extra .= sprintf(" AND `rel` = %d ", intval(Contact::FRIEND));
80                 }
81
82                 if (!empty($x['exclude'])) {
83                         $sql_extra .= sprintf(" AND `id` != %d ", intval($x['exclude']));
84                 }
85
86                 if (!empty($x['networks'])) {
87                         /// @TODO rewrite to foreach()
88                         array_walk($x['networks'], function (&$value) {
89                                 $value = "'" . DBA::escape($value) . "'";
90                         });
91                         $str_nets = implode(',', $x['networks']);
92                         $sql_extra .= " AND `network` IN ( $str_nets ) ";
93                 }
94
95                 $tabindex = (!empty($options['tabindex']) ? 'tabindex="' . $options["tabindex"] . '"' : '');
96
97                 if (!empty($x['single'])) {
98                         $o .= "<select name=\"$selname\" id=\"$selclass\" class=\"$selclass\" size=\"" . $x['size'] . "\" $tabindex >\r\n";
99                 } else {
100                         $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"" . $x['size'] . "$\" $tabindex >\r\n";
101                 }
102
103                 $stmt = DBA::p("SELECT `id`, `name`, `url`, `network` FROM `contact`
104                         WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
105                         $sql_extra
106                         ORDER BY `name` ASC ", intval(local_user())
107                 );
108
109                 $contacts = DBA::toArray($stmt);
110
111                 $arr = ['contact' => $contacts, 'entry' => $o];
112
113                 // e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
114                 Hook::callAll($a->module . '_pre_' . $selname, $arr);
115
116                 if (DBA::isResult($contacts)) {
117                         foreach ($contacts as $contact) {
118                                 if (in_array($contact['id'], $preselected)) {
119                                         $selected = ' selected="selected" ';
120                                 } else {
121                                         $selected = '';
122                                 }
123
124                                 $trimmed = mb_substr($contact['name'], 0, 20);
125
126                                 $o .= "<option value=\"{$contact['id']}\" $selected title=\"{$contact['name']}|{$contact['url']}\" >$trimmed</option>\r\n";
127                         }
128                 }
129
130                 $o .= '</select>' . PHP_EOL;
131
132                 Hook::callAll($a->module . '_post_' . $selname, $o);
133
134                 return $o;
135         }
136
137         /**
138          * Returns a select input tag with all the contact of the local user
139          *
140          * @param string $selname     Name attribute of the select input tag
141          * @param string $selclass    Class attribute of the select input tag
142          * @param array  $preselected Contact IDs that should be already selected
143          * @param int    $size        Length of the select box
144          * @param int    $tabindex    Select input tag tabindex attribute
145          * @return string
146          * @throws \Exception
147          */
148         public static function getMessageContactSelectHTML($selname, $selclass, array $preselected = [], $size = 4, $tabindex = null)
149         {
150                 $a = self::getApp();
151
152                 $o = '';
153
154                 // When used for private messages, we limit correspondence to mutual DFRN/Friendica friends and the selector
155                 // to one recipient. By default our selector allows multiple selects amongst all contacts.
156                 $sql_extra = sprintf(" AND `rel` = %d ", intval(Contact::FRIEND));
157                 $sql_extra .= sprintf(" AND `network` IN ('%s' , '%s') ", Protocol::DFRN, Protocol::DIASPORA);
158
159                 $tabindex_attr = !empty($tabindex) ? ' tabindex="' . intval($tabindex) . '"' : '';
160
161                 $hidepreselected = '';
162                 if ($preselected) {
163                         $sql_extra .= " AND `id` IN (" . implode(",", $preselected) . ")";
164                         $hidepreselected = ' style="display: none;"';
165                 }
166
167                 $o .= "<select name=\"$selname\" id=\"$selclass\" class=\"$selclass\" size=\"$size\"$tabindex_attr$hidepreselected>\r\n";
168
169                 $stmt = DBA::p("SELECT `id`, `name`, `url`, `network` FROM `contact`
170                         WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
171                         $sql_extra
172                         ORDER BY `name` ASC ", intval(local_user())
173                 );
174
175                 $contacts = DBA::toArray($stmt);
176
177                 $arr = ['contact' => $contacts, 'entry' => $o];
178
179                 // e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
180                 Hook::callAll($a->module . '_pre_' . $selname, $arr);
181
182                 $receiverlist = [];
183
184                 if (DBA::isResult($contacts)) {
185                         foreach ($contacts as $contact) {
186                                 if (in_array($contact['id'], $preselected)) {
187                                         $selected = ' selected="selected"';
188                                 } else {
189                                         $selected = '';
190                                 }
191
192                                 $trimmed = Protocol::formatMention($contact['url'], $contact['name']);
193
194                                 $receiverlist[] = $trimmed;
195
196                                 $o .= "<option value=\"{$contact['id']}\"$selected title=\"{$contact['name']}|{$contact['url']}\" >$trimmed</option>\r\n";
197                         }
198                 }
199
200                 $o .= '</select>' . PHP_EOL;
201
202                 if ($preselected) {
203                         $o .= implode(', ', $receiverlist);
204                 }
205
206                 Hook::callAll($a->module . '_post_' . $selname, $o);
207
208                 return $o;
209         }
210
211         private static function fixACL(&$item)
212         {
213                 $item = intval(str_replace(['<', '>'], ['', ''], $item));
214         }
215
216         /**
217          * Return the default permission of the provided user array
218          *
219          * @param array $user
220          * @return array Hash of contact id lists
221          * @throws \Exception
222          */
223         public static function getDefaultUserPermissions(array $user = null)
224         {
225                 $matches = [];
226
227                 $acl_regex = '/<([0-9]+)>/i';
228
229                 preg_match_all($acl_regex, defaults($user, 'allow_cid', ''), $matches);
230                 $allow_cid = $matches[1];
231                 preg_match_all($acl_regex, defaults($user, 'allow_gid', ''), $matches);
232                 $allow_gid = $matches[1];
233                 preg_match_all($acl_regex, defaults($user, 'deny_cid', ''), $matches);
234                 $deny_cid = $matches[1];
235                 preg_match_all($acl_regex, defaults($user, 'deny_gid', ''), $matches);
236                 $deny_gid = $matches[1];
237
238                 // Reformats the ACL data so that it is accepted by the JS frontend
239                 array_walk($allow_cid, 'self::fixACL');
240                 array_walk($allow_gid, 'self::fixACL');
241                 array_walk($deny_cid, 'self::fixACL');
242                 array_walk($deny_gid, 'self::fixACL');
243
244                 Contact::pruneUnavailable($allow_cid);
245
246                 return [
247                         'allow_cid' => $allow_cid,
248                         'allow_gid' => $allow_gid,
249                         'deny_cid' => $deny_cid,
250                         'deny_gid' => $deny_gid,
251                 ];
252         }
253
254         /**
255          * Return the full jot ACL selector HTML
256          *
257          * @param array $user                User array
258          * @param bool  $show_jotnets
259          * @param array $default_permissions Static defaults permission array: ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']
260          * @return string
261          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
262          */
263         public static function getFullSelectorHTML(array $user = null, $show_jotnets = false, array $default_permissions = [])
264         {
265                 // Defaults user permissions
266                 if (empty($default_permissions)) {
267                         $default_permissions = self::getDefaultUserPermissions($user);
268                 }
269
270                 $jotnets_fields = [];
271                 if ($show_jotnets) {
272                         $mail_enabled = false;
273                         $pubmail_enabled = false;
274
275                         if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) {
276                                 $mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]);
277                                 if (DBA::isResult($mailacct)) {
278                                         $mail_enabled = true;
279                                         $pubmail_enabled = !empty($mailacct['pubmail']);
280                                 }
281                         }
282
283                         if (empty($default_permissions['hidewall'])) {
284                                 if ($mail_enabled) {
285                                         $jotnets_fields[] = [
286                                                 'type' => 'checkbox',
287                                                 'field' => [
288                                                         'pubmail_enable',
289                                                         L10n::t('Post to Email'),
290                                                         $pubmail_enabled
291                                                 ]
292                                         ];
293                                 }
294
295                                 Hook::callAll('jot_networks', $jotnets_fields);
296                         }
297                 }
298
299                 $tpl = Renderer::getMarkupTemplate('acl_selector.tpl');
300                 $o = Renderer::replaceMacros($tpl, [
301                         '$showall' => L10n::t('Visible to everybody'),
302                         '$show' => L10n::t('show'),
303                         '$hide' => L10n::t('don\'t show'),
304                         '$allowcid' => json_encode(defaults($default_permissions, 'allow_cid', [])), // we need arrays for Javascript since we call .remove() and .push() on this values
305                         '$allowgid' => json_encode(defaults($default_permissions, 'allow_gid', [])),
306                         '$denycid' => json_encode(defaults($default_permissions, 'deny_cid', [])),
307                         '$denygid' => json_encode(defaults($default_permissions, 'deny_gid', [])),
308                         '$networks' => $show_jotnets,
309                         '$emailcc' => L10n::t('CC: email addresses'),
310                         '$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'),
311                         '$jotnets_enabled' => empty($default_permissions['hidewall']),
312                         '$jotnets_summary' => L10n::t('Connectors'),
313                         '$jotnets_fields' => $jotnets_fields,
314                         '$jotnets_disabled_label' => L10n::t('Connectors disabled, since "%s" is enabled.', L10n::t('Hide your profile details from unknown viewers?')),
315                         '$aclModalTitle' => L10n::t('Permissions'),
316                         '$aclModalDismiss' => L10n::t('Close'),
317                         '$features' => [
318                                 'aclautomention' => !empty($user['uid']) && Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
319                         ],
320                 ]);
321
322                 return $o;
323         }
324
325         /**
326          * Searching for global contacts for autocompletion
327          *
328          * @brief Searching for global contacts for autocompletion
329          * @param string $search Name or part of a name or nick
330          * @param string $mode   Search mode (e.g. "community")
331          * @param int    $page   Page number (starts at 1)
332          * @return array with the search results
333          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
334          */
335         public static function contactAutocomplete($search, $mode, int $page = 1)
336         {
337                 if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
338                         return [];
339                 }
340
341                 // don't search if search term has less than 2 characters
342                 if (!$search || mb_strlen($search) < 2) {
343                         return [];
344                 }
345
346                 if (substr($search, 0, 1) === '@') {
347                         $search = substr($search, 1);
348                 }
349
350                 // check if searching in the local global contact table is enabled
351                 if (Config::get('system', 'poco_local_search')) {
352                         $return = GContact::searchByName($search, $mode);
353                 } else {
354                         $p = $page > 1 ? 'p=' . $page : '';
355
356                         $curlResult = Network::curl(get_server() . '/lsearch?' . $p . '&search=' . urlencode($search));
357                         if ($curlResult->isSuccess()) {
358                                 $lsearch = json_decode($curlResult->getBody(), true);
359                                 if (!empty($lsearch['results'])) {
360                                         $return = $lsearch['results'];
361                                 }
362                         }
363                 }
364
365                 return $return ?? [];
366         }
367 }