Merge pull request #10893 from nupplaphil/feat/depository_introduction

Move Introduction to Depository Paradigm
This commit is contained in:
Hypolite Petovan 2021-10-21 16:26:44 -04:00 committed by GitHub
commit 09a809aee5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 617 additions and 238 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2021.12-dev (Siberian Iris) -- Friendica 2021.12-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1441 -- DB_UPDATE_VERSION 1442
-- ------------------------------------------ -- ------------------------------------------
@ -715,11 +715,11 @@ CREATE TABLE IF NOT EXISTS `intro` (
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT '', `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`suggest-cid` int unsigned COMMENT 'Suggested contact', `suggest-cid` int unsigned COMMENT 'Suggested contact',
`knowyou` boolean NOT NULL DEFAULT '0' COMMENT '', `knowyou` boolean NOT NULL DEFAULT '0' COMMENT '',
`duplex` boolean NOT NULL DEFAULT '0' COMMENT '', `duplex` boolean NOT NULL DEFAULT '0' COMMENT 'deprecated',
`note` text COMMENT '', `note` text COMMENT '',
`hash` varchar(255) NOT NULL DEFAULT '' COMMENT '', `hash` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`datetime` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `datetime` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`blocked` boolean NOT NULL DEFAULT '1' COMMENT '', `blocked` boolean NOT NULL DEFAULT '0' COMMENT 'deprecated',
`ignore` boolean NOT NULL DEFAULT '0' COMMENT '', `ignore` boolean NOT NULL DEFAULT '0' COMMENT '',
PRIMARY KEY(`id`), PRIMARY KEY(`id`),
INDEX `contact-id` (`contact-id`), INDEX `contact-id` (`contact-id`),

View file

@ -36,17 +36,18 @@ doSomething($intros);
``` ```
After: After:
```php ```php
function doSomething(\Friendica\Collection\Introductions $intros) function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros)
{ {
foreach ($intros as $intro) { foreach ($intros as $intro) {
/** @var $intro \Friendica\Model\Introduction */ /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */
$introId = $intro->id; $introId = $intro->id;
} }
} }
/** @var $intros \Friendica\Collection\Introductions */ /** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
$intros = \Friendica\DI::intro()->select(['uid' => local_user()]); $intros = \Friendica\DI::intro()->selecForUser(local_user());
doSomething($intros); doSomething($intros);
``` ```

View file

@ -14,11 +14,11 @@ Fields
| contact-id | | int unsigned | NO | | 0 | | | contact-id | | int unsigned | NO | | 0 | |
| suggest-cid | Suggested contact | int unsigned | YES | | NULL | | | suggest-cid | Suggested contact | int unsigned | YES | | NULL | |
| knowyou | | boolean | NO | | 0 | | | knowyou | | boolean | NO | | 0 | |
| duplex | | boolean | NO | | 0 | | | duplex | deprecated | boolean | NO | | 0 | |
| note | | text | YES | | NULL | | | note | | text | YES | | NULL | |
| hash | | varchar(255) | NO | | | | | hash | | varchar(255) | NO | | | |
| datetime | | datetime | NO | | 0001-01-01 00:00:00 | | | datetime | | datetime | NO | | 0001-01-01 00:00:00 | |
| blocked | | boolean | NO | | 1 | | | blocked | deprecated | boolean | NO | | 0 | |
| ignore | | boolean | NO | | 0 | | | ignore | | boolean | NO | | 0 | |
Indexes Indexes

View file

@ -1,29 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @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\Collection;
use Friendica\BaseCollection;
class Introductions extends BaseCollection
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Friendica\Contact\Introduction\Collection;
use Friendica\BaseCollection;
class Introductions extends BaseCollection
{
}

View file

@ -0,0 +1,209 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @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\Contact\Introduction\Depository;
use Friendica\BaseDepository;
use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
use Friendica\Contact\Introduction\Exception\IntroductionPersistenceException;
use Friendica\Contact\Introduction\Collection;
use Friendica\Contact\Introduction\Entity;
use Friendica\Contact\Introduction\Factory;
use Friendica\Database\Database;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
class Introduction extends BaseDepository
{
/** @var Factory\Introduction */
protected $factory;
protected static $table_name = 'intro';
public function __construct(Database $database, LoggerInterface $logger, Factory\Introduction $factory)
{
parent::__construct($database, $logger, $factory);
}
/**
* @param array $condition
* @param array $params
*
* @return Entity\Introduction
*
* @throws NotFoundException the underlying exception if there's no Introduction with the given conditions
*/
private function selectOne(array $condition, array $params = []): Entity\Introduction
{
return parent::_selectOne($condition, $params);
}
/**
* Converts a given Introduction into a DB compatible row array
*
* @param Entity\Introduction $introduction
*
* @return array
*/
protected function convertToTableRow(Entity\Introduction $introduction): array
{
return [
'uid' => $introduction->uid,
'contact-id' => $introduction->cid,
'suggest-cid' => $introduction->sid,
'knowyou' => $introduction->knowyou ? 1 : 0,
'note' => $introduction->note,
'hash' => $introduction->hash,
'ignore' => $introduction->ignore ? 1 : 0,
'datetime' => $introduction->datetime->format(DateTimeFormat::MYSQL),
];
}
/**
* @param int $id
* @param int $uid
*
* @return Entity\Introduction
*
* @throws IntroductionNotFoundException in case there is no Introduction with this id
*/
public function selectOneById(int $id, int $uid): Entity\Introduction
{
try {
return $this->selectOne(['id' => $id, 'uid' => $uid]);
} catch (NotFoundException $exception) {
throw new IntroductionNotFoundException(sprintf('There is no Introduction with the ID %d for the user %d', $id, $uid), $exception);
}
}
/**
* Selects introductions for a given user
*
* @param int $uid
* @param int|null $min_id
* @param int|null $max_id
* @param int $limit
*
* @return Collection\Introductions
*/
public function selectForUser(int $uid, int $min_id = null, int $max_id = null, int $limit = self::LIMIT): Collection\Introductions
{
try {
$BaseCollection = parent::_selectByBoundaries(
['`uid = ?` AND NOT `ignore`',$uid],
['order' => ['id' => 'DESC']],
$min_id, $max_id, $limit);
} catch (\Exception $e) {
throw new IntroductionPersistenceException(sprintf('Cannot select Introductions for used %d', $uid), $e);
}
return new Collection\Introductions($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount());
}
/**
* Selects the introduction for a given contact
*
* @param int $cid
*
* @return Entity\Introduction
*
* @throws IntroductionNotFoundException in case there is not Introduction for this contact
*/
public function selectForContact(int $cid): Entity\Introduction
{
try {
return $this->selectOne(['contact-id' => $cid]);
} catch (NotFoundException $exception) {
throw new IntroductionNotFoundException(sprintf('There is no Introduction for the contact %d', $cid), $exception);
}
}
public function countActiveForUser($uid, array $params = []): int
{
try {
return $this->count(['ignore' => false, 'uid' => $uid], $params);
} catch (\Exception $e) {
throw new IntroductionPersistenceException(sprintf('Cannot count Introductions for used %d', $uid), $e);
}
}
/**
* Checks, if the suggested contact already exists for the user
*
* @param int $sid
* @param int $uid
*
* @return bool
*/
public function suggestionExistsForUser(int $sid, int $uid): bool
{
try {
return $this->exists(['uid' => $uid, 'suggest-cid' => $sid]);
} catch (\Exception $e) {
throw new IntroductionPersistenceException(sprintf('Cannot check suggested Introduction for contact %d and user %d', $sid, $uid), $e);
}
}
/**
* @param Entity\Introduction $introduction
*
* @return bool
*
* @throws IntroductionPersistenceException in case the underlying storage cannot delete the Introduction
*/
public function delete(Entity\Introduction $introduction): bool
{
if (!$introduction->id) {
return false;
}
try {
return $this->db->delete(self::$table_name, ['id' => $introduction->id]);
} catch (\Exception $e) {
throw new IntroductionPersistenceException(sprintf('Cannot delete Introduction with id %d', $introduction->id), $e);
}
}
/**
* @param Entity\Introduction $introduction
*
* @return Entity\Introduction
*
* @throws IntroductionPersistenceException In case the underlying storage cannot save the Introduction
*/
public function save(Entity\Introduction $introduction): Entity\Introduction
{
try {
$fields = $this->convertToTableRow($introduction);
if ($introduction->id) {
$this->db->update(self::$table_name, $fields, ['id' => $introduction->id]);
return $this->factory->createFromTableRow($fields);
} else {
$this->db->insert(self::$table_name, $fields);
return $this->selectOneById($this->db->lastInsertId(), $introduction->uid);
}
} catch (\Exception $exception) {
throw new IntroductionPersistenceException(sprintf('Cannot insert/update the Introduction %d for user %d', $introduction->id, $introduction->uid), $exception);
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @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\Contact\Introduction\Entity;
use Friendica\BaseEntity;
/**
* @property-read int $uid
* @property-read int $cid
* @property-read int|null $sid
* @property-read bool $knowyou
* @property-read string $note
* @property-read string $hash
* @property-read \DateTime $datetime
* @property-read bool $ignore
* @property-read int|null $id
*/
class Introduction extends BaseEntity
{
/** @var int */
protected $uid;
/** @var int */
protected $cid;
/** @var int|null */
protected $sid;
/** @var bool */
protected $knowyou;
/** @var string */
protected $note;
/** @var string */
protected $hash;
/** @var \DateTime */
protected $datetime;
/** @var bool */
protected $ignore;
/** @var int|null */
protected $id;
/**
* @param int $uid
* @param int $cid
* @param int|null $sid
* @param bool $knowyou
* @param string $note
* @param string $hash
* @param \DateTime $datetime
* @param bool $ignore
* @param int|null $id
*/
public function __construct(int $uid, int $cid, ?int $sid, bool $knowyou, string $note, string $hash, \DateTime $datetime, bool $ignore, ?int $id)
{
$this->uid = $uid;
$this->cid = $cid;
$this->sid = $sid;
$this->knowyou = $knowyou;
$this->note = $note;
$this->hash = $hash;
$this->datetime = $datetime;
$this->ignore = $ignore;
$this->id = $id;
}
/**
* Ignore the current Introduction
*/
public function ignore()
{
$this->ignore = true;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Friendica\Contact\Introduction\Exception;
class IntroductionNotFoundException extends \OutOfBoundsException
{
public function __construct($message = "", \Throwable $previous = null)
{
parent::__construct($message, 404, $previous);
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Friendica\Contact\Introduction\Exception;
class IntroductionPersistenceException extends \RuntimeException
{
public function __construct($message = "", \Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}
}

View file

@ -0,0 +1,73 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @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\Contact\Introduction\Factory;
use Friendica\BaseFactory;
use Friendica\Contact\Introduction\Entity;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
class Introduction extends BaseFactory implements ICanCreateFromTableRow
{
/**
* @inheritDoc
*/
public function createFromTableRow(array $row): Entity\Introduction
{
return new Entity\Introduction(
$row['uid'] ?? 0,
$row['contact-id'] ?? 0,
$row['suggest-cid'] ?? null,
!empty($row['knowyou']),
$row['note'] ?? '',
$row['hash'] ?? '',
new \DateTime($row['datetime'] ?? 'now', new \DateTimeZone('UTC')),
!empty($row['ignore']),
$row['id'] ?? null
);
}
public function createNew(
int $uid,
int $cid,
string $note,
int $sid = null,
bool $knowyou = false
): Entity\Introduction {
return $this->createFromTableRow([
'uid' => $uid,
'suggest-cid' => $sid,
'contact-id' => $cid,
'knowyou' => $knowyou,
'note' => $note,
'hash' => Strings::getRandomHex(),
'datetime' => DateTimeFormat::utcNow(),
'ignore' => false,
]);
}
public function createDummy(?int $id): Entity\Introduction
{
return $this->createFromTableRow(['id' => $id]);
}
}

View file

@ -435,11 +435,19 @@ abstract class DI
} }
/** /**
* @return Repository\Introduction * @return Contact\Introduction\Depository\Introduction
*/ */
public static function intro() public static function intro()
{ {
return self::$dice->create(Repository\Introduction::class); return self::$dice->create(Contact\Introduction\Depository\Introduction::class);
}
/**
* @return Contact\Introduction\Factory\Introduction
*/
public static function introFactory()
{
return self::$dice->create(Contact\Introduction\Factory\Introduction::class);
} }
public static function permissionSet(): Security\PermissionSet\Depository\PermissionSet public static function permissionSet(): Security\PermissionSet\Depository\PermissionSet

View file

@ -23,9 +23,9 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
use Friendica\BaseFactory; use Friendica\BaseFactory;
use Friendica\Contact\Introduction\Entity\Introduction;
use Friendica\Model\APContact; use Friendica\Model\APContact;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Introduction;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use ImagickException; use ImagickException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -49,7 +49,7 @@ class FollowRequest extends BaseFactory
*/ */
public function createFromIntroduction(Introduction $introduction): \Friendica\Object\Api\Mastodon\FollowRequest public function createFromIntroduction(Introduction $introduction): \Friendica\Object\Api\Mastodon\FollowRequest
{ {
$cdata = Contact::getPublicAndUserContactID($introduction->{'contact-id'}, $introduction->uid); $cdata = Contact::getPublicAndUserContactID($introduction->cid, $introduction->uid);
if (empty($cdata)) { if (empty($cdata)) {
$this->logger->warning('Wrong introduction data', ['Introduction' => $introduction]); $this->logger->warning('Wrong introduction data', ['Introduction' => $introduction]);

View file

@ -22,6 +22,7 @@
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
use Friendica\Content\Pager; use Friendica\Content\Pager;
use Friendica\Content\Text\HTML; use Friendica\Content\Text\HTML;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -1085,9 +1086,11 @@ class Contact
]; ];
if (!empty($contact['pending'])) { if (!empty($contact['pending'])) {
$intro = DBA::selectFirst('intro', ['id'], ['contact-id' => $contact['id']]); try {
if (DBA::isResult($intro)) { $intro = DI::intro()->selectForContact($contact['id']);
$menu['follow'] = [DI::l10n()->t('Approve'), 'notifications/intros/' . $intro['id'], true]; $menu['follow'] = [DI::l10n()->t('Approve'), 'notifications/intros/' . $intro['id'], true];
} catch (IntroductionNotFoundException $exception) {
DI::logger()->error('Pending contact doesn\'t have an introduction.', ['exception' => $exception]);
} }
} }
} }
@ -2706,12 +2709,13 @@ class Contact
$user = DBA::selectFirst('user', $fields, ['uid' => $importer['uid']]); $user = DBA::selectFirst('user', $fields, ['uid' => $importer['uid']]);
if (DBA::isResult($user) && !in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) { if (DBA::isResult($user) && !in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) {
// create notification // create notification
$hash = Strings::getRandomHex();
if (is_array($contact_record)) { if (is_array($contact_record)) {
DBA::insert('intro', ['uid' => $importer['uid'], 'contact-id' => $contact_record['id'], $intro = DI::introFactory()->createNew(
'blocked' => false, 'knowyou' => false, 'note' => $note, $importer['uid'],
'hash' => $hash, 'datetime' => DateTimeFormat::utcNow()]); $contact_record['id'],
$note
);
DI::intro()->save($intro);
} }
Group::addMember(User::getDefaultGroup($importer['uid'], $contact_record["network"]), $contact_record['id']); Group::addMember(User::getDefaultGroup($importer['uid'], $contact_record["network"]), $contact_record['id']);

View file

@ -19,64 +19,43 @@
* *
*/ */
namespace Friendica\Model; namespace Friendica\Model\Contact;
use Friendica\BaseModel; use Friendica\Contact\Introduction\Entity;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Database\Database; use Friendica\DI;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Model\Contact;
use Friendica\Model\User;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Repository;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
/** class Introduction
* @property int uid
* @property int fid
* @property int contact-id
* @property bool knowyou
* @property bool duplex
* @property string note
* @property string hash
* @property string datetime
* @property bool blocked
* @property bool ignore
*/
class Introduction extends BaseModel
{ {
/** @var Repository\Introduction */
protected $intro;
public function __construct(Database $dba, LoggerInterface $logger, Repository\Introduction $intro, array $data = [])
{
parent::__construct($dba, $logger, $data);
$this->intro = $intro;
}
/** /**
* Confirms a follow request and sends a notice to the remote contact. * Confirms a follow request and sends a notice to the remote contact.
* *
* @param bool $duplex Is it a follow back? * @param Entity\Introduction $introduction
* @param bool|null $hidden Should this contact be hidden? null = no change * @param bool $duplex Is it a follow back?
* @return bool * @param bool|null $hidden Should this contact be hidden? null = no change
*
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException * @throws HTTPException\NotFoundException
* @throws \ImagickException * @throws \ImagickException
*/ */
public function confirm(bool $duplex = false, bool $hidden = null) public static function confirm(Entity\Introduction $introduction, bool $duplex = false, ?bool $hidden = null): void
{ {
$this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]); DI::logger()->info('Confirming follower', ['cid' => $introduction->cid]);
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]); $contact = Contact::selectFirst([], ['id' => $introduction->cid, 'uid' => $introduction->uid]);
if (!$contact) { if (!$contact) {
throw new HTTPException\NotFoundException('Contact record not found.'); throw new HTTPException\NotFoundException('Contact record not found.');
} }
$newRelation = $contact['rel']; $newRelation = $contact['rel'];
$writable = $contact['writable']; $writable = $contact['writable'];
if (!empty($contact['protocol'])) { if (!empty($contact['protocol'])) {
$protocol = $contact['protocol']; $protocol = $contact['protocol'];
@ -117,53 +96,24 @@ class Introduction extends BaseModel
if ($newRelation == Contact::FRIEND) { if ($newRelation == Contact::FRIEND) {
if ($protocol == Protocol::DIASPORA) { if ($protocol == Protocol::DIASPORA) {
$ret = Diaspora::sendShare(User::getById($contact['uid']), $contact); $ret = Diaspora::sendShare(User::getById($contact['uid']), $contact);
$this->logger->info('share returns', ['return' => $ret]); DI::logger()->info('share returns', ['return' => $ret]);
} elseif ($protocol == Protocol::ACTIVITYPUB) { } elseif ($protocol == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']); ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
} }
} }
return $this->intro->delete($this);
}
/**
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
* additional follow requests.
*
* @return bool
* @throws \Exception
*/
public function ignore()
{
$this->ignore = true;
return $this->intro->update($this);
} }
/** /**
* Discards the introduction and sends a rejection message to AP contacts. * Discards the introduction and sends a rejection message to AP contacts.
* *
* @return bool * @param Entity\Introduction $introduction
*
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException
* @throws \ImagickException * @throws \ImagickException
*/ */
public function discard() public static function discard(Entity\Introduction $introduction): void
{ {
// If it is a friend suggestion, the contact is not a new friend but an existing friend $contact = Contact::selectFirst([], ['id' => $introduction->cid, 'uid' => $introduction->uid]);
// that should not be deleted.
if (!$this->fid) {
// When the contact entry had been created just for that intro, we want to get rid of it now
$condition = ['id' => $this->{'contact-id'}, 'uid' => $this->uid,
'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
if ($this->dba->exists('contact', $condition)) {
Contact::remove($this->{'contact-id'});
} else {
Contact::update(['pending' => false], ['id' => $this->{'contact-id'}]);
}
}
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
if (!empty($contact)) { if (!empty($contact)) {
if (!empty($contact['protocol'])) { if (!empty($contact['protocol'])) {
$protocol = $contact['protocol']; $protocol = $contact['protocol'];
@ -175,7 +125,5 @@ class Introduction extends BaseModel
ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']); ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']);
} }
} }
return $this->intro->delete($this);
} }
} }

View file

@ -23,6 +23,7 @@ namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
@ -34,7 +35,6 @@ class FollowRequests extends BaseApi
/** /**
* @param array $parameters * @param array $parameters
* @throws HTTPException\BadRequestException * @throws HTTPException\BadRequestException
* @throws HTTPException\ForbiddenException
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException * @throws HTTPException\NotFoundException
* @throws HTTPException\UnauthorizedException * @throws HTTPException\UnauthorizedException
@ -48,25 +48,28 @@ class FollowRequests extends BaseApi
self::checkAllowedScope(self::SCOPE_FOLLOW); self::checkAllowedScope(self::SCOPE_FOLLOW);
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$introduction = DI::intro()->selectFirst(['id' => $parameters['id'], 'uid' => $uid]); $introduction = DI::intro()->selectOneById($parameters['id'], $uid);
$contactId = $introduction->{'contact-id'}; $contactId = $introduction->cid;
switch ($parameters['action']) { switch ($parameters['action']) {
case 'authorize': case 'authorize':
$introduction->confirm(); Contact\Introduction::confirm($introduction);
$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
DI::intro()->delete($introduction);
break; break;
case 'ignore': case 'ignore':
$introduction->ignore(); $introduction->ignore();
$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
DI::intro()->save($introduction);
break; break;
case 'reject': case 'reject':
$introduction->discard(); Contact\Introduction::discard($introduction);
$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
DI::intro()->delete($introduction);
break; break;
default: default:
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"'); throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
@ -92,13 +95,7 @@ class FollowRequests extends BaseApi
'limit' => 40, // Maximum number of results to return. Defaults to 40. Paginate using the HTTP Link header. 'limit' => 40, // Maximum number of results to return. Defaults to 40. Paginate using the HTTP Link header.
]); ]);
$introductions = DI::intro()->selectByBoundaries( $introductions = DI::intro()->selectForUser($uid, $request['min_id'], $request['max_id'], $request['limit']);
['`uid` = ? AND NOT `ignore`', $uid],
['order' => ['id' => 'DESC']],
$request['min_id'],
$request['max_id'],
$request['limit']
);
$return = []; $return = [];

View file

@ -133,7 +133,7 @@ class Delegation extends BaseModule
$params = ['distinct' => true, 'expression' => 'convid']; $params = ['distinct' => true, 'expression' => 'convid'];
$notifications += DBA::count('mail', ['uid' => $identity['uid'], 'seen' => false], $params); $notifications += DBA::count('mail', ['uid' => $identity['uid'], 'seen' => false], $params);
$notifications += DBA::count('intro', ['blocked' => false, 'ignore' => false, 'uid' => $identity['uid']]); $notifications += DI::intro()->countActiveForUser($identity['uid']);
$identities[$key]['notifications'] = $notifications; $identities[$key]['notifications'] = $notifications;
} }

View file

@ -3,6 +3,7 @@ namespace Friendica\Module;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact;
/** /**
* Process follow request confirmations * Process follow request confirmations
@ -21,12 +22,11 @@ class FollowConfirm extends BaseModule
$duplex = intval($_POST['duplex'] ?? 0); $duplex = intval($_POST['duplex'] ?? 0);
$hidden = intval($_POST['hidden'] ?? 0); $hidden = intval($_POST['hidden'] ?? 0);
$intro = DI::intro()->selectFirst(['id' => $intro_id, 'uid' => local_user()]); $intro = DI::intro()->selectOneById($intro_id, local_user());
$cid = $intro->{'contact-id'}; Contact\Introduction::confirm($intro, $duplex, $hidden);
DI::intro()->delete($intro);
$intro->confirm($duplex, $hidden); DI::baseUrl()->redirect('contact/' . $intro->cid);
DI::baseUrl()->redirect('contact/' . intval($cid));
} }
} }

View file

@ -24,6 +24,7 @@ namespace Friendica\Module\Notifications;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
@ -50,14 +51,16 @@ class Notification extends BaseModule
$request_id = $parameters['id'] ?? false; $request_id = $parameters['id'] ?? false;
if ($request_id) { if ($request_id) {
$intro = DI::intro()->selectFirst(['id' => $request_id, 'uid' => local_user()]); $intro = DI::intro()->selectOneById($request_id, local_user());
switch ($_POST['submit']) { switch ($_POST['submit']) {
case DI::l10n()->t('Discard'): case DI::l10n()->t('Discard'):
$intro->discard(); Contact\Introduction::discard($intro);
DI::intro()->delete($intro);
break; break;
case DI::l10n()->t('Ignore'): case DI::l10n()->t('Ignore'):
$intro->ignore(); $intro->ignore();
DI::intro()->save($intro);
break; break;
} }

View file

@ -485,9 +485,9 @@ class Notify extends BaseDepository
private function storeAndSend($params, $sitelink, $tsitelink, $hsitelink, $title, $subject, $preamble, $epreamble, $body, $itemlink, $show_in_notification_page) private function storeAndSend($params, $sitelink, $tsitelink, $hsitelink, $title, $subject, $preamble, $epreamble, $body, $itemlink, $show_in_notification_page)
{ {
$item_id = $params['item']['id'] ?? 0; $item_id = $params['item']['id'] ?? 0;
$uri_id = $params['item']['uri-id'] ?? 0; $uri_id = $params['item']['uri-id'] ?? null;
$parent_id = $params['item']['parent'] ?? 0; $parent_id = $params['item']['parent'] ?? 0;
$parent_uri_id = $params['item']['parent-uri-id'] ?? 0; $parent_uri_id = $params['item']['parent-uri-id'] ?? null;
// Ensure that the important fields are set at any time // Ensure that the important fields are set at any time
$fields = ['nickname']; $fields = ['nickname'];

View file

@ -60,14 +60,14 @@ class Notify extends BaseEntity
protected $name_cache; protected $name_cache;
/** @var string */ /** @var string */
protected $msg_cache; protected $msg_cache;
/** @var int */ /** @var int|null */
protected $uriId; protected $uriId;
/** @var int */ /** @var int|null */
protected $parentUriId; protected $parentUriId;
/** @var int */ /** @var int */
protected $id; protected $id;
public function __construct(int $type, string $name, UriInterface $url, UriInterface $photo, DateTime $date, int $uid, UriInterface $link, bool $seen, string $verb, string $otype, string $name_cache, string $msg = null, string $msg_cache = null, int $itemId = null, int $uriId = null, int $parent = null, int $parentUriId = null, int $id = null) public function __construct(int $type, string $name, UriInterface $url, UriInterface $photo, DateTime $date, int $uid, UriInterface $link, bool $seen, string $verb, string $otype, string $name_cache, string $msg = null, string $msg_cache = null, int $itemId = null, int $uriId = null, int $parent = null, ?int $parentUriId = null, ?int $id = null)
{ {
$this->type = $type; $this->type = $type;
$this->name = $name; $this->name = $name;

View file

@ -86,7 +86,6 @@ class ProfileField extends BaseDepository
$Entities = new Collection\ProfileFields(); $Entities = new Collection\ProfileFields();
foreach ($rows as $fields) { foreach ($rows as $fields) {
$this->logger->warning('row', ['row' => $fields]);
$Entities[] = $this->factory->createFromTableRow($fields); $Entities[] = $this->factory->createFromTableRow($fields);
} }

View file

@ -1352,7 +1352,7 @@ class DFRN
} }
// Quit if we already have an introduction for this person // Quit if we already have an introduction for this person
if (DBA::exists('intro', ['uid' => $uid, 'suggest-cid' => $cid])) { if (DI::intro()->suggestionExistsForUser($cid, $uid)) {
return false; return false;
} }
@ -1366,10 +1366,13 @@ class DFRN
$suggest['title'] = ''; $suggest['title'] = '';
$suggest['body'] = $note; $suggest['body'] = $note;
$hash = Strings::getRandomHex(); DI::intro()->save(DI::introFactory()->createNew(
$fields = ['uid' => $suggest['uid'], 'suggest-cid' => $cid, 'contact-id' => $suggest['cid'], $suggest['uid'],
'note' => $suggest['body'], 'hash' => $hash, 'datetime' => DateTimeFormat::utcNow(), 'blocked' => false]; $suggest['cid'],
DBA::insert('intro', $fields); $suggest['body'],
null,
$cid
));
DI::notify()->createFromArray([ DI::notify()->createFromArray([
'type' => Notification\Type::SUGGEST, 'type' => Notification\Type::SUGGEST,

View file

@ -1,79 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @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\Repository;
use Friendica\BaseRepository;
use Friendica\Collection;
use Friendica\Model;
class Introduction extends BaseRepository
{
protected static $table_name = 'intro';
protected static $model_class = Model\Introduction::class;
protected static $collection_class = Collection\Introductions::class;
/**
* @param array $data
* @return Model\Introduction
*/
protected function create(array $data)
{
return new Model\Introduction($this->dba, $this->logger, $this, $data);
}
/**
* @param array $condition
* @return Model\Introduction
* @throws \Friendica\Network\HTTPException\NotFoundException
*/
public function selectFirst(array $condition)
{
return parent::selectFirst($condition);
}
/**
* @param array $condition
* @param array $params
* @return Collection\Introductions
* @throws \Exception
*/
public function select(array $condition = [], array $params = [])
{
return parent::select($condition, $params);
}
/**
* @param array $condition
* @param array $params
* @param int|null $min_id
* @param int|null $max_id
* @param int $limit
* @return Collection\Introductions
* @throws \Exception
*/
public function selectByBoundaries(array $condition = [], array $params = [], int $min_id = null, int $max_id = null, int $limit = self::LIMIT)
{
return parent::selectByBoundaries($condition, $params, $min_id, $max_id, $limit);
}
}

View file

@ -24,6 +24,7 @@ namespace Friendica\Worker\Contact;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\DBStructure; use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Model\Post; use Friendica\Model\Post;
@ -84,7 +85,7 @@ class RemoveContent
DBA::delete('user-contact', ['cid' => $id]); DBA::delete('user-contact', ['cid' => $id]);
DBA::delete('group_member', ['contact-id' => $id]); DBA::delete('group_member', ['contact-id' => $id]);
DBA::delete('intro', ['contact-id' => $id]); DI::intro()->delete(DI::introFactory()->createDummy($id));
return $contact; return $contact;
} }

View file

@ -55,7 +55,7 @@
use Friendica\Database\DBA; use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1441); define('DB_UPDATE_VERSION', 1442);
} }
return [ return [
@ -778,11 +778,11 @@ return [
"contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => ""], "contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => ""],
"suggest-cid" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Suggested contact"], "suggest-cid" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Suggested contact"],
"knowyou" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "knowyou" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"duplex" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "duplex" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "deprecated"],
"note" => ["type" => "text", "comment" => ""], "note" => ["type" => "text", "comment" => ""],
"hash" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "hash" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
"datetime" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "datetime" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
"blocked" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => ""], "blocked" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "deprecated"],
"ignore" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "ignore" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
], ],
"indexes" => [ "indexes" => [

View file

@ -0,0 +1,113 @@
<?php
namespace Friendica\Test\src\Contact\Introduction\Factory;
use Friendica\Contact\Introduction\Factory\Introduction;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
class IntroductionTest extends TestCase
{
public function dataRow()
{
return [
'default' => [
'input' => [
'uid' => 42,
'suggest-cid' => 13,
'contact-id' => 24,
'knowyou' => 1,
'note' => 'a note',
'hash' => '12345',
'datetime' => '1970-01-01 00:00:00',
'ignore' => 0,
'id' => 56,
],
'assertion' => [
'uid' => 42,
'suggest-cid' => 13,
'contact-id' => 24,
'knowyou' => true,
'note' => 'a note',
'hash' => '12345',
'datetime' => new \DateTime('1970-01-01 00:00:00', new \DateTimeZone('UTC')),
'ignore' => false,
'id' => 56,
]
],
'empty' => [
'input' => [
],
'assertion' => [
'uid' => 0,
'contact-id' => 0,
'suggest-cid' => null,
'knowyou' => false,
'note' => '',
'ignore' => false,
'id' => null,
]
],
];
}
public function assertIntro(\Friendica\Contact\Introduction\Entity\Introduction $intro, array $assertion)
{
self::assertEquals($intro->id, $assertion['id'] ?? null);
self::assertEquals($intro->uid, $assertion['uid'] ?? 0);
self::assertEquals($intro->cid, $assertion['contact-id'] ?? 0);
self::assertEquals($intro->sid, $assertion['suggest-cid'] ?? null);
self::assertEquals($intro->knowyou, $assertion['knowyou'] ?? false);
self::assertEquals($intro->note, $assertion['note'] ?? '');
if (isset($assertion['hash'])) {
self::assertEquals($intro->hash, $assertion['hash']);
} else {
self::assertIsString($intro->hash);
}
if (isset($assertion['datetime'])) {
self::assertEquals($intro->datetime, $assertion['datetime']);
} else {
self::assertInstanceOf(\DateTime::class, $intro->datetime);
}
self::assertEquals($intro->ignore, $assertion['ignore'] ?? false);
}
/**
* @dataProvider dataRow
*/
public function testCreateFromTableRow(array $input, array $assertion)
{
$factory = new Introduction(new NullLogger());
$intro = $factory->createFromTableRow($input);
$this->assertIntro($intro, $assertion);
}
/**
* @dataProvider dataRow
*/
public function testCreateNew(array $input, array $assertion)
{
$factory = new Introduction(new NullLogger());
$intro = $factory->createNew($input['uid'] ?? 0, $input['cid'] ?? 0, $input['note'] ?? '');
$this->assertIntro($intro, [
'uid' => $input['uid'] ?? 0,
'contact-id' => $input['cid'] ?? 0,
'note' => $input['note'] ?? '',
]);
}
/**
* @dataProvider dataRow
*/
public function testCreateDummy(array $input, array $assertion)
{
$factory = new Introduction(new NullLogger());
$intro = $factory->createDummy($input['id'] ?? null);
$this->assertIntro($intro, ['id' => $input['id'] ?? null]);
}
}

View file

@ -1039,7 +1039,7 @@ function update_1440()
return Update::SUCCESS; return Update::SUCCESS;
} }
function update__1441() function update_1441()
{ {
$languages = DI::l10n()->getAvailableLanguages(); $languages = DI::l10n()->getAvailableLanguages();
@ -1053,3 +1053,11 @@ function update__1441()
return Update::SUCCESS; return Update::SUCCESS;
} }
function update_1442()
{
// transform blocked intros into ignored intros
DBA::update('intro', ['ignore' => 1, 'blocked' => 0], ['blocked' => 1]);
return Update::SUCCESS;
}