diff --git a/boot.php b/boot.php index 402273bddf..698587eb59 100644 --- a/boot.php +++ b/boot.php @@ -27,6 +27,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Util\Lock; require_once 'include/network.php'; diff --git a/include/acl_selectors.php b/include/acl_selectors.php index a18a1b33a7..f206685784 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -8,6 +8,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; require_once "include/contact_selectors.php"; require_once "include/contact_widgets.php"; diff --git a/include/api.php b/include/api.php index d25aeb8c5e..7303524232 100644 --- a/include/api.php +++ b/include/api.php @@ -12,6 +12,7 @@ use Friendica\Core\Config; use Friendica\Core\NotificationsManager; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Protocol\Diaspora; use Friendica\Util\XML; diff --git a/include/bb2diaspora.php b/include/bb2diaspora.php index 20309b9d20..2188cf8c70 100644 --- a/include/bb2diaspora.php +++ b/include/bb2diaspora.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Network\Probe; +use Friendica\Object\Contact; use League\HTMLToMarkdown\HtmlConverter; diff --git a/include/bbcode.php b/include/bbcode.php index 89311e7755..609ca922bc 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -5,6 +5,7 @@ use Friendica\Content\Smilies; use Friendica\Core\Cache; use Friendica\Core\System; use Friendica\Core\Config; +use Friendica\Object\Contact; require_once 'include/oembed.php'; require_once 'include/event.php'; diff --git a/include/conversation.php b/include/conversation.php index 3eae184a82..aea0855695 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -7,6 +7,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Object\Conversation; use Friendica\Object\Item; diff --git a/include/identity.php b/include/identity.php index 6991a8dcc9..31c17ef21c 100644 --- a/include/identity.php +++ b/include/identity.php @@ -10,6 +10,7 @@ use Friendica\Core\PConfig; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/ForumManager.php'; require_once 'include/bbcode.php'; diff --git a/include/items.php b/include/items.php index ed2204f608..63ecc791b4 100644 --- a/include/items.php +++ b/include/items.php @@ -12,6 +12,7 @@ use Friendica\Core\Worker; use Friendica\Core\System; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; use Friendica\Protocol\DFRN; use Friendica\Protocol\OStatus; use Friendica\Util\Lock; diff --git a/include/like.php b/include/like.php index 5e05121f69..e8d69689b5 100644 --- a/include/like.php +++ b/include/like.php @@ -4,6 +4,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Protocol\Diaspora; /** diff --git a/include/post_update.php b/include/post_update.php index 8a346b7a28..f77cb01846 100644 --- a/include/post_update.php +++ b/include/post_update.php @@ -6,6 +6,7 @@ use Friendica\Core\Config; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; /** * @brief Calls the post update functions diff --git a/include/threads.php b/include/threads.php index 16d4915fdf..a1a6c78251 100644 --- a/include/threads.php +++ b/include/threads.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; function add_thread($itemid, $onlyshadow = false) { $items = q("SELECT `uid`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, diff --git a/mod/admin.php b/mod/admin.php index ee456a23f0..67313e0603 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -11,6 +11,7 @@ use Friendica\Core\System; use Friendica\Core\Config; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Model\User; require_once("include/enotify.php"); require_once("include/text.php"); diff --git a/mod/allfriends.php b/mod/allfriends.php index 0a6989e4d0..13a85e6aa4 100644 --- a/mod/allfriends.php +++ b/mod/allfriends.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; require_once 'include/Contact.php'; require_once 'include/contact_selectors.php'; diff --git a/mod/cal.php b/mod/cal.php index 1bfc8d95d9..f3cb9f6335 100644 --- a/mod/cal.php +++ b/mod/cal.php @@ -11,6 +11,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once('include/event.php'); require_once('include/redir.php'); diff --git a/mod/common.php b/mod/common.php index ab5343093a..94c8847713 100644 --- a/mod/common.php +++ b/mod/common.php @@ -5,6 +5,7 @@ use Friendica\App; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; require_once 'include/Contact.php'; require_once 'include/contact_selectors.php'; diff --git a/mod/contacts.php b/mod/contacts.php index 10a4f6b1b8..dba7680bad 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -6,6 +6,7 @@ use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; +use Friendica\Object\Contact; require_once 'include/Contact.php'; require_once 'include/contact_selectors.php'; diff --git a/mod/crepair.php b/mod/crepair.php index a73429e157..f9c25845d0 100644 --- a/mod/crepair.php +++ b/mod/crepair.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once("include/contact_selectors.php"); require_once("mod/contacts.php"); diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php index 15cda13adf..74f6d4e9f4 100644 --- a/mod/dfrn_notify.php +++ b/mod/dfrn_notify.php @@ -9,6 +9,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Protocol\DFRN; require_once 'include/items.php'; diff --git a/mod/directory.php b/mod/directory.php index b32a58b359..269dc03d35 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Database\DBM; +use Friendica\Object\Contact; function directory_init(App $a) { $a->set_pager_itemspage(60); diff --git a/mod/dirfind.php b/mod/dirfind.php index 89df7c885f..c8a9709ab2 100644 --- a/mod/dirfind.php +++ b/mod/dirfind.php @@ -8,6 +8,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; +use Friendica\Object\Contact; use Friendica\Protocol\PortableContact; require_once 'include/contact_widgets.php'; diff --git a/mod/display.php b/mod/display.php index 570582343a..f36266e683 100644 --- a/mod/display.php +++ b/mod/display.php @@ -4,6 +4,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Protocol\DFRN; function display_init(App $a) { diff --git a/mod/follow.php b/mod/follow.php index 38ec83dc0f..b3a503ec06 100644 --- a/mod/follow.php +++ b/mod/follow.php @@ -4,6 +4,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Core\System; use Friendica\Network\Probe; +use Friendica\Object\Contact; require_once 'include/follow.php'; require_once 'include/Contact.php'; diff --git a/mod/hovercard.php b/mod/hovercard.php index a5a41e2635..a1d861067e 100644 --- a/mod/hovercard.php +++ b/mod/hovercard.php @@ -13,6 +13,7 @@ use Friendica\Core\Config; use Friendica\Model\GlobalContact; require_once "include/Contact.php"; +use Friendica\Object\Contact; function hovercard_init(App $a) { // Just for testing purposes diff --git a/mod/item.php b/mod/item.php index 2fcaa6c3f2..040ea76873 100644 --- a/mod/item.php +++ b/mod/item.php @@ -22,6 +22,7 @@ use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; use Friendica\Network\Probe; +use Friendica\Object\Contact; use Friendica\Protocol\Diaspora; require_once 'include/crypto.php'; diff --git a/mod/match.php b/mod/match.php index 7258fd9c70..b5e1ea31fa 100644 --- a/mod/match.php +++ b/mod/match.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/text.php'; require_once 'include/contact_widgets.php'; diff --git a/mod/message.php b/mod/message.php index 07145a6beb..ecd377b7d0 100644 --- a/mod/message.php +++ b/mod/message.php @@ -4,6 +4,7 @@ use Friendica\App; use Friendica\Content\Smilies; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/acl_selectors.php'; require_once 'include/message.php'; diff --git a/mod/network.php b/mod/network.php index 662c306f59..8008fcc060 100644 --- a/mod/network.php +++ b/mod/network.php @@ -5,6 +5,7 @@ use Friendica\Core\System; use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/conversation.php'; require_once 'include/group.php'; diff --git a/mod/nogroup.php b/mod/nogroup.php index e212b30dc1..d88722e3f8 100644 --- a/mod/nogroup.php +++ b/mod/nogroup.php @@ -4,6 +4,7 @@ */ use Friendica\App; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/Contact.php'; require_once 'include/contact_selectors.php'; diff --git a/mod/photos.php b/mod/photos.php index 1546bd9a87..3efa4a12e9 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -6,6 +6,7 @@ use Friendica\Core\Config; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Network\Probe; +use Friendica\Object\Contact; require_once 'include/Photo.php'; require_once 'include/photos.php'; diff --git a/mod/ping.php b/mod/ping.php index 00ee848dc4..e722295b2a 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -7,6 +7,7 @@ use Friendica\Core\Cache; use Friendica\Core\System; use Friendica\Core\PConfig; use Friendica\Database\DBM; +use Friendica\Object\Contact; use Friendica\Util\XML; require_once 'include/datetime.php'; diff --git a/mod/profiles.php b/mod/profiles.php index bbce17c33b..835dd59f10 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -12,6 +12,7 @@ use Friendica\Model\GlobalContact; use Friendica\Network\Probe; require_once 'include/Contact.php'; +use Friendica\Object\Profile; function profiles_init(App $a) { diff --git a/mod/randprof.php b/mod/randprof.php index f835780e07..79057b4955 100644 --- a/mod/randprof.php +++ b/mod/randprof.php @@ -2,6 +2,7 @@ use Friendica\App; use Friendica\Core\System; +use Friendica\Model\GlobalContact; function randprof_init(App $a) { require_once('include/Contact.php'); diff --git a/mod/removeme.php b/mod/removeme.php index 5dcd33e8f5..dcb07a5bee 100644 --- a/mod/removeme.php +++ b/mod/removeme.php @@ -2,6 +2,7 @@ use Friendica\App; use Friendica\Core\System; +use Friendica\Model\User; function removeme_post(App $a) { diff --git a/mod/suggest.php b/mod/suggest.php index bb23fc8cba..cb2b3f9c44 100644 --- a/mod/suggest.php +++ b/mod/suggest.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Database\DBM; use Friendica\Model\GlobalContact; +use Friendica\Object\Contact; require_once 'include/contact_widgets.php'; diff --git a/mod/unfollow.php b/mod/unfollow.php index 58b4397ca5..28af7f86e6 100644 --- a/mod/unfollow.php +++ b/mod/unfollow.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/follow.php'; require_once 'include/Contact.php'; diff --git a/mod/videos.php b/mod/videos.php index a46f5de1ab..662da4ca4b 100644 --- a/mod/videos.php +++ b/mod/videos.php @@ -5,6 +5,7 @@ use Friendica\Core\Config; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once('include/items.php'); require_once('include/acl_selectors.php'); diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php index 8c35be77d8..4747330d6c 100644 --- a/mod/viewcontacts.php +++ b/mod/viewcontacts.php @@ -3,6 +3,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once('include/Contact.php'); require_once('include/contact_selectors.php'); diff --git a/src/Core/NotificationsManager.php b/src/Core/NotificationsManager.php index 318d5a8e80..5e2f1ad1f2 100644 --- a/src/Core/NotificationsManager.php +++ b/src/Core/NotificationsManager.php @@ -9,6 +9,7 @@ namespace Friendica\Core; use Friendica\Core\Pconfig; use Friendica\Core\System; use Friendica\Database\DBM; +use Friendica\Object\Contact; require_once 'include/html2plain.php'; require_once 'include/datetime.php'; diff --git a/src/Model/GlobalContact.php b/src/Model/GlobalContact.php index 1ca814789b..dc30c8b692 100644 --- a/src/Model/GlobalContact.php +++ b/src/Model/GlobalContact.php @@ -10,6 +10,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; use Friendica\Network\Probe; +use Friendica\Object\Profile; use Friendica\Protocol\PortableContact; use dba; use Exception; diff --git a/src/Model/User.php b/src/Model/User.php new file mode 100644 index 0000000000..ec4d1013b4 --- /dev/null +++ b/src/Model/User.php @@ -0,0 +1,50 @@ + $uid), array("limit" => 1)); + + call_hooks('remove_user', $r); + + // save username (actually the nickname as it is guaranteed + // unique), so it cannot be re-registered in the future. + + dba::insert('userd', array('username' => $r['nickname'])); + + // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php) + q("UPDATE `user` SET `account_removed` = 1, `account_expires_on` = UTC_TIMESTAMP() WHERE `uid` = %d", intval($uid)); + Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid); + + // Send an update to the directory + Worker::add(PRIORITY_LOW, "Directory", $r['url']); + + if ($uid == local_user()) { + unset($_SESSION['authenticated']); + unset($_SESSION['uid']); + goaway(System::baseUrl()); + } + } +} diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 37ca51d8ab..b758956123 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -13,6 +13,7 @@ use Friendica\Core\System; use Friendica\Core\Cache; use Friendica\Core\Config; use Friendica\Database\DBM; +use Friendica\Object\Profile; use Friendica\Util\XML; use dba; diff --git a/src/Object/Contact.php b/src/Object/Contact.php new file mode 100644 index 0000000000..2b8a11640e --- /dev/null +++ b/src/Object/Contact.php @@ -0,0 +1,830 @@ + $id)); + + // Delete the rest in the background + Worker::add(PRIORITY_LOW, 'RemoveContact', $id); + } + + /** + * @brief Sends an unfriend message. Does not remove the contact + * + * @param array $user User unfriending + * @param array $contact Contact unfriended + */ + public static function terminateFriendship(array $user, array $contact) + { + if ($contact['network'] === NETWORK_OSTATUS) { + // create an unfollow slap + $item = array(); + $item['verb'] = NAMESPACE_OSTATUS . "/unfollow"; + $item['follow'] = $contact["url"]; + $slap = OStatus::salmon($item, $user); + + if ((x($contact, 'notify')) && (strlen($contact['notify']))) { + require_once 'include/salmon.php'; + slapper($user, $contact['notify'], $slap); + } + } elseif ($contact['network'] === NETWORK_DIASPORA) { + Diaspora::sendUnshare($user, $contact); + } elseif ($contact['network'] === NETWORK_DFRN) { + DFRN::deliver($user, $contact, 'placeholder', 1); + } + } + + /** + * @brief Marks a contact for archival after a communication issue delay + * + * Contact has refused to recognise us as a friend. We will start a countdown. + * If they still don't recognise us in 32 days, the relationship is over, + * and we won't waste any more time trying to communicate with them. + * This provides for the possibility that their database is temporarily messed + * up or some other transient event and that there's a possibility we could recover from it. + * + * @param array $contact + * @return type + */ + public static function markForArchival(array $contact) + { + // Contact already archived, nothing to do + if ($contact['archive']) { + return; + } + + if ($contact['term-date'] <= NULL_DATE) { + q( + "UPDATE `contact` SET `term-date` = '%s' WHERE `id` = %d", dbesc(datetime_convert()), intval($contact['id']) + ); + + if ($contact['url'] != '') { + q( + "UPDATE `contact` SET `term-date` = '%s' + WHERE `nurl` = '%s' AND `term-date` <= '1000-00-00'", dbesc(datetime_convert()), dbesc(normalise_link($contact['url'])) + ); + } + } else { + + /* @todo + * We really should send a notification to the owner after 2-3 weeks + * so they won't be surprised when the contact vanishes and can take + * remedial action if this was a serious mistake or glitch + */ + + /// @todo Check for contact vitality via probing + $expiry = $contact['term-date'] . ' + 32 days '; + if (datetime_convert() > datetime_convert('UTC', 'UTC', $expiry)) { + + /* Relationship is really truly dead. archive them rather than + * delete, though if the owner tries to unarchive them we'll start + * the whole process over again. + */ + q( + "UPDATE `contact` SET `archive` = 1 WHERE `id` = %d", intval($contact['id']) + ); + + if ($contact['url'] != '') { + q( + "UPDATE `contact` SET `archive` = 1 WHERE `nurl` = '%s'", dbesc(normalise_link($contact['url'])) + ); + } + } + } + } + + /** + * @brief Cancels the archival countdown + * + * @see Contact::markForArchival() + * + * @param array $contact + * @return null + */ + public static function unmarkForArchival(array $contact) + { + $r = q( + "SELECT `term-date` FROM `contact` WHERE `id` = %d AND (`term-date` > '%s' OR `archive`)", intval($contact['id']), dbesc('1000-00-00 00:00:00') + ); + + // We don't need to update, we never marked this contact for archival + if (!DBM::is_result($r)) { + return; + } + + // It's a miracle. Our dead contact has inexplicably come back to life. + $fields = array('term-date' => NULL_DATE, 'archive' => false); + dba::update('contact', $fields, array('id' => $contact['id'])); + + if ($contact['url'] != '') { + dba::update('contact', $fields, array('nurl' => normalise_link($contact['url']))); + } + } + + /** + * @brief Get contact data for a given profile link + * + * The function looks at several places (contact table and gcontact table) for the contact + * It caches its result for the same script execution to prevent duplicate calls + * + * @param string $url The profile link + * @param int $uid User id + * @param array $default If not data was found take this data as default value + * + * @return array Contact data + */ + public static function getDetailsByURL($url, $uid = -1, array $default = []) + { + static $cache = array(); + + if ($url == '') { + return $default; + } + + if ($uid == -1) { + $uid = local_user(); + } + + if (isset($cache[$url][$uid])) { + return $cache[$url][$uid]; + } + + $ssl_url = str_replace('http://', 'https://', $url); + + // Fetch contact data from the contact table for the given user + $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`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self` + FROM `contact` WHERE `nurl` = ? AND `uid` = ?", normalise_link($url), $uid); + $r = dba::inArray($s); + + // Fetch contact data from the contact table for the given user, checking with the alias + if (!DBM::is_result($r)) { + $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`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self` + FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ?", normalise_link($url), $url, $ssl_url, $uid); + $r = dba::inArray($s); + } + + // Fetch the data from the contact table with "uid=0" (which is filled automatically) + if (!DBM::is_result($r)) { + $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`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self` + FROM `contact` WHERE `nurl` = ? AND `uid` = 0", normalise_link($url)); + $r = dba::inArray($s); + } + + // Fetch the data from the contact table with "uid=0" (which is filled automatically) - checked with the alias + if (!DBM::is_result($r)) { + $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`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self` + FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = 0", normalise_link($url), $url, $ssl_url); + $r = dba::inArray($s); + } + + // Fetch the data from the gcontact table + if (!DBM::is_result($r)) { + $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`, + `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self` + FROM `gcontact` WHERE `nurl` = ?", normalise_link($url)); + $r = dba::inArray($s); + } + + if (DBM::is_result($r)) { + // If there is more than one entry we filter out the connector networks + if (count($r) > 1) { + foreach ($r AS $id => $result) { + if ($result["network"] == NETWORK_STATUSNET) { + unset($r[$id]); + } + } + } + + $profile = array_shift($r); + + // "bd" always contains the upcoming birthday of a contact. + // "birthday" might contain the birthday including the year of birth. + if ($profile["birthday"] > '0001-01-01') { + $bd_timestamp = strtotime($profile["birthday"]); + $month = date("m", $bd_timestamp); + $day = date("d", $bd_timestamp); + + $current_timestamp = time(); + $current_year = date("Y", $current_timestamp); + $current_month = date("m", $current_timestamp); + $current_day = date("d", $current_timestamp); + + $profile["bd"] = $current_year . "-" . $month . "-" . $day; + $current = $current_year . "-" . $current_month . "-" . $current_day; + + if ($profile["bd"] < $current) { + $profile["bd"] = ( ++$current_year) . "-" . $month . "-" . $day; + } + } else { + $profile["bd"] = '0001-01-01'; + } + } else { + $profile = $default; + } + + if (($profile["photo"] == "") && isset($default["photo"])) { + $profile["photo"] = $default["photo"]; + } + + if (($profile["name"] == "") && isset($default["name"])) { + $profile["name"] = $default["name"]; + } + + if (($profile["network"] == "") && isset($default["network"])) { + $profile["network"] = $default["network"]; + } + + if (($profile["thumb"] == "") && isset($profile["photo"])) { + $profile["thumb"] = $profile["photo"]; + } + + if (($profile["micro"] == "") && isset($profile["thumb"])) { + $profile["micro"] = $profile["thumb"]; + } + + if ((($profile["addr"] == "") || ($profile["name"] == "")) && ($profile["gid"] != 0) && + in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS))) { + Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]); + } + + // Show contact details of Diaspora contacts only if connected + if (($profile["cid"] == 0) && ($profile["network"] == NETWORK_DIASPORA)) { + $profile["location"] = ""; + $profile["about"] = ""; + $profile["gender"] = ""; + $profile["birthday"] = '0001-01-01'; + } + + $cache[$url][$uid] = $profile; + + return $profile; + } + + /** + * @brief Get contact data for a given address + * + * The function looks at several places (contact table and gcontact table) for the contact + * + * @param string $addr The profile link + * @param int $uid User id + * + * @return array Contact data + */ + public static function getDetailsByAddr($addr, $uid = -1) + { + static $cache = array(); + + if ($addr == '') { + return array(); + } + + if ($uid == -1) { + $uid = local_user(); + } + + // Fetch contact data from the contact table for the given user + $r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self` + FROM `contact` WHERE `addr` = '%s' AND `uid` = %d", dbesc($addr), intval($uid)); + + // Fetch the data from the contact table with "uid=0" (which is filled automatically) + if (!DBM::is_result($r)) + $r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`, + `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self` + FROM `contact` WHERE `addr` = '%s' AND `uid` = 0", dbesc($addr)); + + // Fetch the data from the gcontact table + if (!DBM::is_result($r)) + $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`, + `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self` + FROM `gcontact` WHERE `addr` = '%s'", dbesc($addr)); + + if (!DBM::is_result($r)) { + $data = Probe::uri($addr); + + $profile = self::getDetailsByURL($data['url'], $uid); + } else { + $profile = $r[0]; + } + + return $profile; + } + + /** + * @brief Returns the data array for the photo menu of a given contact + * + * @param array $contact + * @param int $uid + * @return array + */ + public static function photoMenu(array $contact, $uid = 0) + { + // @todo Unused, to be removed + $a = get_app(); + + $contact_url = ''; + $pm_url = ''; + $status_link = ''; + $photos_link = ''; + $posts_link = ''; + $contact_drop_link = ''; + $poke_link = ''; + + if ($uid == 0) { + $uid = local_user(); + } + + if ($contact['uid'] != $uid) { + if ($uid == 0) { + $profile_link = zrl($contact['url']); + $menu = Array('profile' => array(t('View Profile'), $profile_link, true)); + + return $menu; + } + + $r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `network` = '%s' AND `uid` = %d", dbesc($contact['nurl']), dbesc($contact['network']), intval($uid)); + if ($r) { + return self::photoMenu($r[0], $uid); + } else { + $profile_link = zrl($contact['url']); + $connlnk = 'follow/?url=' . $contact['url']; + $menu = array( + 'profile' => array(t('View Profile'), $profile_link, true), + 'follow' => array(t('Connect/Follow'), $connlnk, true) + ); + + return $menu; + } + } + + $sparkle = false; + if ($contact['network'] === NETWORK_DFRN) { + $sparkle = true; + $profile_link = System::baseUrl() . '/redir/' . $contact['id']; + } else { + $profile_link = $contact['url']; + } + + if ($profile_link === 'mailbox') { + $profile_link = ''; + } + + if ($sparkle) { + $status_link = $profile_link . '?url=status'; + $photos_link = $profile_link . '?url=photos'; + $profile_link = $profile_link . '?url=profile'; + } + + if (in_array($contact['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) { + $pm_url = System::baseUrl() . '/message/new/' . $contact['id']; + } + + if ($contact['network'] == NETWORK_DFRN) { + $poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id']; + } + + $contact_url = System::baseUrl() . '/contacts/' . $contact['id']; + + $posts_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/posts'; + $contact_drop_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/drop?confirm=1'; + + /** + * menu array: + * "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ] + */ + $menu = array( + 'status' => array(t("View Status"), $status_link, true), + 'profile' => array(t("View Profile"), $profile_link, true), + 'photos' => array(t("View Photos"), $photos_link, true), + 'network' => array(t("Network Posts"), $posts_link, false), + 'edit' => array(t("View Contact"), $contact_url, false), + 'drop' => array(t("Drop Contact"), $contact_drop_link, false), + 'pm' => array(t("Send PM"), $pm_url, false), + 'poke' => array(t("Poke"), $poke_link, false), + ); + + + $args = array('contact' => $contact, 'menu' => &$menu); + + call_hooks('contact_photo_menu', $args); + + $menucondensed = array(); + + foreach ($menu AS $menuname => $menuitem) { + if ($menuitem[1] != '') { + $menucondensed[$menuname] = $menuitem; + } + } + + return $menucondensed; + } + + /** + * Returns either the total number of ungrouped contacts for the given user + * id or a paginated list of ungrouped contacts. + * + * @brief Returns ungrouped contact count or list for user + * + * @param int $uid + * @param int $start + * @param int $count + * @return array + */ + public static function getUngroupedList($uid, $start = 0, $count = 0) + { + if (!$count) { + $r = q( + "SELECT COUNT(*) AS `total` + FROM `contact` + WHERE `uid` = %d + AND `self` = 0 + AND `id` NOT IN ( + SELECT DISTINCT(`contact-id`) + FROM `group_member` + WHERE `uid` = %d + ) ", intval($uid), intval($uid) + ); + + return $r; + } + + $r = q( + "SELECT * + FROM `contact` + WHERE `uid` = %d + AND `self` = 0 + AND `id` NOT IN ( + SELECT DISTINCT(`contact-id`) + FROM `group_member` WHERE `uid` = %d + ) + AND `blocked` = 0 + AND `pending` = 0 + LIMIT %d, %d", intval($uid), intval($uid), intval($start), intval($count) + ); + + return $r; + } + + /** + * @brief Fetch the contact id for a given url and user + * + * First lookup in the contact table to find a record matching either `url`, `nurl`, + * `addr` or `alias`. + * + * If there's no record and we aren't looking for a public contact, we quit. + * If there's one, we check that it isn't time to update the picture else we + * directly return the found contact id. + * + * Second, we probe the provided $url wether it's http://server.tld/profile or + * nick@server.tld. We quit if we can't get any info back. + * + * Third, we create the contact record if it doesn't exist + * + * Fourth, we update the existing record with the new data (avatar, alias, nick) + * if there's any updates + * + * @param string $url Contact URL + * @param integer $uid The user id for the contact (0 = public contact) + * @param boolean $no_update Don't update the contact + * + * @return integer Contact ID + */ + public static function getIdForUrl($url, $uid = 0, $no_update = false) + { + logger("Get contact data for url " . $url . " and user " . $uid . " - " . System::callstack(), LOGGER_DEBUG); + + $contact_id = 0; + + if ($url == '') { + return 0; + } + + /// @todo Verify if we can't use Contact::getDetailsByUrl instead of the following + // We first try the nurl (http://server.tld/nick), most common case + $contact = dba::select('contact', array('id', 'avatar-date'), array('nurl' => normalise_link($url), 'uid' => $uid), array('limit' => 1)); + + // Then the addr (nick@server.tld) + if (!DBM::is_result($contact)) { + $contact = dba::select('contact', array('id', 'avatar-date'), array('addr' => $url, 'uid' => $uid), array('limit' => 1)); + } + + // Then the alias (which could be anything) + if (!DBM::is_result($contact)) { + // The link could be provided as http although we stored it as https + $ssl_url = str_replace('http://', 'https://', $url); + $r = dba::p("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ? LIMIT 1", $url, normalise_link($url), $ssl_url, $uid); + $contact = dba::fetch($r); + dba::close($r); + } + + if (DBM::is_result($contact)) { + $contact_id = $contact["id"]; + + // Update the contact every 7 days + $update_contact = ($contact['avatar-date'] < datetime_convert('', '', 'now -7 days')); + + // We force the update if the avatar is empty + if ($contact['avatar'] == '') { + $update_contact = true; + } + + if (!$update_contact || $no_update) { + return $contact_id; + } + } elseif ($uid != 0) { + // Non-existing user-specific contact, exiting + return 0; + } + + $data = Probe::uri($url, "", $uid); + + // Last try in gcontact for unsupported networks + if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA, NETWORK_PUMPIO, NETWORK_MAIL))) { + if ($uid != 0) { + return 0; + } + + // Get data from the gcontact table + $gcontacts = dba::select('gcontact', array('name', 'nick', 'url', 'photo', 'addr', 'alias', 'network'), array('nurl' => normalise_link($url)), array('limit' => 1)); + if (!DBM::is_result($gcontacts)) { + return 0; + } + + $data = array_merge($data, $gcontacts); + } + + if (!$contact_id && ($data["alias"] != '') && ($data["alias"] != $url)) { + $contact_id = self::getIdForUrl($data["alias"], $uid, true); + } + + $url = $data["url"]; + if (!$contact_id) { + dba::insert('contact', array('uid' => $uid, 'created' => datetime_convert(), 'url' => $data["url"], + 'nurl' => normalise_link($data["url"]), 'addr' => $data["addr"], + 'alias' => $data["alias"], 'notify' => $data["notify"], 'poll' => $data["poll"], + 'name' => $data["name"], 'nick' => $data["nick"], 'photo' => $data["photo"], + 'keywords' => $data["keywords"], 'location' => $data["location"], 'about' => $data["about"], + 'network' => $data["network"], 'pubkey' => $data["pubkey"], + 'rel' => CONTACT_IS_SHARING, 'priority' => $data["priority"], + 'batch' => $data["batch"], 'request' => $data["request"], + 'confirm' => $data["confirm"], 'poco' => $data["poco"], + 'name-date' => datetime_convert(), 'uri-date' => datetime_convert(), + 'avatar-date' => datetime_convert(), 'writable' => 1, 'blocked' => 0, + 'readonly' => 0, 'pending' => 0)); + + $contacts = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2", dbesc(normalise_link($data["url"])), intval($uid)); + if (!DBM::is_result($contacts)) { + return 0; + } + + $contact_id = $contacts[0]["id"]; + + // Update the newly created contact from data in the gcontact table + $gcontact = dba::select('gcontact', array('location', 'about', 'keywords', 'gender'), array('nurl' => normalise_link($data["url"])), array('limit' => 1)); + if (DBM::is_result($gcontact)) { + // Only use the information when the probing hadn't fetched these values + if ($data['keywords'] != '') { + unset($gcontact['keywords']); + } + if ($data['location'] != '') { + unset($gcontact['location']); + } + if ($data['about'] != '') { + unset($gcontact['about']); + } + dba::update('contact', $gcontact, array('id' => $contact_id)); + } + + if (count($contacts) > 1 && $uid == 0 && $contact_id != 0 && $data["url"] != "") { + dba::delete('contact', array("`nurl` = ? AND `uid` = 0 AND `id` != ? AND NOT `self`", + normalise_link($data["url"]), $contact_id)); + } + } + + require_once 'include/Photo.php'; + + update_contact_avatar($data["photo"], $uid, $contact_id); + + $contact = dba::select('contact', array('url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date'), array('id' => $contact_id), array('limit' => 1)); + + // This condition should always be true + if (!DBM::is_result($contact)) { + return $contact_id; + } + + $updated = array('addr' => $data['addr'], + 'alias' => $data['alias'], + 'url' => $data['url'], + 'nurl' => normalise_link($data['url']), + 'name' => $data['name'], + 'nick' => $data['nick']); + + if ($data['keywords'] != '') { + $updated['keywords'] = $data['keywords']; + } + if ($data['location'] != '') { + $updated['location'] = $data['location']; + } + if ($data['about'] != '') { + $updated['about'] = $data['about']; + } + + if (($data["addr"] != $contact["addr"]) || ($data["alias"] != $contact["alias"])) { + $updated['uri-date'] = datetime_convert(); + } + if (($data["name"] != $contact["name"]) || ($data["nick"] != $contact["nick"])) { + $updated['name-date'] = datetime_convert(); + } + + $updated['avatar-date'] = datetime_convert(); + + dba::update('contact', $updated, array('id' => $contact_id), $contact); + + return $contact_id; + } + + /** + * @brief Checks if the contact is blocked + * + * @param int $cid contact id + * + * @return boolean Is the contact blocked? + */ + public static function isBlocked($cid) + { + if ($cid == 0) { + return false; + } + + $blocked = dba::select('contact', array('blocked'), array('id' => $cid), array('limit' => 1)); + if (!DBM::is_result($blocked)) { + return false; + } + return (bool) $blocked['blocked']; + } + + /** + * @brief Checks if the contact is hidden + * + * @param int $cid contact id + * + * @return boolean Is the contact hidden? + */ + public static function isHidden($cid) + { + if ($cid == 0) { + return false; + } + + $hidden = dba::select('contact', array('hidden'), array('id' => $cid), array('limit' => 1)); + if (!DBM::is_result($hidden)) { + return false; + } + return (bool) $hidden['hidden']; + } + + /** + * @brief Returns posts from a given contact url + * + * @param App $a argv application class + * @param string $contact_url Contact URL + * + * @return string posts in HTML + */ + public static function getPostsFromUrl($contact_url) + { + require_once 'include/conversation.php'; + + // There are no posts with "uid = 0" with connector networks + // This speeds up the query a lot + $r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact` + WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0", dbesc(normalise_link($contact_url))); + + if (!DBM::is_result($r)) { + return ''; + } + + if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) { + $sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND NOT `item`.`global`))"; + } else { + $sql = "`item`.`uid` = %d"; + } + + $author_id = intval($r[0]["author-id"]); + + $contact = ($r[0]["contact-type"] == ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id'); + + $r = q(item_query() . " AND `item`.`" . $contact . "` = %d AND " . $sql . + " ORDER BY `item`.`created` DESC LIMIT %d, %d", intval($author_id), intval(local_user()), intval($a->pager['start']), intval($a->pager['itemspage']) + ); + + $a = self::getApp(); + + $o = conversation($a, $r, 'community', false); + + $o .= alt_pager($a, count($r)); + + return $o; + } + + /** + * @brief Returns the account type name + * + * The function can be called with either the user or the contact array + * + * @param array $contact contact or user array + * @return string + */ + public static function getAccountType(array $contact) + { + // There are several fields that indicate that the contact or user is a forum + // "page-flags" is a field in the user table, + // "forum" and "prv" are used in the contact table. They stand for PAGE_COMMUNITY and PAGE_PRVGROUP. + // "community" is used in the gcontact table and is true if the contact is PAGE_COMMUNITY or PAGE_PRVGROUP. + if ( + (isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_COMMUNITY)) + || (isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_PRVGROUP)) + || (isset($contact['forum']) && intval($contact['forum'])) + || (isset($contact['prv']) && intval($contact['prv'])) + || (isset($contact['community']) && intval($contact['community'])) + ) { + $type = ACCOUNT_TYPE_COMMUNITY; + } else { + $type = ACCOUNT_TYPE_PERSON; + } + + // The "contact-type" (contact table) and "account-type" (user table) are more general then the chaos from above. + if (isset($contact["contact-type"])) { + $type = $contact["contact-type"]; + } + if (isset($contact["account-type"])) { + $type = $contact["account-type"]; + } + + switch ($type) { + case ACCOUNT_TYPE_ORGANISATION: + $account_type = t("Organisation"); + break; + case ACCOUNT_TYPE_NEWS: + $account_type = t('News'); + break; + case ACCOUNT_TYPE_COMMUNITY: + $account_type = t("Forum"); + break; + default: + $account_type = ""; + break; + } + + return $account_type; + } +} diff --git a/src/Object/Profile.php b/src/Object/Profile.php new file mode 100644 index 0000000000..29925a9497 --- /dev/null +++ b/src/Object/Profile.php @@ -0,0 +1,46 @@ +