]> git.mxchange.org Git - friendica.git/blob - src/Object/Contact.php
Revert "Move Objects to Model"
[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 or "self" contact? => nothing to do
145                 if ($contact['archive'] || $contact['self']) {
146                         return;
147                 }
148
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` <= ? AND NOT `self`', 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']), 'self' => false));
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                 $condition = array('`id` = ? AND (`term-date` > ? OR `archive`)', $contact['id'], NULL_DATE);
189                 $exists = dba::exists('contact', $condition);
190
191                 // We don't need to update, we never marked this contact for archival
192                 if (!$exists) {
193                         return;
194                 }
195
196                 // It's a miracle. Our dead contact has inexplicably come back to life.
197                 $fields = array('term-date' => NULL_DATE, 'archive' => false);
198                 dba::update('contact', $fields, array('id' => $contact['id']));
199
200                 if ($contact['url'] != '') {
201                         dba::update('contact', $fields, array('nurl' => normalise_link($contact['url'])));
202                 }
203         }
204
205         /**
206          * @brief Get contact data for a given profile link
207          *
208          * The function looks at several places (contact table and gcontact table) for the contact
209          * It caches its result for the same script execution to prevent duplicate calls
210          *
211          * @param string $url     The profile link
212          * @param int    $uid     User id
213          * @param array  $default If not data was found take this data as default value
214          *
215          * @return array Contact data
216          */
217         public static function getDetailsByURL($url, $uid = -1, array $default = [])
218         {
219                 static $cache = array();
220
221                 if ($url == '') {
222                         return $default;
223                 }
224
225                 if ($uid == -1) {
226                         $uid = local_user();
227                 }
228
229                 if (isset($cache[$url][$uid])) {
230                         return $cache[$url][$uid];
231                 }
232
233                 $ssl_url = str_replace('http://', 'https://', $url);
234
235                 // Fetch contact data from the contact table for the given user
236                 $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`,
237                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
238                 FROM `contact` WHERE `nurl` = ? AND `uid` = ?", normalise_link($url), $uid);
239                 $r = dba::inArray($s);
240
241                 // Fetch contact data from the contact table for the given user, checking with the alias
242                 if (!DBM::is_result($r)) {
243                         $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`,
244                                 `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
245                         FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ?", normalise_link($url), $url, $ssl_url, $uid);
246                         $r = dba::inArray($s);
247                 }
248
249                 // Fetch the data from the contact table with "uid=0" (which is filled automatically)
250                 if (!DBM::is_result($r)) {
251                         $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`,
252                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
253                         FROM `contact` WHERE `nurl` = ? AND `uid` = 0", normalise_link($url));
254                         $r = dba::inArray($s);
255                 }
256
257                 // Fetch the data from the contact table with "uid=0" (which is filled automatically) - checked with the alias
258                 if (!DBM::is_result($r)) {
259                         $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`,
260                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
261                         FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = 0", normalise_link($url), $url, $ssl_url);
262                         $r = dba::inArray($s);
263                 }
264
265                 // Fetch the data from the gcontact table
266                 if (!DBM::is_result($r)) {
267                         $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`,
268                         `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
269                         FROM `gcontact` WHERE `nurl` = ?", normalise_link($url));
270                         $r = dba::inArray($s);
271                 }
272
273                 if (DBM::is_result($r)) {
274                         // If there is more than one entry we filter out the connector networks
275                         if (count($r) > 1) {
276                                 foreach ($r as $id => $result) {
277                                         if ($result["network"] == NETWORK_STATUSNET) {
278                                                 unset($r[$id]);
279                                         }
280                                 }
281                         }
282
283                         $profile = array_shift($r);
284
285                         // "bd" always contains the upcoming birthday of a contact.
286                         // "birthday" might contain the birthday including the year of birth.
287                         if ($profile["birthday"] > '0001-01-01') {
288                                 $bd_timestamp = strtotime($profile["birthday"]);
289                                 $month = date("m", $bd_timestamp);
290                                 $day = date("d", $bd_timestamp);
291
292                                 $current_timestamp = time();
293                                 $current_year = date("Y", $current_timestamp);
294                                 $current_month = date("m", $current_timestamp);
295                                 $current_day = date("d", $current_timestamp);
296
297                                 $profile["bd"] = $current_year . "-" . $month . "-" . $day;
298                                 $current = $current_year . "-" . $current_month . "-" . $current_day;
299
300                                 if ($profile["bd"] < $current) {
301                                         $profile["bd"] = ( ++$current_year) . "-" . $month . "-" . $day;
302                                 }
303                         } else {
304                                 $profile["bd"] = '0001-01-01';
305                         }
306                 } else {
307                         $profile = $default;
308                 }
309
310                 if (($profile["photo"] == "") && isset($default["photo"])) {
311                         $profile["photo"] = $default["photo"];
312                 }
313
314                 if (($profile["name"] == "") && isset($default["name"])) {
315                         $profile["name"] = $default["name"];
316                 }
317
318                 if (($profile["network"] == "") && isset($default["network"])) {
319                         $profile["network"] = $default["network"];
320                 }
321
322                 if (($profile["thumb"] == "") && isset($profile["photo"])) {
323                         $profile["thumb"] = $profile["photo"];
324                 }
325
326                 if (($profile["micro"] == "") && isset($profile["thumb"])) {
327                         $profile["micro"] = $profile["thumb"];
328                 }
329
330                 if ((($profile["addr"] == "") || ($profile["name"] == "")) && ($profile["gid"] != 0)
331                         && in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS))
332                 ) {
333                         Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]);
334                 }
335
336                 // Show contact details of Diaspora contacts only if connected
337                 if (($profile["cid"] == 0) && ($profile["network"] == NETWORK_DIASPORA)) {
338                         $profile["location"] = "";
339                         $profile["about"] = "";
340                         $profile["gender"] = "";
341                         $profile["birthday"] = '0001-01-01';
342                 }
343
344                 $cache[$url][$uid] = $profile;
345
346                 return $profile;
347         }
348
349         /**
350          * @brief Get contact data for a given address
351          *
352          * The function looks at several places (contact table and gcontact table) for the contact
353          *
354          * @param string $addr The profile link
355          * @param int    $uid  User id
356          *
357          * @return array Contact data
358          */
359         public static function getDetailsByAddr($addr, $uid = -1)
360         {
361                 static $cache = array();
362
363                 if ($addr == '') {
364                         return array();
365                 }
366
367                 if ($uid == -1) {
368                         $uid = local_user();
369                 }
370
371                 // Fetch contact data from the contact table for the given user
372                 $r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
373                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
374                 FROM `contact` WHERE `addr` = '%s' AND `uid` = %d", dbesc($addr), intval($uid));
375
376                 // Fetch the data from the contact table with "uid=0" (which is filled automatically)
377                 if (!DBM::is_result($r))
378                         $r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
379                         `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
380                         FROM `contact` WHERE `addr` = '%s' AND `uid` = 0", dbesc($addr));
381
382                 // Fetch the data from the gcontact table
383                 if (!DBM::is_result($r))
384                         $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`,
385                         `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
386                         FROM `gcontact` WHERE `addr` = '%s'", dbesc($addr));
387
388                 if (!DBM::is_result($r)) {
389                         $data = Probe::uri($addr);
390
391                         $profile = self::getDetailsByURL($data['url'], $uid);
392                 } else {
393                         $profile = $r[0];
394                 }
395
396                 return $profile;
397         }
398
399         /**
400          * @brief Returns the data array for the photo menu of a given contact
401          *
402          * @param array $contact contact
403          * @param int   $uid     optional, default 0
404          * @return array
405          */
406         public static function photoMenu(array $contact, $uid = 0)
407         {
408                 // @todo Unused, to be removed
409                 $a = get_app();
410
411                 $contact_url = '';
412                 $pm_url = '';
413                 $status_link = '';
414                 $photos_link = '';
415                 $posts_link = '';
416                 $contact_drop_link = '';
417                 $poke_link = '';
418
419                 if ($uid == 0) {
420                         $uid = local_user();
421                 }
422
423                 if ($contact['uid'] != $uid) {
424                         if ($uid == 0) {
425                                 $profile_link = zrl($contact['url']);
426                                 $menu = array('profile' => array(t('View Profile'), $profile_link, true));
427
428                                 return $menu;
429                         }
430
431                         $r = dba::select('contact', array(), array('nurl' => $contact['nurl'], 'network' => $contact['network'], 'uid' => $uid), array('limit' => 1));
432                         if ($r) {
433                                 return self::photoMenu($r, $uid);
434                         } else {
435                                 $profile_link = zrl($contact['url']);
436                                 $connlnk = 'follow/?url=' . $contact['url'];
437                                 $menu = array(
438                                         'profile' => array(t('View Profile'), $profile_link, true),
439                                         'follow' => array(t('Connect/Follow'), $connlnk, true)
440                                 );
441
442                                 return $menu;
443                         }
444                 }
445
446                 $sparkle = false;
447                 if ($contact['network'] === NETWORK_DFRN) {
448                         $sparkle = true;
449                         $profile_link = System::baseUrl() . '/redir/' . $contact['id'];
450                 } else {
451                         $profile_link = $contact['url'];
452                 }
453
454                 if ($profile_link === 'mailbox') {
455                         $profile_link = '';
456                 }
457
458                 if ($sparkle) {
459                         $status_link = $profile_link . '?url=status';
460                         $photos_link = $profile_link . '?url=photos';
461                         $profile_link = $profile_link . '?url=profile';
462                 }
463
464                 if (in_array($contact['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
465                         $pm_url = System::baseUrl() . '/message/new/' . $contact['id'];
466                 }
467
468                 if ($contact['network'] == NETWORK_DFRN) {
469                         $poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id'];
470                 }
471
472                 $contact_url = System::baseUrl() . '/contacts/' . $contact['id'];
473
474                 $posts_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/posts';
475                 $contact_drop_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/drop?confirm=1';
476
477                 /**
478                  * Menu array:
479                  * "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ]
480                  */
481                 $menu = array(
482                         'status' => array(t("View Status"), $status_link, true),
483                         'profile' => array(t("View Profile"), $profile_link, true),
484                         'photos' => array(t("View Photos"), $photos_link, true),
485                         'network' => array(t("Network Posts"), $posts_link, false),
486                         'edit' => array(t("View Contact"), $contact_url, false),
487                         'drop' => array(t("Drop Contact"), $contact_drop_link, false),
488                         'pm' => array(t("Send PM"), $pm_url, false),
489                         'poke' => array(t("Poke"), $poke_link, false),
490                 );
491
492
493                 $args = array('contact' => $contact, 'menu' => &$menu);
494
495                 call_hooks('contact_photo_menu', $args);
496
497                 $menucondensed = array();
498
499                 foreach ($menu as $menuname => $menuitem) {
500                         if ($menuitem[1] != '') {
501                                 $menucondensed[$menuname] = $menuitem;
502                         }
503                 }
504
505                 return $menucondensed;
506         }
507
508         /**
509          * @brief Returns ungrouped contact count or list for user
510          *
511          * Returns either the total number of ungrouped contacts for the given user
512          * id or a paginated list of ungrouped contacts.
513          *
514          * @param int $uid   uid
515          * @param int $start optional, default 0
516          * @param int $count optional, default 0
517          *
518          * @return array
519          */
520         public static function getUngroupedList($uid, $start = 0, $count = 0)
521         {
522                 if (!$count) {
523                         $r = q(
524                                 "SELECT COUNT(*) AS `total`
525                                  FROM `contact`
526                                  WHERE `uid` = %d
527                                  AND NOT `self`
528                                  AND NOT `blocked`
529                                  AND NOT `pending`
530                                  AND `id` NOT IN (
531                                         SELECT DISTINCT(`contact-id`)
532                                         FROM `group_member`
533                                         WHERE `uid` = %d
534                                 )", intval($uid), intval($uid)
535                         );
536
537                         return $r;
538                 }
539
540                 $r = q(
541                         "SELECT *
542                         FROM `contact`
543                         WHERE `uid` = %d
544                         AND NOT `self`
545                         AND NOT `blocked`
546                         AND NOT `pending`
547                         AND `id` NOT IN (
548                                 SELECT DISTINCT(`contact-id`)
549                                 FROM `group_member` WHERE `uid` = %d
550                         )
551                         LIMIT %d, %d", intval($uid), intval($uid), intval($start), intval($count)
552                 );
553                 return $r;
554         }
555
556         /**
557          * @brief Fetch the contact id for a given url and user
558          *
559          * First lookup in the contact table to find a record matching either `url`, `nurl`,
560          * `addr` or `alias`.
561          *
562          * If there's no record and we aren't looking for a public contact, we quit.
563          * If there's one, we check that it isn't time to update the picture else we
564          * directly return the found contact id.
565          *
566          * Second, we probe the provided $url wether it's http://server.tld/profile or
567          * nick@server.tld. We quit if we can't get any info back.
568          *
569          * Third, we create the contact record if it doesn't exist
570          *
571          * Fourth, we update the existing record with the new data (avatar, alias, nick)
572          * if there's any updates
573          *
574          * @param string  $url       Contact URL
575          * @param integer $uid       The user id for the contact (0 = public contact)
576          * @param boolean $no_update Don't update the contact
577          *
578          * @return integer Contact ID
579          */
580         public static function getIdForURL($url, $uid = 0, $no_update = false)
581         {
582                 logger("Get contact data for url " . $url . " and user " . $uid . " - " . System::callstack(), LOGGER_DEBUG);
583
584                 $contact_id = 0;
585
586                 if ($url == '') {
587                         return 0;
588                 }
589
590                 /// @todo Verify if we can't use Contact::getDetailsByUrl instead of the following
591                 // We first try the nurl (http://server.tld/nick), most common case
592                 $contact = dba::select('contact', array('id', 'avatar-date'), array('nurl' => normalise_link($url), 'uid' => $uid), array('limit' => 1));
593
594                 // Then the addr (nick@server.tld)
595                 if (!DBM::is_result($contact)) {
596                         $contact = dba::select('contact', array('id', 'avatar-date'), array('addr' => $url, 'uid' => $uid), array('limit' => 1));
597                 }
598
599                 // Then the alias (which could be anything)
600                 if (!DBM::is_result($contact)) {
601                         // The link could be provided as http although we stored it as https
602                         $ssl_url = str_replace('http://', 'https://', $url);
603                         $r = dba::select('contact', array('id', 'avatar-date'), array('`alias` IN (?, ?, ?) AND `uid` = ?', $url, normalise_link($url), $ssl_url, $uid), array('limit' => 1));
604                         $contact = dba::fetch($r);
605                         dba::close($r);
606                 }
607
608                 if (DBM::is_result($contact)) {
609                         $contact_id = $contact["id"];
610
611                         // Update the contact every 7 days
612                         $update_contact = ($contact['avatar-date'] < datetime_convert('', '', 'now -7 days'));
613
614                         // We force the update if the avatar is empty
615                         if ($contact['avatar'] == '') {
616                                 $update_contact = true;
617                         }
618
619                         if (!$update_contact || $no_update) {
620                                 return $contact_id;
621                         }
622                 } elseif ($uid != 0) {
623                         // Non-existing user-specific contact, exiting
624                         return 0;
625                 }
626
627                 $data = Probe::uri($url, "", $uid);
628
629                 // Last try in gcontact for unsupported networks
630                 if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA, NETWORK_PUMPIO, NETWORK_MAIL))) {
631                         if ($uid != 0) {
632                                 return 0;
633                         }
634
635                         // Get data from the gcontact table
636                         $gcontacts = dba::select('gcontact', array('name', 'nick', 'url', 'photo', 'addr', 'alias', 'network'), array('nurl' => normalise_link($url)), array('limit' => 1));
637                         if (!DBM::is_result($gcontacts)) {
638                                 return 0;
639                         }
640
641                         $data = array_merge($data, $gcontacts);
642                 }
643
644                 if (!$contact_id && ($data["alias"] != '') && ($data["alias"] != $url)) {
645                         $contact_id = self::getIdForURL($data["alias"], $uid, true);
646                 }
647
648                 $url = $data["url"];
649                 if (!$contact_id) {
650                         dba::insert(
651                                 'contact', array('uid' => $uid, 'created' => datetime_convert(), 'url' => $data["url"],
652                                 'nurl' => normalise_link($data["url"]), 'addr' => $data["addr"],
653                                 'alias' => $data["alias"], 'notify' => $data["notify"], 'poll' => $data["poll"],
654                                 'name' => $data["name"], 'nick' => $data["nick"], 'photo' => $data["photo"],
655                                 'keywords' => $data["keywords"], 'location' => $data["location"], 'about' => $data["about"],
656                                 'network' => $data["network"], 'pubkey' => $data["pubkey"],
657                                 'rel' => CONTACT_IS_SHARING, 'priority' => $data["priority"],
658                                 'batch' => $data["batch"], 'request' => $data["request"],
659                                 'confirm' => $data["confirm"], 'poco' => $data["poco"],
660                                 'name-date' => datetime_convert(), 'uri-date' => datetime_convert(),
661                                 'avatar-date' => datetime_convert(), 'writable' => 1, 'blocked' => 0,
662                                 'readonly' => 0, 'pending' => 0)
663                         );
664
665                         $s = dba::select('contact', array('id'), array('nurl' => normalise_link($data["url"]), 'uid' => $uid), array('order' => array('id'), 'limit' => 2));
666                         $contacts = dba::inArray($s);
667                         if (!DBM::is_result($contacts)) {
668                                 return 0;
669                         }
670
671                         $contact_id = $contacts[0]["id"];
672
673                         // Update the newly created contact from data in the gcontact table
674                         $gcontact = dba::select('gcontact', array('location', 'about', 'keywords', 'gender'), array('nurl' => normalise_link($data["url"])), array('limit' => 1));
675                         if (DBM::is_result($gcontact)) {
676                                 // Only use the information when the probing hadn't fetched these values
677                                 if ($data['keywords'] != '') {
678                                         unset($gcontact['keywords']);
679                                 }
680                                 if ($data['location'] != '') {
681                                         unset($gcontact['location']);
682                                 }
683                                 if ($data['about'] != '') {
684                                         unset($gcontact['about']);
685                                 }
686                                 dba::update('contact', $gcontact, array('id' => $contact_id));
687                         }
688
689                         if (count($contacts) > 1 && $uid == 0 && $contact_id != 0 && $data["url"] != "") {
690                                 dba::delete('contact', array("`nurl` = ? AND `uid` = 0 AND `id` != ? AND NOT `self`",
691                                         normalise_link($data["url"]), $contact_id));
692                         }
693                 }
694
695                 self::updateAvatar($data["photo"], $uid, $contact_id);
696
697                 $fields = array('url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date', 'pubkey');
698                 $contact = dba::select('contact', $fields, 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                 // Only fill the pubkey if it was empty before. We have to prevent identity theft.
713                 if (!empty($contact['pubkey'])) {
714                         unset($contact['pubkey']);
715                 } else {
716                         $updated['pubkey'] = $data['pubkey'];
717                 }
718
719                 if ($data['keywords'] != '') {
720                         $updated['keywords'] = $data['keywords'];
721                 }
722                 if ($data['location'] != '') {
723                         $updated['location'] = $data['location'];
724                 }
725                 if ($data['about'] != '') {
726                         $updated['about'] = $data['about'];
727                 }
728
729                 if (($data["addr"] != $contact["addr"]) || ($data["alias"] != $contact["alias"])) {
730                         $updated['uri-date'] = datetime_convert();
731                 }
732                 if (($data["name"] != $contact["name"]) || ($data["nick"] != $contact["nick"])) {
733                         $updated['name-date'] = datetime_convert();
734                 }
735
736                 $updated['avatar-date'] = datetime_convert();
737
738                 dba::update('contact', $updated, array('id' => $contact_id), $contact);
739
740                 return $contact_id;
741         }
742
743         /**
744          * @brief Checks if the contact is blocked
745          *
746          * @param int $cid contact id
747          *
748          * @return boolean Is the contact blocked?
749          */
750         public static function isBlocked($cid)
751         {
752                 if ($cid == 0) {
753                         return false;
754                 }
755
756                 $blocked = dba::select('contact', array('blocked'), array('id' => $cid), array('limit' => 1));
757                 if (!DBM::is_result($blocked)) {
758                         return false;
759                 }
760                 return (bool) $blocked['blocked'];
761         }
762
763         /**
764          * @brief Checks if the contact is hidden
765          *
766          * @param int $cid contact id
767          *
768          * @return boolean Is the contact hidden?
769          */
770         public static function isHidden($cid)
771         {
772                 if ($cid == 0) {
773                         return false;
774                 }
775
776                 $hidden = dba::select('contact', array('hidden'), array('id' => $cid), array('limit' => 1));
777                 if (!DBM::is_result($hidden)) {
778                         return false;
779                 }
780                 return (bool) $hidden['hidden'];
781         }
782
783         /**
784          * @brief Returns posts from a given contact url
785          *
786          * @param string $contact_url Contact URL
787          *
788          * @return string posts in HTML
789          */
790         public static function getPostsFromUrl($contact_url)
791         {
792                 $a = self::getApp();
793
794                 require_once 'include/conversation.php';
795
796                 // There are no posts with "uid = 0" with connector networks
797                 // This speeds up the query a lot
798                 $r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact`
799                         WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0", dbesc(normalise_link($contact_url)));
800
801                 if (!DBM::is_result($r)) {
802                         return '';
803                 }
804
805                 if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
806                         $sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND NOT `item`.`global`))";
807                 } else {
808                         $sql = "`item`.`uid` = %d";
809                 }
810
811                 $author_id = intval($r[0]["author-id"]);
812
813                 $contact = ($r[0]["contact-type"] == ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
814
815                 $r = q(item_query() . " AND `item`.`" . $contact . "` = %d AND " . $sql .
816                         " ORDER BY `item`.`created` DESC LIMIT %d, %d", intval($author_id), intval(local_user()), intval($a->pager['start']), intval($a->pager['itemspage'])
817                 );
818
819
820                 $o = conversation($a, $r, 'community', false);
821
822                 $o .= alt_pager($a, count($r));
823
824                 return $o;
825         }
826
827         /**
828          * @brief Returns the account type name
829          *
830          * The function can be called with either the user or the contact array
831          *
832          * @param array $contact contact or user array
833          * @return string
834          */
835         public static function getAccountType(array $contact)
836         {
837                 // There are several fields that indicate that the contact or user is a forum
838                 // "page-flags" is a field in the user table,
839                 // "forum" and "prv" are used in the contact table. They stand for PAGE_COMMUNITY and PAGE_PRVGROUP.
840                 // "community" is used in the gcontact table and is true if the contact is PAGE_COMMUNITY or PAGE_PRVGROUP.
841                 if ((isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_COMMUNITY))
842                         || (isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_PRVGROUP))
843                         || (isset($contact['forum']) && intval($contact['forum']))
844                         || (isset($contact['prv']) && intval($contact['prv']))
845                         || (isset($contact['community']) && intval($contact['community']))
846                 ) {
847                         $type = ACCOUNT_TYPE_COMMUNITY;
848                 } else {
849                         $type = ACCOUNT_TYPE_PERSON;
850                 }
851
852                 // The "contact-type" (contact table) and "account-type" (user table) are more general then the chaos from above.
853                 if (isset($contact["contact-type"])) {
854                         $type = $contact["contact-type"];
855                 }
856                 if (isset($contact["account-type"])) {
857                         $type = $contact["account-type"];
858                 }
859
860                 switch ($type) {
861                         case ACCOUNT_TYPE_ORGANISATION:
862                                 $account_type = t("Organisation");
863                                 break;
864                         case ACCOUNT_TYPE_NEWS:
865                                 $account_type = t('News');
866                                 break;
867                         case ACCOUNT_TYPE_COMMUNITY:
868                                 $account_type = t("Forum");
869                                 break;
870                         default:
871                                 $account_type = "";
872                                 break;
873                 }
874
875                 return $account_type;
876         }
877
878         /**
879          * @brief Blocks a contact
880          *
881          * @param int $uid
882          * @return bool
883          */
884         public static function block($uid)
885         {
886                 $return = dba::update('contact', ['blocked' => true], ['id' => $uid]);
887
888                 return $return;
889         }
890
891         /**
892          * @brief Unblocks a contact
893          *
894          * @param int $uid
895          * @return bool
896          */
897         public static function unblock($uid)
898         {
899                 $return = dba::update('contact', ['blocked' => false], ['id' => $uid]);
900
901                 return $return;
902   }
903
904   /**
905    * @brief Updates the avatar links in a contact only if needed
906          *
907          * @param string $avatar Link to avatar picture
908          * @param int    $uid    User id of contact owner
909          * @param int    $cid    Contact id
910          * @param bool   $force  force picture update
911          *
912          * @return array Returns array of the different avatar sizes
913          */
914         public static function updateAvatar($avatar, $uid, $cid, $force = false)
915         {
916                 // Limit = 1 returns the row so no need for dba:inArray()
917                 $r = dba::select('contact', array('avatar', 'photo', 'thumb', 'micro', 'nurl'), array('id' => $cid), array('limit' => 1));
918                 if (!DBM::is_result($r)) {
919                         return false;
920                 } else {
921                         $data = array($r["photo"], $r["thumb"], $r["micro"]);
922                 }
923
924                 if (($r["avatar"] != $avatar) || $force) {
925                         $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true);
926
927                         if ($photos) {
928                                 dba::update(
929                                         'contact',
930                                         array('avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => datetime_convert()),
931                                         array('id' => $cid)
932                                 );
933
934                                 // Update the public contact (contact id = 0)
935                                 if ($uid != 0) {
936                                         $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1));
937                                         if (DBM::is_result($pcontact)) {
938                                                 self::updateAvatar($avatar, 0, $pcontact['id'], $force);
939                                         }
940                                 }
941
942                                 return $photos;
943                         }
944                 }
945
946                 return $data;
947         }
948 }