+ /**
+ * @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
+ * @throws \Exception
+ */
+ private static function updateContact($id, $uid, $url, array $fields)
+ {
+ DBA::update('contact', $fields, ['id' => $id]);
+
+ // Search for duplicated contacts and get rid of them
+ if (self::handleDuplicates(Strings::normaliseLink($url), $uid, $id) || ($uid != 0)) {
+ return;
+ }
+
+ // Update the corresponding gcontact entry
+ GContact::updateFromPublicContactID($id);
+
+ // 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 (!empty($fields['success_update'])) {
+ self::unmarkForArchival($contact);
+ } elseif (!empty($fields['failure_update'])) {
+ self::markForArchival($contact);
+ }
+
+ $condition = ['self' => false, 'nurl' => Strings::normaliseLink($url), 'network' => Protocol::FEDERATED];
+
+ // These contacts are sharing with us, we don't poll them.
+ // This means that we don't set the update fields in "OnePoll.php".
+ $condition['rel'] = self::SHARING;
+ DBA::update('contact', $fields, $condition);
+
+ unset($fields['last-update']);
+ unset($fields['success_update']);
+ unset($fields['failure_update']);
+
+ if (empty($fields)) {
+ return;
+ }
+
+ // We are polling these contacts, so we mustn't set the update fields here.
+ $condition['rel'] = [self::FOLLOWER, self::FRIEND];
+ DBA::update('contact', $fields, $condition);
+ }
+
+ /**
+ * @brief Helper function for "updateFromProbe". 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)
+ {
+ $condition = ['nurl' => $nurl, 'uid' => $uid, 'deleted' => false];
+ $count = DBA::count('contact', $condition);
+ if ($count <= 1) {
+ return false;
+ }
+
+ $first_contact = DBA::selectFirst('contact', ['id'], $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]);
+ 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);
+ 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]);
+
+ // Remove the duplicate
+ DBA::delete('contact', ['id' => $dup_id]);
+ }
+ Logger::info('Duplicates handled', ['uid' => $uid, 'nurl' => $nurl]);
+ return true;
+ }
+