return $contact;
}
+ /**
+ * Insert a row into the contact table
+ * Important: You can't use DBA::lastInsertId() after this call since it will be set to 0.
+ *
+ * @param array $param parameter array
+ * @param bool $on_duplicate_update Do an update on a duplicate entry
+ *
+ * @return boolean was the insert successful?
+ * @throws \Exception
+ */
+ public static function insert(array $param, bool $on_duplicate_update = false)
+ {
+ $ret = DBA::insert('contact', $param, $on_duplicate_update);
+ $contact = DBA::selectFirst('contact', ['nurl', 'uid'], ['id' => DBA::lastInsertId()]);
+ if (!DBA::isResult($contact)) {
+ // Shouldn't happen
+ return $ret;
+ }
+
+ // Search for duplicated contacts and get rid of them
+ self::removeDuplicates($contact['nurl'], $contact['uid']);
+
+ return $ret;
+ }
+
/**
* @param integer $id Contact ID
* @param array $fields Array of selected fields, empty for all
* delete, though if the owner tries to unarchive them we'll start
* the whole process over again.
*/
- DBA::update('contact', ['archive' => 1], ['id' => $contact['id']]);
- DBA::update('contact', ['archive' => 1], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
+ DBA::update('contact', ['archive' => true], ['id' => $contact['id']]);
+ DBA::update('contact', ['archive' => true], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
GContact::updateFromPublicContactURL($contact['url']);
}
}
*/
public static function unmarkForArchival(array $contact)
{
+ // Always unarchive the relay contact entry
+ if (!empty($contact['batch'])) {
+ $fields = ['term-date' => DBA::NULL_DATETIME, 'archive' => false];
+ $condition = ['batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
+ DBA::update('contact', $fields, $condition);
+ }
+
$condition = ['`id` = ? AND (`term-date` > ? OR `archive`)', $contact['id'], DBA::NULL_DATETIME];
$exists = DBA::exists('contact', $condition);
// It's a miracle. Our dead contact has inexplicably come back to life.
$fields = ['term-date' => DBA::NULL_DATETIME, 'archive' => false];
DBA::update('contact', $fields, ['id' => $contact['id']]);
- DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($contact['url'])]);
+ DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
GContact::updateFromPublicContactURL($contact['url']);
-
- if (!empty($contact['batch'])) {
- $condition = ['batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
- DBA::update('contact', $fields, $condition);
- }
}
/**
if (!DBA::isResult($contact)) {
Logger::info('Create new contact', $fields);
- DBA::insert('contact', $fields);
+ self::insert($fields);
// We intentionally aren't using lastInsertId here. There is a chance for duplicates.
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
return $contact_id;
}
+ /**
+ * @brief Checks if the contact is archived
+ *
+ * @param int $cid contact id
+ *
+ * @return boolean Is the contact archived?
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function isArchived(int $cid)
+ {
+ if ($cid == 0) {
+ return false;
+ }
+
+ $contact = DBA::selectFirst('contact', ['archive', 'url', 'batch'], ['id' => $cid]);
+ if (!DBA::isResult($contact)) {
+ return false;
+ }
+
+ if ($contact['archive']) {
+ return true;
+ }
+
+ // Check status of ActivityPub endpoints
+ $apcontact = APContact::getByURL($contact['url'], false);
+ if (!empty($apcontact)) {
+ if (!empty($apcontact['inbox']) && DBA::exists('inbox-status', ['archive' => true, 'url' => $apcontact['inbox']])) {
+ return true;
+ }
+
+ if (!empty($apcontact['sharedinbox']) && DBA::exists('inbox-status', ['archive' => true, 'url' => $apcontact['sharedinbox']])) {
+ return true;
+ }
+ }
+
+ // Check status of Diaspora endpoints
+ if (!empty($contact['batch'])) {
+ return DBA::exists('contact', ['archive' => true, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY]);
+ }
+
+ return false;
+ }
+
/**
* @brief Checks if the contact is blocked
*
/**
* @brief Helper function for "updateFromProbe". Updates personal and public contact
*
- * @param array $contact The personal contact entry
- * @param array $fields The fields that are updated
+ * @param integer $id contact id
+ * @param integer $uid user id
+ * @param string $url The profile URL of the contact
+ * @param array $fields The fields that are updated
+ *
* @throws \Exception
*/
private static function updateContact($id, $uid, $url, array $fields)
{
- DBA::update('contact', $fields, ['id' => $id]);
+ if (!DBA::update('contact', $fields, ['id' => $id])) {
+ Logger::info('Couldn\'t update contact.', ['id' => $id, 'fields' => $fields]);
+ return;
+ }
// Search for duplicated contacts and get rid of them
- if (self::handleDuplicates(Strings::normaliseLink($url), $uid, $id) || ($uid != 0)) {
+ if (self::removeDuplicates(Strings::normaliseLink($url), $uid) || ($uid != 0)) {
return;
}
// Archive or unarchive the contact. We only need to do this for the public contact.
// The archive/unarchive function will update the personal contacts by themselves.
$contact = DBA::selectFirst('contact', [], ['id' => $id]);
+ if (!DBA::isResult($contact)) {
+ Logger::info('Couldn\'t select contact for archival.', ['id' => $id]);
+ return;
+ }
+
if (!empty($fields['success_update'])) {
self::unmarkForArchival($contact);
} elseif (!empty($fields['failure_update'])) {
}
/**
- * @brief Helper function for "updateFromProbe". Remove duplicated contacts
+ * @brief Remove duplicated contacts
*
* @param string $nurl Normalised contact url
* @param integer $uid User id
- * @param integer $id Contact id of a duplicate
* @return boolean
* @throws \Exception
*/
- private static function handleDuplicates($nurl, $uid, $id)
+ public static function removeDuplicates(string $nurl, int $uid)
{
- $condition = ['nurl' => $nurl, 'uid' => $uid, 'deleted' => false];
+ $condition = ['nurl' => $nurl, 'uid' => $uid, 'deleted' => false, 'network' => Protocol::FEDERATED];
$count = DBA::count('contact', $condition);
if ($count <= 1) {
return false;
}
- $first_contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
+ $first_contact = DBA::selectFirst('contact', ['id', 'network'], $condition, ['order' => ['id']]);
if (!DBA::isResult($first_contact)) {
// Shouldn't happen - so we handle it
return false;
}
$first = $first_contact['id'];
- Logger::info('Found duplicates', ['count' => $count, 'id' => $id, 'first' => $first, 'uid' => $uid, 'nurl' => $nurl]);
- if ($uid != 0) {
- // Don't handle non public duplicates by now
- Logger::info('Not handling non public duplicate', ['uid' => $uid, 'nurl' => $nurl]);
+ Logger::info('Found duplicates', ['count' => $count, 'first' => $first, 'uid' => $uid, 'nurl' => $nurl]);
+ if (($uid != 0 && ($first_contact['network'] == Protocol::DFRN))) {
+ // Don't handle non public DFRN duplicates by now (legacy DFRN is very special because of the key handling)
+ Logger::info('Not handling non public DFRN duplicate', ['uid' => $uid, 'nurl' => $nurl]);
return false;
}
// Find all duplicates
$condition = ["`nurl` = ? AND `uid` = ? AND `id` != ? AND NOT `self` AND NOT `deleted`", $nurl, $uid, $first];
- $duplicates = DBA::select('contact', ['id'], $condition);
+ $duplicates = DBA::select('contact', ['id', 'network'], $condition);
while ($duplicate = DBA::fetch($duplicates)) {
- $dup_id = $duplicate['id'];
- Logger::info('Handling duplicate', ['search' => $dup_id, 'replace' => $first]);
-
- // Search and replace
- DBA::update('item', ['author-id' => $first], ['author-id' => $dup_id]);
- DBA::update('item', ['owner-id' => $first], ['owner-id' => $dup_id]);
- DBA::update('item', ['contact-id' => $first], ['contact-id' => $dup_id]);
+ if (!in_array($duplicate['network'], Protocol::FEDERATED)) {
+ continue;
+ }
- // Remove the duplicate
- DBA::delete('contact', ['id' => $dup_id]);
+ Worker::add(PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
}
Logger::info('Duplicates handled', ['uid' => $uid, 'nurl' => $nurl]);
return true;
}
if (!$update) {
- if ($force && ($uid == 0)) {
+ if ($force) {
self::updateContact($id, $uid, $ret['url'], ['last-update' => $updated, 'success_update' => $updated]);
}
return true;
$new_relation = (in_array($protocol, [Protocol::MAIL]) ? self::FRIEND : self::SHARING);
// create contact record
- DBA::insert('contact', [
+ self::insert([
'uid' => $uid,
'created' => DateTimeFormat::utcNow(),
'url' => $ret['url'],
$nick = $pub_contact['nick'];
$network = $pub_contact['network'];
+ // Ensure that we don't create a new contact when there already is one
+ $cid = self::getIdForURL($url, $importer['uid']);
+ if (!empty($cid)) {
+ $contact = DBA::selectFirst('contact', [], ['id' => $cid]);
+ }
+
if (!empty($contact)) {
+ if (!empty($contact['pending'])) {
+ Logger::info('Pending contact request already exists.', ['url' => $url, 'uid' => $importer['uid']]);
+ return null;
+ }
+
// Contact is blocked at user-level
if (!empty($contact['id']) && !empty($importer['id']) &&
self::isBlockedByUser($contact['id'], $importer['id'])) {