use Friendica\Network\Probe;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub;
-use Friendica\Protocol\DFRN;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\OStatus;
use Friendica\Protocol\Salmon;
const FOLLOWER = 1;
const SHARING = 2;
const FRIEND = 3;
+ const SELF = 4;
/**
* @}
*/
* @param array $fields field array
* @param int $duplicate_mode Do an update on a duplicate entry
*
- * @return boolean was the insert successful?
+ * @return int id of the created contact
* @throws \Exception
*/
public static function insert(array $fields, int $duplicate_mode = Database::INSERT_DEFAULT)
$fields['created'] = DateTimeFormat::utcNow();
}
- $ret = DBA::insert('contact', $fields, $duplicate_mode);
- $contact = DBA::selectFirst('contact', ['nurl', 'uid'], ['id' => DBA::lastInsertId()]);
+ DBA::insert('contact', $fields, $duplicate_mode);
+ $contact = DBA::selectFirst('contact', [], ['id' => DBA::lastInsertId()]);
if (!DBA::isResult($contact)) {
// Shouldn't happen
- return $ret;
+ Logger::warning('Created contact could not be found', ['fields' => $fields]);
+ return 0;
}
+ Contact\User::insertForContactArray($contact);
+
// Search for duplicated contacts and get rid of them
- self::removeDuplicates($contact['nurl'], $contact['uid']);
+ if (!$contact['self']) {
+ self::removeDuplicates($contact['nurl'], $contact['uid']);
+ }
+
+ return $contact['id'];
+ }
+
+ /**
+ * Updates rows in the contact table
+ *
+ * @param array $fields contains the fields that are updated
+ * @param array $condition condition array with the key values
+ * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate, false = don't update identical fields)
+ *
+ * @return boolean was the update successfull?
+ * @throws \Exception
+ */
+ public static function update(array $fields, array $condition, $old_fields = [])
+ {
+ $ret = DBA::update('contact', $fields, $condition, $old_fields);
+
+ // Apply changes to the "user-contact" table on dedicated fields
+ Contact\User::updateByContactUpdate($fields, $condition);
return $ret;
}
return false;
}
- $cdata = self::getPublicAndUserContacID($cid, $uid);
+ $cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
return false;
}
return false;
}
- $cdata = self::getPublicAndUserContacID($cid, $uid);
+ $cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
return false;
}
*/
public static function isLocal($url)
{
+ if (!parse_url($url, PHP_URL_SCHEME)) {
+ $addr_parts = explode('@', $url);
+ return (count($addr_parts) == 2) && ($addr_parts[1] == DI::baseUrl()->getHostname());
+ }
+
return Strings::compareLink(self::getBasepath($url, true), DI::baseUrl());
}
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- public static function getPublicAndUserContacID($cid, $uid)
+ public static function getPublicAndUserContactID($cid, $uid)
+ {
+ // We have to use the legacy function as long as the post update hasn't finished
+ if (DI::config()->get('system', 'post_update_version') < 1427) {
+ return self::legacyGetPublicAndUserContactID($cid, $uid);
+ }
+
+ if (empty($uid) || empty($cid)) {
+ return [];
+ }
+
+ $contact = DBA::selectFirst('account-user-view', ['id', 'uid', 'pid'], ['id' => $cid]);
+ if (!DBA::isResult($contact) || !in_array($contact['uid'], [0, $uid])) {
+ return [];
+ }
+
+ $pcid = $contact['pid'];
+ if ($contact['uid'] == $uid) {
+ $ucid = $contact['id'];
+ } else {
+ $contact = DBA::selectFirst('account-user-view', ['id', 'uid'], ['pid' => $cid, 'uid' => $uid]);
+ if (DBA::isResult($contact)) {
+ $ucid = $contact['id'];
+ } else {
+ $ucid = 0;
+ }
+ }
+
+ return ['public' => $pcid, 'user' => $ucid];
+ }
+
+ /**
+ * Helper function for "getPublicAndUserContactID"
+ *
+ * @param int $cid Either public contact id or user's contact id
+ * @param int $uid User ID
+ *
+ * @return array with public and user's contact id
+ * @throws HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ private static function legacyGetPublicAndUserContactID($cid, $uid)
{
if (empty($uid) || empty($cid)) {
return [];
'nick' => $user['nickname'],
'pubkey' => $user['pubkey'],
'prvkey' => $user['prvkey'],
- 'photo' => DI::baseUrl() . '/photo/profile/' . $user['uid'] . '.jpg',
- 'thumb' => DI::baseUrl() . '/photo/avatar/' . $user['uid'] . '.jpg',
- 'micro' => DI::baseUrl() . '/photo/micro/' . $user['uid'] . '.jpg',
+ 'photo' => User::getAvatarUrl($user),
+ 'thumb' => User::getAvatarUrl($user, Proxy::SIZE_THUMB),
+ 'micro' => User::getAvatarUrl($user, Proxy::SIZE_MICRO),
'blocked' => 0,
'pending' => 0,
'url' => DI::baseUrl() . '/profile/' . $user['nickname'],
// Only create the entry if it doesn't exist yet
if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
- $return = DBA::insert('contact', $contact);
+ $return = (bool)self::insert($contact);
}
// Create the public contact
$contact['uid'] = 0;
$contact['prvkey'] = null;
- DBA::insert('contact', $contact, Database::INSERT_IGNORE);
+ self::insert($contact, Database::INSERT_IGNORE);
}
return $return;
public static function updateSelfFromUserID($uid, $update_avatar = false)
{
$fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey',
- 'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
+ 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($self)) {
return false;
}
- $fields = ['nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey'];
+ $fields = ['uid', 'nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey'];
$user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'account_expired' => false]);
if (!DBA::isResult($user)) {
return false;
}
$fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
- 'country-name', 'pub_keywords', 'xmpp', 'net-publish'];
+ 'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish'];
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
if (!DBA::isResult($profile)) {
return false;
'avatar-date' => $self['avatar-date'], 'location' => Profile::formatLocation($profile),
'about' => $profile['about'], 'keywords' => $profile['pub_keywords'],
'contact-type' => $user['account-type'], 'prvkey' => $user['prvkey'],
- 'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp'], 'network' => Protocol::DFRN];
+ 'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp'], 'matrix' => $profile['matrix'], 'network' => Protocol::DFRN];
// it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
$fields['url'] = DI::baseUrl() . '/profile/' . $user['nickname'];
$fields['micro'] = self::getDefaultAvatar($fields, Proxy::SIZE_MICRO);
}
- $fields['avatar'] = DI::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix;
+ $fields['avatar'] = User::getAvatarUrl($user);
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
$fields['unsearchable'] = !$profile['net-publish'];
$fields['name-date'] = DateTimeFormat::utcNow();
}
$fields['updated'] = DateTimeFormat::utcNow();
- DBA::update('contact', $fields, ['id' => $self['id']]);
+ self::update($fields, ['id' => $self['id']]);
// Update the public contact as well
$fields['prvkey'] = null;
$fields['self'] = false;
- DBA::update('contact', $fields, ['uid' => 0, 'nurl' => $self['nurl']]);
+ self::update($fields, ['uid' => 0, 'nurl' => $self['nurl']]);
// Update the profile
- $fields = ['photo' => DI::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix,
- 'thumb' => DI::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix];
+ $fields = [
+ 'photo' => User::getAvatarUrl($user),
+ 'thumb' => User::getAvatarUrl($user, Proxy::SIZE_THUMB)
+ ];
+
DBA::update('profile', $fields, ['uid' => $uid]);
}
* Marks a contact for removal
*
* @param int $id contact id
- * @return null
* @throws HTTPException\InternalServerErrorException
*/
public static function remove($id)
}
// Archive the contact
- DBA::update('contact', ['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
+ self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
// Delete it in the background
Worker::add(PRIORITY_MEDIUM, 'RemoveContact', $id);
}
/**
- * Sends an unfriend message. Does not remove the contact
+ * Sends an unfriend message. Removes the contact for two-way unfriending or sharing only protocols (feed an mail)
+ *
+ * @param array $user User unfriending
+ * @param array $contact Contact (uid != 0) unfriended
+ * @param boolean $two_way Revoke eventual inbound follow as well
+ * @return bool|null true if successful, false if not, null if no action was performed
+ * @throws HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function terminateFriendship(array $user, array $contact): bool
+ {
+ $result = Protocol::terminateFriendship($user, $contact);
+
+ if ($contact['rel'] == Contact::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
+ self::remove($contact['id']);
+ } else {
+ self::update(['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Revoke follow privileges of the remote user contact
*
- * @param array $user User unfriending
* @param array $contact Contact unfriended
- * @param boolean $dissolve Remove the contact on the remote side
- * @return void
+ * @return bool|null Whether the remote operation is successful or null if no remote operation was performed
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- public static function terminateFriendship(array $user, array $contact, $dissolve = false)
+ public static function revokeFollow(array $contact): bool
{
if (empty($contact['network'])) {
- return;
+ throw new \InvalidArgumentException('Empty network in contact array');
}
- $protocol = $contact['network'];
- if (($protocol == Protocol::DFRN) && !self::isLegacyDFRNContact($contact)) {
- $protocol = Protocol::ACTIVITYPUB;
+ if (empty($contact['uid'])) {
+ throw new \InvalidArgumentException('Unexpected public contact record');
}
- if (($protocol == Protocol::DFRN) && $dissolve) {
- DFRN::deliver($user, $contact, 'placeholder', true);
- } elseif (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
- // create an unfollow slap
- $item = [];
- $item['verb'] = Activity::O_UNFOLLOW;
- $item['gravity'] = GRAVITY_ACTIVITY;
- $item['follow'] = $contact["url"];
- $item['body'] = '';
- $item['title'] = '';
- $item['guid'] = '';
- $item['uri-id'] = 0;
- $slap = OStatus::salmon($item, $user);
+ $result = Protocol::revokeFollow($contact);
- if (!empty($contact['notify'])) {
- Salmon::slapper($user, $contact['notify'], $slap);
- }
- } elseif ($protocol == Protocol::DIASPORA) {
- Diaspora::sendUnshare($user, $contact);
- } elseif ($protocol == Protocol::ACTIVITYPUB) {
- ActivityPub\Transmitter::sendContactUndo($contact['url'], $contact['id'], $user['uid']);
-
- if ($dissolve) {
- ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $user['uid']);
+ // A null value here means the remote network doesn't support explicit follow revocation, we can still
+ // break the locally recorded relationship
+ if ($result !== false) {
+ if ($contact['rel'] == self::FRIEND) {
+ self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
+ } else {
+ self::remove($contact['id']);
}
}
+
+ return $result;
}
+
/**
* Marks a contact for archival after a communication issue delay
*
}
if ($contact['term-date'] <= DBA::NULL_DATETIME) {
- DBA::update('contact', ['term-date' => DateTimeFormat::utcNow()], ['id' => $contact['id']]);
- DBA::update('contact', ['term-date' => DateTimeFormat::utcNow()], ['`nurl` = ? AND `term-date` <= ? AND NOT `self`', Strings::normaliseLink($contact['url']), DBA::NULL_DATETIME]);
+ self::update(['term-date' => DateTimeFormat::utcNow()], ['id' => $contact['id']]);
+ self::update(['term-date' => DateTimeFormat::utcNow()], ['`nurl` = ? AND `term-date` <= ? AND NOT `self`', Strings::normaliseLink($contact['url']), DBA::NULL_DATETIME]);
} else {
/* @todo
* We really should send a notification to the owner after 2-3 weeks
* delete, though if the owner tries to unarchive them we'll start
* the whole process over again.
*/
- DBA::update('contact', ['archive' => true], ['id' => $contact['id']]);
- DBA::update('contact', ['archive' => true], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
+ self::update(['archive' => true], ['id' => $contact['id']]);
+ self::update(['archive' => true], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
}
}
}
$fields = ['failed' => false, 'term-date' => DBA::NULL_DATETIME, 'archive' => false];
$condition = ['uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
if (!DBA::exists('contact', array_merge($condition, $fields))) {
- DBA::update('contact', $fields, $condition);
+ self::update($fields, $condition);
}
}
// It's a miracle. Our dead contact has inexplicably come back to life.
$fields = ['failed' => false, 'term-date' => DBA::NULL_DATETIME, 'archive' => false];
- DBA::update('contact', $fields, ['id' => $contact['id']]);
- DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
+ self::update($fields, ['id' => $contact['id']]);
+ self::update($fields, ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
}
/**
$pm_url = '';
$status_link = '';
$photos_link = '';
- $contact_drop_link = '';
$poke_link = '';
if ($uid == 0) {
$posts_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
- if (!$contact['self']) {
- $contact_drop_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/drop?confirm=1';
- }
-
$follow_link = '';
$unfollow_link = '';
- if (!$contact['self'] && in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
+ if (!$contact['self'] && Protocol::supportsFollow($contact['network'])) {
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']) . '&auto=1';
} elseif(!$contact['pending']) {
}
}
- if (!empty($follow_link) || !empty($unfollow_link)) {
- $contact_drop_link = '';
- }
-
/**
* Menu array:
* "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ]
'photos' => [DI::l10n()->t('View Photos') , $photos_link , true],
'network' => [DI::l10n()->t('Network Posts') , $posts_link , false],
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false],
- 'drop' => [DI::l10n()->t('Drop Contact') , $contact_drop_link, false],
'pm' => [DI::l10n()->t('Send PM') , $pm_url , false],
'poke' => [DI::l10n()->t('Poke') , $poke_link , false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true],
return 0;
}
- $contact = self::getByURL($url, false, ['id', 'network'], $uid);
+ $contact = self::getByURL($url, false, ['id', 'network', 'uri-id'], $uid);
if (!empty($contact)) {
$contact_id = $contact["id"];
- if (empty($update)) {
+ if (empty($update) && (!empty($contact['uri-id']) || is_bool($update))) {
Logger::debug('Contact found', ['url' => $url, 'uid' => $uid, 'update' => $update, 'cid' => $contact_id]);
return $contact_id;
}
$contact_id = $contact['id'];
Logger::notice('Contact had been created (shortly) before', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]);
} else {
- DBA::insert('contact', $fields);
- $contact_id = DBA::lastInsertId();
+ $contact_id = self::insert($fields);
if ($contact_id) {
Logger::info('Contact inserted', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]);
}
* @param bool $thread_mode
* @param int $update Update mode
* @param int $parent Item parent ID for the update mode
+ * @param bool $only_media Only display media content
* @return string posts in HTML
* @throws \Exception
*/
- public static function getPostsFromUrl($contact_url, $thread_mode = false, $update = 0, $parent = 0)
+ public static function getPostsFromUrl($contact_url, $thread_mode = false, $update = 0, $parent = 0, bool $only_media = false)
{
- return self::getPostsFromId(self::getIdForURL($contact_url), $thread_mode, $update, $parent);
+ return self::getPostsFromId(self::getIdForURL($contact_url), $thread_mode, $update, $parent, $only_media);
}
/**
* @param int $cid Contact ID
* @param bool $thread_mode
* @param int $update Update mode
- * @param int $parent Item parent ID for the update mode
+ * @param int $parent Item parent ID for the update mode
+ * @param bool $only_media Only display media content
* @return string posts in HTML
* @throws \Exception
*/
- public static function getPostsFromId($cid, $thread_mode = false, $update = 0, $parent = 0)
+ public static function getPostsFromId($cid, $thread_mode = false, $update = 0, $parent = 0, bool $only_media = false)
{
- $a = DI::app();
-
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
if (!DBA::isResult($contact)) {
return '';
}
}
+ if ($only_media) {
+ $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))",
+ Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]);
+ }
+
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));
if ($thread_mode) {
$items = Post::toArray(Post::selectForUser(local_user(), ['uri-id', 'gravity', 'parent-uri-id', 'thr-parent-id', 'author-id'], $condition, $params));
- $o .= conversation($a, $items, 'contacts', $update, false, 'commented', local_user());
+ $o .= DI::conversation()->create($items, 'contacts', $update, false, 'commented', local_user());
} else {
$items = Post::toArray(Post::selectForUser(local_user(), Item::DISPLAY_FIELDLIST, $condition, $params));
- $o .= conversation($a, $items, 'contact-posts', $update);
+ $o .= DI::conversation()->create($items, 'contact-posts', $update);
}
if (!$update) {
*/
public static function block($cid, $reason = null)
{
- $return = DBA::update('contact', ['blocked' => true, 'block_reason' => $reason], ['id' => $cid]);
+ $return = self::update(['blocked' => true, 'block_reason' => $reason], ['id' => $cid]);
return $return;
}
*/
public static function unblock($cid)
{
- $return = DBA::update('contact', ['blocked' => false, 'block_reason' => null], ['id' => $cid]);
+ $return = self::update(['blocked' => false, 'block_reason' => null], ['id' => $cid]);
return $return;
}
{
$condition = ["`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)",
Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0];
- $contact = self::selectFirst(['id', 'updated'], $condition);
+ $contact = self::selectFirst(['id', 'updated'], $condition, ['order' => ['uid' => true]]);
return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? '');
}
// Only update the cached photo links of public contacts when they already are cached
if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) {
if ($contact['avatar'] != $avatar) {
- DBA::update('contact', ['avatar' => $avatar], ['id' => $cid]);
+ self::update(['avatar' => $avatar], ['id' => $cid]);
Logger::info('Only update the avatar', ['id' => $cid, 'avatar' => $avatar, 'contact' => $contact]);
}
return;
// User contacts use are updated through the public contacts
if (($uid != 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
- $pcid = self::getIdForURL($contact['url'], false);
+ $pcid = self::getIdForURL($contact['url'], 0, false);
if (!empty($pcid)) {
Logger::debug('Update the private contact via the public contact', ['id' => $cid, 'uid' => $uid, 'public' => $pcid]);
self::updateAvatar($pcid, $avatar, $force, true);
$cids[] = $cid;
$uids[] = $uid;
Logger::info('Updating cached contact avatars', ['cid' => $cids, 'uid' => $uids, 'fields' => $fields]);
- DBA::update('contact', $fields, ['id' => $cids]);
+ self::update($fields, ['id' => $cids]);
}
public static function deleteContactByUrl(string $url)
*/
private static function updateContact(int $id, int $uid, string $old_url, string $new_url, array $fields)
{
- if (Strings::normaliseLink($new_url) != Strings::normaliseLink($old_url)) {
- Logger::notice('New URL differs from old URL', ['old' => $old_url, 'new' => $new_url]);
- return;
- }
-
- if (!DBA::update('contact', $fields, ['id' => $id])) {
+ if (!self::update($fields, ['id' => $id])) {
Logger::info('Couldn\'t update contact.', ['id' => $id, 'fields' => $fields]);
return;
}
$condition = ['self' => false, 'nurl' => Strings::normaliseLink($old_url)];
$condition['network'] = [Protocol::DFRN, Protocol::DIASPORA, Protocol::ACTIVITYPUB];
- DBA::update('contact', $fields, $condition);
+ self::update($fields, $condition);
// We mustn't set the update fields for OStatus contacts since they are updated in OnePoll
$condition['network'] = Protocol::OSTATUS;
return;
}
- DBA::update('contact', $fields, $condition);
+ self::update($fields, $condition);
}
/**
*/
public static function removeDuplicates(string $nurl, int $uid)
{
- $condition = ['nurl' => $nurl, 'uid' => $uid, 'deleted' => false, 'network' => Protocol::FEDERATED];
+ $condition = ['nurl' => $nurl, 'uid' => $uid, 'self' => false, 'deleted' => false, 'network' => Protocol::FEDERATED];
$count = DBA::count('contact', $condition);
if ($count <= 1) {
return false;
*/
// These fields aren't updated by this routine:
- // 'xmpp', 'sensitive'
+ // 'sensitive'
$fields = ['uid', 'uri-id', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe',
'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
- 'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item'];
+ 'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item', 'xmpp', 'matrix'];
$contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
if (!DBA::isResult($contact)) {
return false;
$updated = DateTimeFormat::utcNow();
+ if (Strings::normaliseLink($contact['url']) != Strings::normaliseLink($ret['url'])) {
+ Logger::notice('New URL differs from old URL', ['id' => $id, 'uid' => $uid, 'old' => $contact['url'], 'new' => $ret['url']]);
+ self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'last-update' => $updated, 'failure_update' => $updated]);
+ return false;
+ }
+
// We must not try to update relay contacts via probe. They are no real contacts.
// We check after the probing to be able to correct falsely detected contact types.
if (($contact['contact-type'] == self::TYPE_RELAY) &&
}
if (Strings::normaliseLink($ret['url']) != Strings::normaliseLink($contact['url'])) {
- $cid = self::getIdForURL($ret['url']);
+ $cid = self::getIdForURL($ret['url'], 0, false);
if (!empty($cid) && ($cid != $id)) {
Logger::notice('URL of contact changed.', ['id' => $id, 'new_id' => $cid, 'old' => $contact['url'], 'new' => $ret['url']]);
return self::updateFromProbeArray($cid, $ret);
if (empty($guid)) {
$ret['uri-id'] = ItemURI::getIdByURI($ret['url']);
} else {
- $ret['uri-id'] = ItemURI::insert(['uri' => $ret['uri'], 'guid' => $guid]);
+ $ret['uri-id'] = ItemURI::insert(['uri' => $ret['url'], 'guid' => $guid]);
}
$ret['nurl'] = Strings::normaliseLink($ret['url']);
}
if ((!empty($ret['addr']) && ($ret['addr'] != $contact['addr'])) || (!empty($ret['alias']) && ($ret['alias'] != $contact['alias']))) {
- $ret['uri-date'] = DateTimeFormat::utcNow();
+ $ret['uri-date'] = $updated;
}
- if (($ret['name'] != $contact['name']) || ($ret['nick'] != $contact['nick'])) {
+ if ((!empty($ret['name']) && ($ret['name'] != $contact['name'])) || (!empty($ret['nick']) && ($ret['nick'] != $contact['nick']))) {
$ret['name-date'] = $updated;
}
}
}
if (!empty($fields)) {
- DBA::update('contact', $fields, ['id' => $id, 'self' => false]);
+ self::update($fields, ['id' => $id, 'self' => false]);
Logger::info('Updating local contact', ['id' => $id]);
}
}
return $id;
}
- /**
- * Detects if a given contact array belongs to a legacy DFRN connection
- *
- * @param array $contact
- * @return boolean
- */
- public static function isLegacyDFRNContact($contact)
- {
- // Newer Friendica contacts are connected via AP, then these fields aren't set
- return !empty($contact['dfrn-id']) || !empty($contact['issued-id']);
- }
-
/**
* Detects the communication protocol for a given contact url.
* This is used to detect Friendica contacts that we can communicate via AP.
*
* Takes a $uid and a url/handle and adds a new contact
*
- * @param array $user The user the contact should be created for
+ * @param int $uid The user id the contact should be created for
* @param string $url The profile URL of the contact
- * @param bool $interactive
* @param string $network
* @return array
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException
* @throws \ImagickException
*/
- public static function createFromProbe(array $user, $url, $interactive = false, $network = '')
+ public static function createFromProbeForUser(int $uid, $url, $network = '')
{
$result = ['cid' => -1, 'success' => false, 'message' => ''];
$probed = false;
$ret = $arr['contact'];
} else {
- $probed = true;
- $ret = Probe::uri($url, $network, $user['uid']);
+ $probed = true;
+ $ret = Probe::uri($url, $network, $uid);
}
if (($network != '') && ($ret['network'] != $network)) {
// the poll url is more reliable than the profile url, as we may have
// indirect links or webfinger links
- $condition = ['uid' => $user['uid'], 'poll' => [$ret['poll'], Strings::normaliseLink($ret['poll'])], 'network' => $ret['network'], 'pending' => false];
+ $condition = ['uid' => $uid, 'poll' => [$ret['poll'], Strings::normaliseLink($ret['poll'])], 'network' => $ret['network'], 'pending' => false];
$contact = DBA::selectFirst('contact', ['id', 'rel'], $condition);
if (!DBA::isResult($contact)) {
- $condition = ['uid' => $user['uid'], 'nurl' => Strings::normaliseLink($ret['url']), 'network' => $ret['network'], 'pending' => false];
+ $condition = ['uid' => $uid, 'nurl' => Strings::normaliseLink($ret['url']), 'network' => $ret['network'], 'pending' => false];
$contact = DBA::selectFirst('contact', ['id', 'rel'], $condition);
}
$protocol = self::getProtocol($ret['url'], $ret['network']);
- if (($protocol === Protocol::DFRN) && !DBA::isResult($contact)) {
- if ($interactive) {
- if (strlen(DI::baseUrl()->getUrlPath())) {
- $myaddr = bin2hex(DI::baseUrl() . '/profile/' . $user['nickname']);
- } else {
- $myaddr = bin2hex($user['nickname'] . '@' . DI::baseUrl()->getHostname());
- }
-
- DI::baseUrl()->redirect($ret['request'] . "&addr=$myaddr");
-
- // NOTREACHED
- }
- } elseif (DI::config()->get('system', 'dfrn_only') && ($ret['network'] != Protocol::DFRN)) {
- $result['message'] = DI::l10n()->t('This site is not configured to allow communications with other networks.') . EOL;
- $result['message'] .= DI::l10n()->t('No compatible communication protocols or feeds were discovered.') . EOL;
- return $result;
- }
-
// This extra param just confuses things, remove it
if ($protocol === Protocol::DIASPORA) {
$ret['url'] = str_replace('?absolute=true', '', $ret['url']);
$new_relation = (($contact['rel'] == self::FOLLOWER) ? self::FRIEND : self::SHARING);
$fields = ['rel' => $new_relation, 'subhub' => $subhub, 'readonly' => false];
- DBA::update('contact', $fields, ['id' => $contact['id']]);
+ self::update($fields, ['id' => $contact['id']]);
} else {
$new_relation = (in_array($protocol, [Protocol::MAIL]) ? self::FRIEND : self::SHARING);
// create contact record
self::insert([
- 'uid' => $user['uid'],
+ 'uid' => $uid,
'created' => DateTimeFormat::utcNow(),
'url' => $ret['url'],
'nurl' => Strings::normaliseLink($ret['url']),
]);
}
- $contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $user['uid']]);
+ $contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $uid]);
if (!DBA::isResult($contact)) {
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . EOL;
return $result;
$contact_id = $contact['id'];
$result['cid'] = $contact_id;
- Group::addMember(User::getDefaultGroup($user['uid'], $contact["network"]), $contact_id);
+ Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact_id);
// Update the avatar
self::updateAvatar($contact_id, $ret['photo']);
Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
}
- $owner = User::getOwnerDataById($user['uid']);
+ $owner = User::getOwnerDataById($uid);
if (DBA::isResult($owner)) {
if (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
return false;
}
- $ret = ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $user['uid'], $activity_id);
+ $ret = ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid, $activity_id);
Logger::log('Follow returns: ' . $ret);
}
}
$fields = ['url' => $contact['url'], 'request' => $contact['request'],
'notify' => $contact['notify'], 'poll' => $contact['poll'],
'confirm' => $contact['confirm'], 'poco' => $contact['poco']];
- DBA::update('contact', $fields, ['id' => $contact['id']]);
+ self::update($fields, ['id' => $contact['id']]);
}
return $contact;
*/
public static function follow(int $cid, int $uid)
{
- $user = User::getById($uid);
- if (empty($user)) {
- return false;
- }
-
$contact = self::getById($cid, ['url']);
- $result = self::createFromProbe($user, $contact['url'], false);
+ $result = self::createFromProbeForUser($uid, $contact['url']);
return $result['cid'];
}
*/
public static function unfollow(int $cid, int $uid)
{
- $cdata = self::getPublicAndUserContacID($cid, $uid);
+ $cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
return false;
}
if (($contact['rel'] == self::SHARING)
|| ($sharing && $contact['rel'] == self::FOLLOWER)) {
- DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true, 'pending' => false],
+ self::update(['rel' => self::FRIEND, 'writable' => true, 'pending' => false],
['id' => $contact['id'], 'uid' => $importer['uid']]);
}
// Ensure to always have the correct network type, independent from the connection request method
self::updateFromProbe($contact['id']);
- Post\UserNotification::insertNotication($contact['id'], Verb::getID(Activity::FOLLOW), $importer['uid']);
+ Post\UserNotification::insertNotification($contact['id'], Activity::FOLLOW, $importer['uid']);
return true;
} else {
}
// create contact record
- DBA::insert('contact', [
+ $contact_id = self::insert([
'uid' => $importer['uid'],
'created' => DateTimeFormat::utcNow(),
'url' => $url,
'writable' => 1,
]);
- $contact_id = DBA::lastInsertId();
-
// Ensure to always have the correct network type, independent from the connection request method
self::updateFromProbe($contact_id);
self::updateAvatar($contact_id, $photo, true);
- Post\UserNotification::insertNotication($contact_id, Verb::getID(Activity::FOLLOW), $importer['uid']);
+ Post\UserNotification::insertNotification($contact_id, Activity::FOLLOW, $importer['uid']);
$contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]);
}
} elseif (DBA::isResult($user) && in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) {
if (($user['page-flags'] == User::PAGE_FLAGS_FREELOVE) && ($network != Protocol::DIASPORA)) {
- self::createFromProbe($importer, $url, false, $network);
+ self::createFromProbeForUser($importer['uid'], $url, $network);
}
$condition = ['uid' => $importer['uid'], 'url' => $url, 'pending' => true];
$fields['rel'] = self::FRIEND;
}
- DBA::update('contact', $fields, $condition);
+ self::update($fields, $condition);
return true;
}
return null;
}
- public static function removeFollower($importer, $contact)
+ public static function removeFollower(array $contact)
{
- if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::SHARING)) {
+ if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) {
DBA::update('contact', ['rel' => self::SHARING], ['id' => $contact['id']]);
- } else {
+ } elseif (!empty($contact['id'])) {
self::remove($contact['id']);
+ } else {
+ DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact, 'callstack' => System::callstack()]);
}
}
public static function removeSharer($importer, $contact)
{
if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) {
- DBA::update('contact', ['rel' => self::FOLLOWER], ['id' => $contact['id']]);
+ self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
} else {
self::remove($contact['id']);
}