|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @copyright Copyright (C) 2020, Friendica
|
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Friendica\Model;
|
|
|
|
|
|
|
|
use Friendica\App\BaseURL;
|
|
|
|
use Friendica\Content\Pager;
|
|
|
|
use Friendica\Core\Hook;
|
|
|
|
use Friendica\Core\Logger;
|
|
|
|
use Friendica\Core\Protocol;
|
|
|
|
use Friendica\Core\Session;
|
|
|
|
use Friendica\Core\System;
|
|
|
|
use Friendica\Core\Worker;
|
|
|
|
use Friendica\Database\DBA;
|
|
|
|
use Friendica\DI;
|
|
|
|
use Friendica\Model\Notify\Type;
|
|
|
|
use Friendica\Network\HTTPException;
|
|
|
|
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;
|
|
|
|
use Friendica\Util\DateTimeFormat;
|
|
|
|
use Friendica\Util\Images;
|
|
|
|
use Friendica\Util\Network;
|
|
|
|
use Friendica\Util\Strings;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* functions for interacting with a contact
|
|
|
|
*/
|
|
|
|
class Contact
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_NORMAL
|
|
|
|
*/
|
|
|
|
const PAGE_NORMAL = User::PAGE_FLAGS_NORMAL;
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_SOAPBOX
|
|
|
|
*/
|
|
|
|
const PAGE_SOAPBOX = User::PAGE_FLAGS_SOAPBOX;
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_COMMUNITY
|
|
|
|
*/
|
|
|
|
const PAGE_COMMUNITY = User::PAGE_FLAGS_COMMUNITY;
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_FREELOVE
|
|
|
|
*/
|
|
|
|
const PAGE_FREELOVE = User::PAGE_FLAGS_FREELOVE;
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_BLOG
|
|
|
|
*/
|
|
|
|
const PAGE_BLOG = User::PAGE_FLAGS_BLOG;
|
|
|
|
/**
|
|
|
|
* @deprecated since version 2019.03
|
|
|
|
* @see User::PAGE_FLAGS_PRVGROUP
|
|
|
|
*/
|
|
|
|
const PAGE_PRVGROUP = User::PAGE_FLAGS_PRVGROUP;
|
|
|
|
/**
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Account types
|
|
|
|
*
|
|
|
|
* TYPE_UNKNOWN - the account has been imported from gcontact where this is the default type value
|
|
|
|
*
|
|
|
|
* TYPE_PERSON - the account belongs to a person
|
|
|
|
* Associated page types: PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE
|
|
|
|
*
|
|
|
|
* TYPE_ORGANISATION - the account belongs to an organisation
|
|
|
|
* Associated page type: PAGE_SOAPBOX
|
|
|
|
*
|
|
|
|
* TYPE_NEWS - the account is a news reflector
|
|
|
|
* Associated page type: PAGE_SOAPBOX
|
|
|
|
*
|
|
|
|
* TYPE_COMMUNITY - the account is community forum
|
|
|
|
* Associated page types: PAGE_COMMUNITY, PAGE_PRVGROUP
|
|
|
|
*
|
|
|
|
* TYPE_RELAY - the account is a relay
|
|
|
|
* This will only be assigned to contacts, not to user accounts
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
const TYPE_UNKNOWN = -1;
|
|
|
|
const TYPE_PERSON = User::ACCOUNT_TYPE_PERSON;
|
|
|
|
const TYPE_ORGANISATION = User::ACCOUNT_TYPE_ORGANISATION;
|
|
|
|
const TYPE_NEWS = User::ACCOUNT_TYPE_NEWS;
|
|
|
|
const TYPE_COMMUNITY = User::ACCOUNT_TYPE_COMMUNITY;
|
|
|
|
const TYPE_RELAY = User::ACCOUNT_TYPE_RELAY;
|
|
|
|
/**
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contact_is
|
|
|
|
*
|
|
|
|
* Relationship types
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
const FOLLOWER = 1;
|
|
|
|
const SHARING = 2;
|
|
|
|
const FRIEND = 3;
|
|
|
|
/**
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $fields Array of selected fields, empty for all
|
|
|
|
* @param array $condition Array of fields for condition
|
|
|
|
* @param array $params Array of several parameters
|
|
|
|
* @return array
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function selectToArray(array $fields = [], array $condition = [], array $params = [])
|
|
|
|
{
|
|
|
|
return DBA::selectToArray('contact', $fields, $condition, $params);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $fields Array of selected fields, empty for all
|
|
|
|
* @param array $condition Array of fields for condition
|
|
|
|
* @param array $params Array of several parameters
|
|
|
|
* @return array
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function selectFirst(array $fields = [], array $condition = [], array $params = [])
|
|
|
|
{
|
|
|
|
$contact = DBA::selectFirst('contact', $fields, $condition, $params);
|
|
|
|
|
|
|
|
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 $fields field 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 $fields, bool $on_duplicate_update = false)
|
|
|
|
{
|
|
|
|
$ret = DBA::insert('contact', $fields, $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
|
|
|
|
* @return array|boolean Contact record if it exists, false otherwise
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function getById($id, $fields = [])
|
|
|
|
{
|
|
|
|
return DBA::selectFirst('contact', $fields, ['id' => $id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches a contact by a given url
|
|
|
|
*
|
|
|
|
* @param string $url profile url
|
|
|
|
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
|
|
|
|
* @param array $fields Field list
|
|
|
|
* @param integer $uid User ID of the contact
|
|
|
|
* @return array contact array
|
|
|
|
*/
|
|
|
|
public static function getByURL(string $url, $update = null, array $fields = [], int $uid = 0)
|
|
|
|
{
|
|
|
|
if ($update || is_null($update)) {
|
|
|
|
$cid = self::getIdForURL($url, $uid, $update);
|
|
|
|
if (empty($cid)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return self::getById($cid, $fields);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add internal fields
|
|
|
|
$removal = [];
|
|
|
|
if (!empty($fields)) {
|
|
|
|
foreach (['id', 'updated', 'network'] as $internal) {
|
|
|
|
if (!in_array($internal, $fields)) {
|
|
|
|
$fields[] = $internal;
|
|
|
|
$removal[] = $internal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We first try the nurl (http://server.tld/nick), most common case
|
|
|
|
$options = ['order' => ['id']];
|
|
|
|
$contact = DBA::selectFirst('contact', $fields, ['nurl' => Strings::normaliseLink($url), 'uid' => $uid, 'deleted' => false], $options);
|
|
|
|
|
|
|
|
// Then the addr (nick@server.tld)
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
$contact = DBA::selectFirst('contact', $fields, ['addr' => str_replace('acct:', '', $url), 'uid' => $uid, 'deleted' => false], $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then the alias (which could be anything)
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
// The link could be provided as http although we stored it as https
|
|
|
|
$ssl_url = str_replace('http://', 'https://', $url);
|
|
|
|
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, $uid];
|
|
|
|
$contact = DBA::selectFirst('contact', $fields, $condition, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the contact in the background if needed
|
|
|
|
if ((($contact['updated'] < DateTimeFormat::utc('now -7 days')) || empty($contact['avatar'])) &&
|
|
|
|
in_array($contact['network'], Protocol::FEDERATED)) {
|
|
|
|
Worker::add(PRIORITY_LOW, "UpdateContact", $contact['id'], ($uid == 0 ? 'force' : ''));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the internal fields
|
|
|
|
foreach ($removal as $internal) {
|
|
|
|
unset($contact[$internal]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $contact;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches a contact for a given user by a given url.
|
|
|
|
* In difference to "getByURL" the function will fetch a public contact when no user contact had been found.
|
|
|
|
*
|
|
|
|
* @param string $url profile url
|
|
|
|
* @param integer $uid User ID of the contact
|
|
|
|
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
|
|
|
|
* @param array $fields Field list
|
|
|
|
* @return array contact array
|
|
|
|
*/
|
|
|
|
public static function getByURLForUser(string $url, int $uid = 0, $update = false, array $fields = [])
|
|
|
|
{
|
|
|
|
if ($uid != 0) {
|
|
|
|
$contact = self::getByURL($url, $update, $fields, $uid);
|
|
|
|
if (!empty($contact)) {
|
|
|
|
if (!empty($contact['id'])) {
|
|
|
|
$contact['cid'] = $contact['id'];
|
|
|
|
$contact['zid'] = 0;
|
|
|
|
}
|
|
|
|
return $contact;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$contact = self::getByURL($url, $update, $fields);
|
|
|
|
if (!empty($contact['id'])) {
|
|
|
|
$contact['cid'] = 0;
|
|
|
|
$contact['zid'] = $contact['id'];
|
|
|
|
}
|
|
|
|
return $contact;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the given contact is a follower
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact id a follower?
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function isFollower($cid, $uid)
|
|
|
|
{
|
|
|
|
if (self::isBlockedByUser($cid, $uid)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata['user'])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$condition = ['id' => $cdata['user'], 'rel' => [self::FOLLOWER, self::FRIEND]];
|
|
|
|
return DBA::exists('contact', $condition);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the given contact url is a follower
|
|
|
|
*
|
|
|
|
* @param string $url Contact URL
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact id a follower?
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function isFollowerByURL($url, $uid)
|
|
|
|
{
|
|
|
|
$cid = self::getIdForURL($url, $uid, false);
|
|
|
|
|
|
|
|
if (empty($cid)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::isFollower($cid, $uid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the given user follow the given contact
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact url being followed?
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function isSharing($cid, $uid)
|
|
|
|
{
|
|
|
|
if (self::isBlockedByUser($cid, $uid)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata['user'])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$condition = ['id' => $cdata['user'], 'rel' => [self::SHARING, self::FRIEND]];
|
|
|
|
return DBA::exists('contact', $condition);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the given user follow the given contact url
|
|
|
|
*
|
|
|
|
* @param string $url Contact URL
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact url being followed?
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function isSharingByURL($url, $uid)
|
|
|
|
{
|
|
|
|
$cid = self::getIdForURL($url, $uid, false);
|
|
|
|
|
|
|
|
if (empty($cid)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::isSharing($cid, $uid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the basepath for a given contact link
|
|
|
|
*
|
|
|
|
* @param string $url The contact link
|
|
|
|
* @param boolean $dont_update Don't update the contact
|
|
|
|
*
|
|
|
|
* @return string basepath
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function getBasepath($url, $dont_update = false)
|
|
|
|
{
|
|
|
|
$contact = DBA::selectFirst('contact', ['id', 'baseurl'], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]);
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($contact['baseurl'])) {
|
|
|
|
return $contact['baseurl'];
|
|
|
|
} elseif ($dont_update) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the existing contact
|
|
|
|
self::updateFromProbe($contact['id'], '', true);
|
|
|
|
|
|
|
|
// And fetch the result
|
|
|
|
$contact = DBA::selectFirst('contact', ['baseurl'], ['id' => $contact['id']]);
|
|
|
|
if (empty($contact['baseurl'])) {
|
|
|
|
Logger::info('No baseurl for contact', ['url' => $url]);
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger::info('Found baseurl for contact', ['url' => $url, 'baseurl' => $contact['baseurl']]);
|
|
|
|
return $contact['baseurl'];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the given contact url is on the same server
|
|
|
|
*
|
|
|
|
* @param string $url The contact link
|
|
|
|
*
|
|
|
|
* @return boolean Is it the same server?
|
|
|
|
*/
|
|
|
|
public static function isLocal($url)
|
|
|
|
{
|
|
|
|
return Strings::compareLink(self::getBasepath($url, true), DI::baseUrl());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the given contact ID is on the same server
|
|
|
|
*
|
|
|
|
* @param string $url The contact link
|
|
|
|
*
|
|
|
|
* @return boolean Is it the same server?
|
|
|
|
*/
|
|
|
|
public static function isLocalById(int $cid)
|
|
|
|
{
|
|
|
|
$contact = DBA::selectFirst('contact', ['url', 'baseurl'], ['id' => $cid]);
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($contact['baseurl'])) {
|
|
|
|
$baseurl = self::getBasepath($contact['url'], true);
|
|
|
|
} else {
|
|
|
|
$baseurl = $contact['baseurl'];
|
|
|
|
}
|
|
|
|
|
|
|
|
return Strings::compareLink($baseurl, DI::baseUrl());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the public contact id of the given user id
|
|
|
|
*
|
|
|
|
* @param integer $uid User ID
|
|
|
|
*
|
|
|
|
* @return integer|boolean Public contact id for given user id
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function getPublicIdByUserId($uid)
|
|
|
|
{
|
|
|
|
$self = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]);
|
|
|
|
if (!DBA::isResult($self)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return self::getIdForURL($self['url'], 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the contact id for the user and the public contact id for a given contact id
|
|
|
|
*
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
public static function getPublicAndUserContacID($cid, $uid)
|
|
|
|
{
|
|
|
|
if (empty($uid) || empty($cid)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$contact = DBA::selectFirst('contact', ['id', 'uid', 'url'], ['id' => $cid]);
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// We quit when the user id don't match the user id of the provided contact
|
|
|
|
if (($contact['uid'] != $uid) && ($contact['uid'] != 0)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($contact['uid'] != 0) {
|
|
|
|
$pcid = Contact::getIdForURL($contact['url'], 0, false, ['url' => $contact['url']]);
|
|
|
|
if (empty($pcid)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$ucid = $contact['id'];
|
|
|
|
} else {
|
|
|
|
$pcid = $contact['id'];
|
|
|
|
$ucid = Contact::getIdForURL($contact['url'], $uid, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ['public' => $pcid, 'user' => $ucid];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns contact details for a given contact id in combination with a user id
|
|
|
|
*
|
|
|
|
* @param int $cid A contact ID
|
|
|
|
* @param int $uid The User ID
|
|
|
|
* @param array $fields The selected fields for the contact
|
|
|
|
*
|
|
|
|
* @return array The contact details
|
|
|
|
*
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function getContactForUser($cid, $uid, array $fields = [])
|
|
|
|
{
|
|
|
|
$contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => $uid]);
|
|
|
|
|
|
|
|
if (!DBA::isResult($contact)) {
|
|
|
|
return [];
|
|
|
|
} else {
|
|
|
|
return $contact;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Block contact id for user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
* @param boolean $blocked Is the contact blocked or unblocked?
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function setBlockedForUser($cid, $uid, $blocked)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($cdata['user'] != 0) {
|
|
|
|
DBA::update('contact', ['blocked' => $blocked], ['id' => $cdata['user'], 'pending' => false]);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns "block" state for contact id and user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact id blocked for the given user?
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function isBlockedByUser($cid, $uid)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$public_blocked = false;
|
|
|
|
|
|
|
|
if (!empty($cdata['public'])) {
|
|
|
|
$public_contact = DBA::selectFirst('user-contact', ['blocked'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
|
|
|
if (DBA::isResult($public_contact)) {
|
|
|
|
$public_blocked = $public_contact['blocked'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$user_blocked = $public_blocked;
|
|
|
|
|
|
|
|
if (!empty($cdata['user'])) {
|
|
|
|
$user_contact = DBA::selectFirst('contact', ['blocked'], ['id' => $cdata['user'], 'pending' => false]);
|
|
|
|
if (DBA::isResult($user_contact)) {
|
|
|
|
$user_blocked = $user_contact['blocked'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($user_blocked != $public_blocked) {
|
|
|
|
DBA::update('user-contact', ['blocked' => $user_blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $user_blocked;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ignore contact id for user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
* @param boolean $ignored Is the contact ignored or unignored?
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function setIgnoredForUser($cid, $uid, $ignored)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($cdata['user'] != 0) {
|
|
|
|
DBA::update('contact', ['readonly' => $ignored], ['id' => $cdata['user'], 'pending' => false]);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBA::update('user-contact', ['ignored' => $ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns "ignore" state for contact id and user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact id ignored for the given user?
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function isIgnoredByUser($cid, $uid)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$public_ignored = false;
|
|
|
|
|
|
|
|
if (!empty($cdata['public'])) {
|
|
|
|
$public_contact = DBA::selectFirst('user-contact', ['ignored'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
|
|
|
if (DBA::isResult($public_contact)) {
|
|
|
|
$public_ignored = $public_contact['ignored'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$user_ignored = $public_ignored;
|
|
|
|
|
|
|
|
if (!empty($cdata['user'])) {
|
|
|
|
$user_contact = DBA::selectFirst('contact', ['readonly'], ['id' => $cdata['user'], 'pending' => false]);
|
|
|
|
if (DBA::isResult($user_contact)) {
|
|
|
|
$user_ignored = $user_contact['readonly'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($user_ignored != $public_ignored) {
|
|
|
|
DBA::update('user-contact', ['ignored' => $user_ignored], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $user_ignored;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set "collapsed" for contact id and user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
* @param boolean $collapsed are the contact's posts collapsed or uncollapsed?
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function setCollapsedForUser($cid, $uid, $collapsed)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns "collapsed" state for contact id and user id
|
|
|
|
*
|
|
|
|
* @param int $cid Either public contact id or user's contact id
|
|
|
|
* @param int $uid User ID
|
|
|
|
*
|
|
|
|
* @return boolean is the contact id blocked for the given user?
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
* @throws \ImagickException
|
|
|
|
*/
|
|
|
|
public static function isCollapsedByUser($cid, $uid)
|
|
|
|
{
|
|
|
|
$cdata = self::getPublicAndUserContacID($cid, $uid);
|
|
|
|
if (empty($cdata)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$collapsed = false;
|
|
|
|
|
|
|
|
if (!empty($cdata['public'])) {
|
|
|
|
$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
|
|
|
if (DBA::isResult($public_contact)) {
|
|
|
|
$collapsed = $public_contact['collapsed'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of contacts belonging in a group
|
|
|
|
*
|
|
|
|
* @param int $gid
|
|
|
|
* @return array
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public static function getByGroupId($gid)
|
|
|
|
{
|
|
|
|
$return = [];
|
|
|
|
|
|
|
|
if (intval($gid)) {
|
|
|
|
$stmt = DBA::p('SELECT `group_member`.`contact-id`, `contact`.*
|
|
|
|
FROM `contact`
|
|
|
|
INNER JOIN `group_member`
|
|
|
|
ON `contact`.`id` = `group_member`.`contact-id`
|
|
|
|
WHERE `gid` = ?
|
|
|
|
AND `contact`.`uid` = ?
|
|
|
|
AND NOT `contact`.`self`
|
|
|
|
AND NOT `contact`.`deleted`
|
|
|
|
AND NOT `contact`.`blocked`
|
|
|
|
AND NOT `contact`.`pending`
|
|
|
|
ORDER BY `contact`.`name` ASC',
|
|
|
|
$gid,
|
|
|
|
local_user()
|
|
|
|
);
|
|
|
|
|
|
|
|
if (DBA::isResult($stmt)) {
|
|
|
|
$return = DBA::toArray($stmt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates the self-contact for the provided user id
|
|
|
|
*
|
|
|
|
* @param int $uid
|
|
|
|
* @return bool Operation success
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
*/
|
|
|
|
public static function createSelfFromUserId($uid)
|
|
|
|
{
|
|
|
|
// Only create the entry if it doesn't exist yet
|
|
|
|
if (DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname'], ['uid' => $uid]);
|
|
|
|
if (!DBA::isResult($user)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$return = DBA::insert('contact', [
|
|
|
|
'uid' => $user['uid'],
|
|
|
|
'created' => DateTimeFormat::utcNow(),
|
|
|
|
'self' => 1,
|
|
|
|
'name' => $user['username'],
|
|
|
|
'nick' => $user['nickname'],
|
|
|
|
'photo' => DI::baseUrl() . '/photo/profile/' . $user['uid'] . '.jpg',
|
|
|
|
'thumb' => DI::baseUrl() . '/photo/avatar/' . $user['uid'] . '.jpg',
|
|
|
|
'micro' => DI::baseUrl() . '/photo/micro/' . $user['uid'] . '.jpg',
|
|
|
|
'blocked' => 0,
|
|
|
|
'pending' => 0,
|
|
|
|
'url' => DI::baseUrl() . '/profile/' . $user['nickname'],
|
|
|
|
'nurl' => Strings::normaliseLink(DI::baseUrl() . '/profile/' . $user['nickname']),
|
|
|
|
'addr' => $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3),
|
|
|
|
'request' => DI::baseUrl() . '/dfrn_request/' . $user['nickname'],
|
|
|
|
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
|
|
|
|
'poll' => DI::baseUrl() . '/dfrn_poll/' . $user['nickname'],
|
|
|
|
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
|
|
|
|
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
|
|
|
|
'name-date' => DateTimeFormat::utcNow(),
|
|
|
|
'uri-date' => DateTimeFormat::utcNow(),
|
|
|
|
'avatar-date' => DateTimeFormat::utcNow(),
|
|
|
|
'closeness' => 0
|
|
|
|
]);
|
|
|
|
|
|
|
|
return $return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the self-contact for the provided user id
|
|
|
|
*
|
|
|
|
* @param int $uid
|
|
|
|
* @param boolean $update_avatar Force the avatar update
|
|
|
|
* @throws HTTPException\InternalServerErrorException
|
|
|
|
*/
|
|
|
|
public static function updateSelfFromUserID($uid, $update_avatar = false)
|
|
|
|
{
|
|
|
|
$fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar',
|
|
|
|
'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
|
|
|
|
'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco'];
|
|
|
|
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
|
|
|
if (!DBA::isResult($self)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$fields = ['nickname', 'page-flags', 'account-type'];
|
|
|
|
$user = DBA::selectFirst('user', $fields, ['uid' => $uid]);
|
|
|
|
if (!DBA::isResult($user)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
|
|
|
|
'country-name', 'pub_keywords', 'xmpp', 'net-publish'];
|
|
|
|
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
|
|
|
|
if (!DBA::isResult($profile)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_suffix = 'jpg';
|
|
|
|
|
|
|
|
$fields = ['name' => $profile['name'], 'nick' => $user['nickname'],
|
|
|
|
'avatar-date' => $self['avatar-date'], 'location' => Profile::formatLocation($profile),
|
|
|
|
'about' => $profile['about'], 'keywords' => $profile['pub_keywords'],
|
|
|
|
'contact-type' => $user['account-type'],
|
|
|
|
'xmpp' => $profile['xmpp']];
|
|
|
|
|
|
|
|
$avatar = Photo::selectFirst(['resource-id', 'type'], ['uid' => $uid, 'profile' => true]);
|
|
|
|
if (DBA::isResult($avatar)) {
|
|
|
|
if ($update_avatar) {
|
|
|
|
$fields['avatar-date'] = DateTimeFormat::utcNow();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creating the path to the avatar, beginning with the file suffix
|
|
|
|
$types = Images::supportedTypes();
|
|
|
|
if (isset($types[$avatar['type']])) {
|
|
|
|
$file_suffix = $types[$avatar['type']];
|
|
|
|
}
|
|
|
|
|
|