]> git.mxchange.org Git - friendica.git/blob - src/Object/Contact.php
Merge remote-tracking branch 'upstream/develop' into archive-contact
[friendica.git] / src / Object / Contact.php
1 <?php
2
3 /**
4  * @file src/Object/Contact.php
5  */
6
7 namespace Friendica\Object;
8
9 use Friendica\App;
10 use Friendica\BaseObject;
11 use Friendica\Core\PConfig;
12 use Friendica\Core\System;
13 use Friendica\Core\Worker;
14 use Friendica\Database\DBM;
15 use Friendica\Network\Probe;
16 use Friendica\Object\Photo;
17 use Friendica\Protocol\Diaspora;
18 use Friendica\Protocol\DFRN;
19 use Friendica\Protocol\OStatus;
20 use Friendica\Protocol\Salmon;
21 use dba;
22
23 require_once 'boot.php';
24 require_once 'include/text.php';
25
26 /**
27  * @brief functions for interacting with a contact
28  */
29 class Contact extends BaseObject
30 {
31         /**
32          * Creates the self-contact for the provided user id
33          *
34          * @param int $uid
35          * @return bool Operation success
36          */
37         public static function createSelfFromUserId($uid)
38         {
39                 // Only create the entry if it doesn't exist yet
40                 if (dba::exists('contact', ['uid' => intval($uid), 'self'])) {
41                         return true;
42                 }
43
44                 $user = dba::select('user', ['uid', 'username', 'nickname'], ['uid' => intval($uid)], ['limit' => 1]);
45                 if (!DBM::is_result($user)) {
46                         return false;
47                 }
48
49                 $return = dba::insert('contact', [
50                         'uid'         => $user['uid'],
51                         'created'     => datetime_convert(),
52                         'self'        => 1,
53                         'name'        => $user['username'],
54                         'nick'        => $user['nickname'],
55                         'photo'       => System::baseUrl() . '/photo/profile/' . $user['uid'] . '.jpg',
56                         'thumb'       => System::baseUrl() . '/photo/avatar/'  . $user['uid'] . '.jpg',
57                         'micro'       => System::baseUrl() . '/photo/micro/'   . $user['uid'] . '.jpg',
58                         'blocked'     => 0,
59                         'pending'     => 0,
60                         'url'         => System::baseUrl() . '/profile/' . $user['nickname'],
61                         'nurl'        => normalise_link(System::baseUrl() . '/profile/' . $user['nickname']),
62                         'addr'        => $user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3),
63                         'request'     => System::baseUrl() . '/dfrn_request/' . $user['nickname'],
64                         'notify'      => System::baseUrl() . '/dfrn_notify/'  . $user['nickname'],
65                         'poll'        => System::baseUrl() . '/dfrn_poll/'    . $user['nickname'],
66                         'confirm'     => System::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
67                         'poco'        => System::baseUrl() . '/poco/'         . $user['nickname'],
68                         'name-date'   => datetime_convert(),
69                         'uri-date'    => datetime_convert(),
70                         'avatar-date' => datetime_convert(),
71                         'closeness'   => 0
72                 ]);
73
74                 return $return;
75         }
76
77         /**
78          * @brief Marks a contact for removal
79          *
80          * @param int $id contact id
81          * @return null
82          */
83         public static function remove($id)
84         {
85                 // We want just to make sure that we don't delete our "self" contact
86                 $r = dba::select('contact', array('uid'), array('id' => $id, 'self' => false), array('limit' => 1));
87
88                 if (!DBM::is_result($r) || !intval($r['uid'])) {
89                         return;
90                 }
91
92                 $archive = PConfig::get($r['uid'], 'system', 'archive_removed_contacts');
93                 if ($archive) {
94                         dba::update('contact', array('archive' => true, 'network' => 'none', 'writable' => false), array('id' => $id));
95                         return;
96                 }
97
98                 dba::delete('contact', array('id' => $id));
99
100                 // Delete the rest in the background
101                 Worker::add(PRIORITY_LOW, 'RemoveContact', $id);
102         }
103
104         /**
105          * @brief Sends an unfriend message. Does not remove the contact
106          *
107          * @param array $user    User unfriending
108          * @param array $contact Contact unfriended
109          * @return void
110          */
111         public static function terminateFriendship(array $user, array $contact)
112         {
113                 if ($contact['network'] === NETWORK_OSTATUS) {
114                         // create an unfollow slap
115                         $item = array();
116                         $item['verb'] = NAMESPACE_OSTATUS . "/unfollow";
117                         $item['follow'] = $contact["url"];
118                         $slap = OStatus::salmon($item, $user);
119
120                         if ((x($contact, 'notify')) && (strlen($contact['notify']))) {
121                                 Salmon::slapper($user, $contact['notify'], $slap);
122                         }
123                 } elseif ($contact['network'] === NETWORK_DIASPORA) {
124                         Diaspora::sendUnshare($user, $contact);
125                 } elseif ($contact['network'] === NETWORK_DFRN) {
126                         DFRN::deliver($user, $contact, 'placeholder', 1);
127                 }
128         }
129
130         /**
131          * @brief Marks a contact for archival after a communication issue delay
132          *
133          * Contact has refused to recognise us as a friend. We will start a countdown.
134          * If they still don't recognise us in 32 days, the relationship is over,
135          * and we won't waste any more time trying to communicate with them.
136          * This provides for the possibility that their database is temporarily messed
137          * up or some other transient event and that there's a possibility we could recover from it.
138          *
139          * @param array $contact contact to mark for archival
140          * @return type
141          */
142         public static function markForArchival(array $contact)
143         {
144                 // Contact already archived, nothing to do
145                 if ($contact['archive']) {
146                         return;
147                 }
148 logger('Blubb-m: '.$contact['id'].' - '.System::callstack());
149                 if ($contact['term-date'] <= NULL_DATE) {
150                         dba::update('contact', array('term-date' => datetime_convert()), array('id' => $contact['id']));
151
152                         if ($contact['url'] != '') {
153                                 dba::update('contact', array('term-date' => datetime_convert()), array('`nurl` = ? AND `term-date` <= ?', normalise_link($contact['url']), NULL_DATE));
154                         }
155                 } else {
156                         /* @todo
157                          * We really should send a notification to the owner after 2-3 weeks
158                          * so they won't be surprised when the contact vanishes and can take
159                          * remedial action if this was a serious mistake or glitch
160                          */
161
162                         /// @todo Check for contact vitality via probing
163                         $expiry = $contact['term-date'] . ' + 32 days ';
164                         if (datetime_convert() > datetime_convert('UTC', 'UTC', $expiry)) {
165                                 /* Relationship is really truly dead. archive them rather than
166                                  * delete, though if the owner tries to unarchive them we'll start
167                                  * the whole process over again.
168                                  */
169                                 dba::update('contact', array('archive' => 1), array('id' => $contact['id']));
170
171                                 if ($contact['url'] != '') {
172                                         dba::update('contact', array('archive' => 1), array('nurl' => normalise_link($contact['url'])));
173                                 }
174                         }
175                 }
176         }
177
178         /**
179          * @brief Cancels the archival countdown
180          *
181          * @see Contact::markForArchival()
182          *
183          * @param array $contact contact to be unmarked for archival
184          * @return null
185          */
186         public static function unmarkForArchival(array $contact)
187         {
188 //logger('Blubb-m: '.$contact['id'].' - '.System::callstack());
189                 $condition = array('`id` = ? AND (`term-date` > ? OR `archive`)', $contact[`id`], NULL_DATE);
190                 $exists = dba::exists('contact', $condition);
191
192                 // We don't need to update, we never marked this contact for archival
193                 if (!$exists) {
194                         return;
195                 }
196
197                 // It's a miracle. Our dead contact has inexplicably come back to life.
198                 $fields = array('term-date' => NULL_DATE, 'archive' => false);
199                 dba::update('contact', $fields, array('id' => $contact['id']));
200
201                 if ($contact['url'] != '') {
202                         dba::update('contact', $fields, array('nurl' => normalise_link($contact['url'])));
203                 }
204         }
205
206         /**
207          * @brief Get contact data for a given profile link
208          *
209          * The function looks at several places (contact table and gcontact table) for the contact
210          * It caches its result for the same script execution to prevent duplicate calls
211          *
212          * @param string $url     The profile link
213          * @param int    $uid     User id
214          * @param array  $default If not data was found take this data as default value
215          *
216          * @return array Contact data
217          */
218         public static function getDetailsByURL($url, $uid = -1, array $default = [])
219         {
220                 static $cache = array();
221
222                 if ($url == '') {
223                         return $default;
224                 }
225
226                 if ($uid == -1) {
227                         $uid = local_user();
228                 }
229
230                 if (isset($cache[$url][$uid])) {
231                         return $cache[$url][$uid];
232                 }
233
234                 $ssl_url = str_replace('http://', 'https://', $url);
235
236                 // Fetch contact data from the contact table for the given user
237                 $s = dba::p("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
238                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
239                 FROM `contact` WHERE `nurl` = ? AND `uid` = ?", normalise_link($url), $uid);
240                 $r = dba::inArray($s);
241
242                 // Fetch contact data from the contact table for the given user, checking with the alias
243                 if (!DBM::is_result($r)) {
244                         $s = dba::p("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
245                                 `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
246                         FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ?", normalise_link($url), $url, $ssl_url, $uid);
247                         $r = dba::inArray($s);
248                 }
249
250                 // Fetch the data from the contact table with "uid=0" (which is filled automatically)
251                 if (!DBM::is_result($r)) {
252                         $s = dba::p("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
253                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
254                         FROM `contact` WHERE `nurl` = ? AND `uid` = 0", normalise_link($url));
255                         $r = dba::inArray($s);
256                 }
257
258                 // Fetch the data from the contact table with "uid=0" (which is filled automatically) - checked with the alias
259                 if (!DBM::is_result($r)) {
260                         $s = dba::p("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
261                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
262                         FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = 0", normalise_link($url), $url, $ssl_url);
263                         $r = dba::inArray($s);
264                 }
265
266                 // Fetch the data from the gcontact table
267                 if (!DBM::is_result($r)) {
268                         $s = dba::p("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
269                         `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
270                         FROM `gcontact` WHERE `nurl` = ?", normalise_link($url));
271                         $r = dba::inArray($s);
272                 }
273
274                 if (DBM::is_result($r)) {
275                         // If there is more than one entry we filter out the connector networks
276                         if (count($r) > 1) {
277                                 foreach ($r as $id => $result) {
278                                         if ($result["network"] == NETWORK_STATUSNET) {
279                                                 unset($r[$id]);
280                                         }
281                                 }
282                         }
283
284                         $profile = array_shift($r);
285
286                         // "bd" always contains the upcoming birthday of a contact.
287                         // "birthday" might contain the birthday including the year of birth.
288                         if ($profile["birthday"] > '0001-01-01') {
289                                 $bd_timestamp = strtotime($profile["birthday"]);
290                                 $month = date("m", $bd_timestamp);
291                                 $day = date("d", $bd_timestamp);
292
293                                 $current_timestamp = time();
294                                 $current_year = date("Y", $current_timestamp);
295                                 $current_month = date("m", $current_timestamp);
296                                 $current_day = date("d", $current_timestamp);
297
298                                 $profile["bd"] = $current_year . "-" . $month . "-" . $day;
299                                 $current = $current_year . "-" . $current_month . "-" . $current_day;
300
301                                 if ($profile["bd"] < $current) {
302                                         $profile["bd"] = ( ++$current_year) . "-" . $month . "-" . $day;
303                                 }
304                         } else {
305                                 $profile["bd"] = '0001-01-01';
306                         }
307                 } else {
308                         $profile = $default;
309                 }
310
311                 if (($profile["photo"] == "") && isset($default["photo"])) {
312                         $profile["photo"] = $default["photo"];
313                 }
314
315                 if (($profile["name"] == "") && isset($default["name"])) {
316                         $profile["name"] = $default["name"];
317                 }
318
319                 if (($profile["network"] == "") && isset($default["network"])) {
320                         $profile["network"] = $default["network"];
321                 }
322
323                 if (($profile["thumb"] == "") && isset($profile["photo"])) {
324                         $profile["thumb"] = $profile["photo"];
325                 }
326
327                 if (($profile["micro"] == "") && isset($profile["thumb"])) {
328                         $profile["micro"] = $profile["thumb"];
329                 }
330
331                 if ((($profile["addr"] == "") || ($profile["name"] == "")) && ($profile["gid"] != 0)
332                         && in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS))
333                 ) {
334                         Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]);
335                 }
336
337                 // Show contact details of Diaspora contacts only if connected
338                 if (($profile["cid"] == 0) && ($profile["network"] == NETWORK_DIASPORA)) {
339                         $profile["location"] = "";
340                         $profile["about"] = "";
341                         $profile["gender"] = "";
342                         $profile["birthday"] = '0001-01-01';
343                 }
344
345                 $cache[$url][$uid] = $profile;
346
347                 return $profile;
348         }
349
350         /**
351          * @brief Get contact data for a given address
352          *
353          * The function looks at several places (contact table and gcontact table) for the contact
354          *
355          * @param string $addr The profile link
356          * @param int    $uid  User id
357          *
358          * @return array Contact data
359          */
360         public static function getDetailsByAddr($addr, $uid = -1)
361         {
362                 static $cache = array();
363
364                 if ($addr == '') {
365                         return array();
366                 }
367
368                 if ($uid == -1) {
369                         $uid = local_user();
370                 }
371
372                 // Fetch contact data from the contact table for the given user
373                 $r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
374                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
375                 FROM `contact` WHERE `addr` = '%s' AND `uid` = %d", dbesc($addr), intval($uid));
376
377                 // Fetch the data from the contact table with "uid=0" (which is filled automatically)
378                 if (!DBM::is_result($r))
379                         $r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
380                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
381                         FROM `contact` WHERE `addr` = '%s' AND `uid` = 0", dbesc($addr));
382
383                 // Fetch the data from the gcontact table
384                 if (!DBM::is_result($r))
385                         $r = q("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
386                         `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
387                         FROM `gcontact` WHERE `addr` = '%s'", dbesc($addr));
388
389                 if (!DBM::is_result($r)) {
390                         $data = Probe::uri($addr);
391
392                         $profile = self::getDetailsByURL($data['url'], $uid);
393                 } else {
394                         $profile = $r[0];
395                 }
396
397                 return $profile;
398         }
399
400         /**
401          * @brief Returns the data array for the photo menu of a given contact
402          *
403          * @param array $contact contact
404          * @param int   $uid     optional, default 0
405          * @return array
406          */
407         public static function photoMenu(array $contact, $uid = 0)
408         {
409                 // @todo Unused, to be removed
410                 $a = get_app();
411
412                 $contact_url = '';
413                 $pm_url = '';
414                 $status_link = '';
415                 $photos_link = '';
416                 $posts_link = '';
417                 $contact_drop_link = '';
418                 $poke_link = '';
419
420                 if ($uid == 0) {
421                         $uid = local_user();
422                 }
423
424                 if ($contact['uid'] != $uid) {
425                         if ($uid == 0) {
426                                 $profile_link = zrl($contact['url']);
427                                 $menu = array('profile' => array(t('View Profile'), $profile_link, true));
428
429                                 return $menu;
430                         }
431
432                         $r = dba::select('contact', array(), array('nurl' => $contact['nurl'], 'network' => $contact['network'], 'uid' => $uid), array('limit' => 1));
433                         if ($r) {
434                                 return self::photoMenu($r, $uid);
435                         } else {
436                                 $profile_link = zrl($contact['url']);
437                                 $connlnk = 'follow/?url=' . $contact['url'];
438                                 $menu = array(
439                                         'profile' => array(t('View Profile'), $profile_link, true),
440                                         'follow' => array(t('Connect/Follow'), $connlnk, true)
441                                 );
442
443                                 return $menu;
444                         }
445                 }
446
447                 $sparkle = false;
448                 if ($contact['network'] === NETWORK_DFRN) {
449                         $sparkle = true;
450                         $profile_link = System::baseUrl() . '/redir/' . $contact['id'];
451                 } else {
452                         $profile_link = $contact['url'];
453                 }
454
455                 if ($profile_link === 'mailbox') {
456                         $profile_link = '';
457                 }
458
459                 if ($sparkle) {
460                         $status_link = $profile_link . '?url=status';
461                         $photos_link = $profile_link . '?url=photos';
462                         $profile_link = $profile_link . '?url=profile';
463                 }
464
465                 if (in_array($contact['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
466                         $pm_url = System::baseUrl() . '/message/new/' . $contact['id'];
467                 }
468
469                 if ($contact['network'] == NETWORK_DFRN) {
470                         $poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id'];
471                 }
472
473                 $contact_url = System::baseUrl() . '/contacts/' . $contact['id'];
474
475                 $posts_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/posts';
476                 $contact_drop_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/drop?confirm=1';
477
478                 /**
479                  * Menu array:
480                  * "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ]
481                  */
482                 $menu = array(
483                         'status' => array(t("View Status"), $status_link, true),
484                         'profile' => array(t("View Profile"), $profile_link, true),
485                         'photos' => array(t("View Photos"), $photos_link, true),
486                         'network' => array(t("Network Posts"), $posts_link, false),
487                         'edit' => array(t("View Contact"), $contact_url, false),
488                         'drop' => array(t("Drop Contact"), $contact_drop_link, false),
489                         'pm' => array(t("Send PM"), $pm_url, false),
490                         'poke' => array(t("Poke"), $poke_link, false),
491                 );
492
493
494                 $args = array('contact' => $contact, 'menu' => &$menu);
495
496                 call_hooks('contact_photo_menu', $args);
497
498                 $menucondensed = array();
499
500                 foreach ($menu as $menuname => $menuitem) {
501                         if ($menuitem[1] != '') {
502                                 $menucondensed[$menuname] = $menuitem;
503                         }
504                 }
505
506                 return $menucondensed;
507         }
508
509         /**
510          * @brief Returns ungrouped contact count or list for user
511          *
512          * Returns either the total number of ungrouped contacts for the given user
513          * id or a paginated list of ungrouped contacts.
514          *
515          * @param int $uid   uid
516          * @param int $start optional, default 0
517          * @param int $count optional, default 0
518          *
519          * @return array
520          */
521         public static function getUngroupedList($uid, $start = 0, $count = 0)
522         {
523                 if (!$count) {
524                         $r = q(
525                                 "SELECT COUNT(*) AS `total`
526                                  FROM `contact`
527                                  WHERE `uid` = %d
528                                  AND NOT `self`
529                                  AND NOT `blocked`
530                                  AND NOT `pending`
531                                  AND `id` NOT IN (
532                                         SELECT DISTINCT(`contact-id`)
533                                         FROM `group_member`
534                                         WHERE `uid` = %d
535                                 )", intval($uid), intval($uid)
536                         );
537
538                         return $r;
539                 }
540
541                 $r = q(
542                         "SELECT *
543                         FROM `contact`
544                         WHERE `uid` = %d
545                         AND NOT `self`
546                         AND NOT `blocked`
547                         AND NOT `pending`
548                         AND `id` NOT IN (
549                                 SELECT DISTINCT(`contact-id`)
550                                 FROM `group_member` WHERE `uid` = %d
551                         )
552                         LIMIT %d, %d", intval($uid), intval($uid), intval($start), intval($count)
553                 );
554                 return $r;
555         }
556
557         /**
558          * @brief Fetch the contact id for a given url and user
559          *
560          * First lookup in the contact table to find a record matching either `url`, `nurl`,
561          * `addr` or `alias`.
562          *
563          * If there's no record and we aren't looking for a public contact, we quit.
564          * If there's one, we check that it isn't time to update the picture else we
565          * directly return the found contact id.
566          *
567          * Second, we probe the provided $url wether it's http://server.tld/profile or
568          * nick@server.tld. We quit if we can't get any info back.
569          *
570          * Third, we create the contact record if it doesn't exist
571          *
572          * Fourth, we update the existing record with the new data (avatar, alias, nick)
573          * if there's any updates
574          *
575          * @param string  $url       Contact URL
576          * @param integer $uid       The user id for the contact (0 = public contact)
577          * @param boolean $no_update Don't update the contact
578          *
579          * @return integer Contact ID
580          */
581         public static function getIdForURL($url, $uid = 0, $no_update = false)
582         {
583                 logger("Get contact data for url " . $url . " and user " . $uid . " - " . System::callstack(), LOGGER_DEBUG);
584
585                 $contact_id = 0;
586
587                 if ($url == '') {
588                         return 0;
589                 }
590
591                 /// @todo Verify if we can't use Contact::getDetailsByUrl instead of the following
592                 // We first try the nurl (http://server.tld/nick), most common case
593                 $contact = dba::select('contact', array('id', 'avatar-date'), array('nurl' => normalise_link($url), 'uid' => $uid), array('limit' => 1));
594
595                 // Then the addr (nick@server.tld)
596                 if (!DBM::is_result($contact)) {
597                         $contact = dba::select('contact', array('id', 'avatar-date'), array('addr' => $url, 'uid' => $uid), array('limit' => 1));
598                 }
599
600                 // Then the alias (which could be anything)
601                 if (!DBM::is_result($contact)) {
602                         // The link could be provided as http although we stored it as https
603                         $ssl_url = str_replace('http://', 'https://', $url);
604                         $r = dba::select('contact', array('id', 'avatar-date'), array('`alias` IN (?, ?, ?) AND `uid` = ?', $url, normalise_link($url), $ssl_url, $uid), array('limit' => 1));
605                         $contact = dba::fetch($r);
606                         dba::close($r);
607                 }
608
609                 if (DBM::is_result($contact)) {
610                         $contact_id = $contact["id"];
611
612                         // Update the contact every 7 days
613                         $update_contact = ($contact['avatar-date'] < datetime_convert('', '', 'now -7 days'));
614
615                         // We force the update if the avatar is empty
616                         if ($contact['avatar'] == '') {
617                                 $update_contact = true;
618                         }
619
620                         if (!$update_contact || $no_update) {
621                                 return $contact_id;
622                         }
623                 } elseif ($uid != 0) {
624                         // Non-existing user-specific contact, exiting
625                         return 0;
626                 }
627
628                 $data = Probe::uri($url, "", $uid);
629
630                 // Last try in gcontact for unsupported networks
631                 if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA, NETWORK_PUMPIO, NETWORK_MAIL))) {
632                         if ($uid != 0) {
633                                 return 0;
634                         }
635
636                         // Get data from the gcontact table
637                         $gcontacts = dba::select('gcontact', array('name', 'nick', 'url', 'photo', 'addr', 'alias', 'network'), array('nurl' => normalise_link($url)), array('limit' => 1));
638                         if (!DBM::is_result($gcontacts)) {
639                                 return 0;
640                         }
641
642                         $data = array_merge($data, $gcontacts);
643                 }
644
645                 if (!$contact_id && ($data["alias"] != '') && ($data["alias"] != $url)) {
646                         $contact_id = self::getIdForURL($data["alias"], $uid, true);
647                 }
648
649                 $url = $data["url"];
650                 if (!$contact_id) {
651                         dba::insert(
652                                 'contact', array('uid' => $uid, 'created' => datetime_convert(), 'url' => $data["url"],
653                                 'nurl' => normalise_link($data["url"]), 'addr' => $data["addr"],
654                                 'alias' => $data["alias"], 'notify' => $data["notify"], 'poll' => $data["poll"],
655                                 'name' => $data["name"], 'nick' => $data["nick"], 'photo' => $data["photo"],
656                                 'keywords' => $data["keywords"], 'location' => $data["location"], 'about' => $data["about"],
657                                 'network' => $data["network"], 'pubkey' => $data["pubkey"],
658                                 'rel' => CONTACT_IS_SHARING, 'priority' => $data["priority"],
659                                 'batch' => $data["batch"], 'request' => $data["request"],
660                                 'confirm' => $data["confirm"], 'poco' => $data["poco"],
661                                 'name-date' => datetime_convert(), 'uri-date' => datetime_convert(),
662                                 'avatar-date' => datetime_convert(), 'writable' => 1, 'blocked' => 0,
663                                 'readonly' => 0, 'pending' => 0)
664                         );
665
666                         $s = dba::select('contact', array('id'), array('nurl' => normalise_link($data["url"]), 'uid' => $uid), array('order' => array('id'), 'limit' => 2));
667                         $contacts = dba::inArray($s);
668                         if (!DBM::is_result($contacts)) {
669                                 return 0;
670                         }
671
672                         $contact_id = $contacts[0]["id"];
673
674                         // Update the newly created contact from data in the gcontact table
675                         $gcontact = dba::select('gcontact', array('location', 'about', 'keywords', 'gender'), array('nurl' => normalise_link($data["url"])), array('limit' => 1));
676                         if (DBM::is_result($gcontact)) {
677                                 // Only use the information when the probing hadn't fetched these values
678                                 if ($data['keywords'] != '') {
679                                         unset($gcontact['keywords']);
680                                 }
681                                 if ($data['location'] != '') {
682                                         unset($gcontact['location']);
683                                 }
684                                 if ($data['about'] != '') {
685                                         unset($gcontact['about']);
686                                 }
687                                 dba::update('contact', $gcontact, array('id' => $contact_id));
688                         }
689
690                         if (count($contacts) > 1 && $uid == 0 && $contact_id != 0 && $data["url"] != "") {
691                                 dba::delete('contact', array("`nurl` = ? AND `uid` = 0 AND `id` != ? AND NOT `self`",
692                                         normalise_link($data["url"]), $contact_id));
693                         }
694                 }
695
696                 self::updateAvatar($data["photo"], $uid, $contact_id);
697
698                 $contact = dba::select('contact', array('url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date'), array('id' => $contact_id), array('limit' => 1));
699
700                 // This condition should always be true
701                 if (!DBM::is_result($contact)) {
702                         return $contact_id;
703                 }
704
705                 $updated = array('addr' => $data['addr'],
706                         'alias' => $data['alias'],
707                         'url' => $data['url'],
708                         'nurl' => normalise_link($data['url']),
709                         'name' => $data['name'],
710                         'nick' => $data['nick']);
711
712                 if ($data['keywords'] != '') {
713                         $updated['keywords'] = $data['keywords'];
714                 }
715                 if ($data['location'] != '') {
716                         $updated['location'] = $data['location'];
717                 }
718                 if ($data['about'] != '') {
719                         $updated['about'] = $data['about'];
720                 }
721
722                 if (($data["addr"] != $contact["addr"]) || ($data["alias"] != $contact["alias"])) {
723                         $updated['uri-date'] = datetime_convert();
724                 }
725                 if (($data["name"] != $contact["name"]) || ($data["nick"] != $contact["nick"])) {
726                         $updated['name-date'] = datetime_convert();
727                 }
728
729                 $updated['avatar-date'] = datetime_convert();
730
731                 dba::update('contact', $updated, array('id' => $contact_id), $contact);
732
733                 return $contact_id;
734         }
735
736         /**
737          * @brief Checks if the contact is blocked
738          *
739          * @param int $cid contact id
740          *
741          * @return boolean Is the contact blocked?
742          */
743         public static function isBlocked($cid)
744         {
745                 if ($cid == 0) {
746                         return false;
747                 }
748
749                 $blocked = dba::select('contact', array('blocked'), array('id' => $cid), array('limit' => 1));
750                 if (!DBM::is_result($blocked)) {
751                         return false;
752                 }
753                 return (bool) $blocked['blocked'];
754         }
755
756         /**
757          * @brief Checks if the contact is hidden
758          *
759          * @param int $cid contact id
760          *
761          * @return boolean Is the contact hidden?
762          */
763         public static function isHidden($cid)
764         {
765                 if ($cid == 0) {
766                         return false;
767                 }
768
769                 $hidden = dba::select('contact', array('hidden'), array('id' => $cid), array('limit' => 1));
770                 if (!DBM::is_result($hidden)) {
771                         return false;
772                 }
773                 return (bool) $hidden['hidden'];
774         }
775
776         /**
777          * @brief Returns posts from a given contact url
778          *
779          * @param string $contact_url Contact URL
780          *
781          * @return string posts in HTML
782          */
783         public static function getPostsFromUrl($contact_url)
784         {
785                 $a = self::getApp();
786
787                 require_once 'include/conversation.php';
788
789                 // There are no posts with "uid = 0" with connector networks
790                 // This speeds up the query a lot
791                 $r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact`
792                         WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0", dbesc(normalise_link($contact_url)));
793
794                 if (!DBM::is_result($r)) {
795                         return '';
796                 }
797
798                 if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
799                         $sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND NOT `item`.`global`))";
800                 } else {
801                         $sql = "`item`.`uid` = %d";
802                 }
803
804                 $author_id = intval($r[0]["author-id"]);
805
806                 $contact = ($r[0]["contact-type"] == ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
807
808                 $r = q(item_query() . " AND `item`.`" . $contact . "` = %d AND " . $sql .
809                         " ORDER BY `item`.`created` DESC LIMIT %d, %d", intval($author_id), intval(local_user()), intval($a->pager['start']), intval($a->pager['itemspage'])
810                 );
811
812
813                 $o = conversation($a, $r, 'community', false);
814
815                 $o .= alt_pager($a, count($r));
816
817                 return $o;
818         }
819
820         /**
821          * @brief Returns the account type name
822          *
823          * The function can be called with either the user or the contact array
824          *
825          * @param array $contact contact or user array
826          * @return string
827          */
828         public static function getAccountType(array $contact)
829         {
830                 // There are several fields that indicate that the contact or user is a forum
831                 // "page-flags" is a field in the user table,
832                 // "forum" and "prv" are used in the contact table. They stand for PAGE_COMMUNITY and PAGE_PRVGROUP.
833                 // "community" is used in the gcontact table and is true if the contact is PAGE_COMMUNITY or PAGE_PRVGROUP.
834                 if ((isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_COMMUNITY))
835                         || (isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_PRVGROUP))
836                         || (isset($contact['forum']) && intval($contact['forum']))
837                         || (isset($contact['prv']) && intval($contact['prv']))
838                         || (isset($contact['community']) && intval($contact['community']))
839                 ) {
840                         $type = ACCOUNT_TYPE_COMMUNITY;
841                 } else {
842                         $type = ACCOUNT_TYPE_PERSON;
843                 }
844
845                 // The "contact-type" (contact table) and "account-type" (user table) are more general then the chaos from above.
846                 if (isset($contact["contact-type"])) {
847                         $type = $contact["contact-type"];
848                 }
849                 if (isset($contact["account-type"])) {
850                         $type = $contact["account-type"];
851                 }
852
853                 switch ($type) {
854                         case ACCOUNT_TYPE_ORGANISATION:
855                                 $account_type = t("Organisation");
856                                 break;
857                         case ACCOUNT_TYPE_NEWS:
858                                 $account_type = t('News');
859                                 break;
860                         case ACCOUNT_TYPE_COMMUNITY:
861                                 $account_type = t("Forum");
862                                 break;
863                         default:
864                                 $account_type = "";
865                                 break;
866                 }
867
868                 return $account_type;
869         }
870
871         /**
872          * @brief Blocks a contact
873          *
874          * @param int $uid
875          * @return bool
876          */
877         public static function block($uid)
878         {
879                 $return = dba::update('contact', ['blocked' => true], ['id' => $uid]);
880
881                 return $return;
882         }
883
884         /**
885          * @brief Unblocks a contact
886          *
887          * @param int $uid
888          * @return bool
889          */
890         public static function unblock($uid)
891         {
892                 $return = dba::update('contact', ['blocked' => false], ['id' => $uid]);
893
894                 return $return;
895   }
896
897   /**
898    * @brief Updates the avatar links in a contact only if needed
899          *
900          * @param string $avatar Link to avatar picture
901          * @param int    $uid    User id of contact owner
902          * @param int    $cid    Contact id
903          * @param bool   $force  force picture update
904          *
905          * @return array Returns array of the different avatar sizes
906          */
907         public static function updateAvatar($avatar, $uid, $cid, $force = false)
908         {
909                 // Limit = 1 returns the row so no need for dba:inArray()
910                 $r = dba::select('contact', array('avatar', 'photo', 'thumb', 'micro', 'nurl'), array('id' => $cid), array('limit' => 1));
911                 if (!DBM::is_result($r)) {
912                         return false;
913                 } else {
914                         $data = array($r["photo"], $r["thumb"], $r["micro"]);
915                 }
916
917                 if (($r["avatar"] != $avatar) || $force) {
918                         $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true);
919
920                         if ($photos) {
921                                 dba::update(
922                                         'contact',
923                                         array('avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => datetime_convert()),
924                                         array('id' => $cid)
925                                 );
926
927                                 // Update the public contact (contact id = 0)
928                                 if ($uid != 0) {
929                                         $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1));
930                                         if (DBM::is_result($pcontact)) {
931                                                 self::updateAvatar($avatar, 0, $pcontact['id'], $force);
932                                         }
933                                 }
934
935                                 return $photos;
936                         }
937                 }
938
939                 return $data;
940         }
941 }