diff --git a/database.sql b/database.sql index 466a8d9b77..fcbcbb3941 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- 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 '', `suggest-cid` int unsigned COMMENT 'Suggested contact', `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 '', `hash` varchar(255) NOT NULL DEFAULT '' 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 '', PRIMARY KEY(`id`), INDEX `contact-id` (`contact-id`), diff --git a/doc/Developer-Domain-Driven-Design.md b/doc/Developer-Domain-Driven-Design.md index b8d886aa0d..1e77dd2f0b 100644 --- a/doc/Developer-Domain-Driven-Design.md +++ b/doc/Developer-Domain-Driven-Design.md @@ -36,17 +36,18 @@ doSomething($intros); ``` After: + ```php -function doSomething(\Friendica\Collection\Introductions $intros) +function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros) { foreach ($intros as $intro) { - /** @var $intro \Friendica\Model\Introduction */ + /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */ $introId = $intro->id; } } -/** @var $intros \Friendica\Collection\Introductions */ -$intros = \Friendica\DI::intro()->select(['uid' => local_user()]); +/** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */ +$intros = \Friendica\DI::intro()->selecForUser(local_user()); doSomething($intros); ``` diff --git a/doc/database/db_intro.md b/doc/database/db_intro.md index da0cbe26ab..3db6240d03 100644 --- a/doc/database/db_intro.md +++ b/doc/database/db_intro.md @@ -14,11 +14,11 @@ Fields | contact-id | | int unsigned | NO | | 0 | | | suggest-cid | Suggested contact | int unsigned | YES | | NULL | | | knowyou | | boolean | NO | | 0 | | -| duplex | | boolean | NO | | 0 | | +| duplex | deprecated | boolean | NO | | 0 | | | note | | text | YES | | NULL | | | hash | | varchar(255) | NO | | | | | datetime | | datetime | NO | | 0001-01-01 00:00:00 | | -| blocked | | boolean | NO | | 1 | | +| blocked | deprecated | boolean | NO | | 0 | | | ignore | | boolean | NO | | 0 | | Indexes diff --git a/src/Collection/Introductions.php b/src/Collection/Introductions.php deleted file mode 100644 index e95433193f..0000000000 --- a/src/Collection/Introductions.php +++ /dev/null @@ -1,29 +0,0 @@ -. - * - */ - -namespace Friendica\Collection; - -use Friendica\BaseCollection; - -class Introductions extends BaseCollection -{ - -} diff --git a/src/Contact/Introduction/Collection/Introductions.php b/src/Contact/Introduction/Collection/Introductions.php new file mode 100644 index 0000000000..4df89cee43 --- /dev/null +++ b/src/Contact/Introduction/Collection/Introductions.php @@ -0,0 +1,9 @@ +. + * + */ + +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); + } + } +} diff --git a/src/Contact/Introduction/Entity/Introduction.php b/src/Contact/Introduction/Entity/Introduction.php new file mode 100644 index 0000000000..9c5fb060ec --- /dev/null +++ b/src/Contact/Introduction/Entity/Introduction.php @@ -0,0 +1,89 @@ +. + * + */ + +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; + } +} diff --git a/src/Contact/Introduction/Exception/IntroductionNotFoundException.php b/src/Contact/Introduction/Exception/IntroductionNotFoundException.php new file mode 100644 index 0000000000..f48c9ea22c --- /dev/null +++ b/src/Contact/Introduction/Exception/IntroductionNotFoundException.php @@ -0,0 +1,11 @@ +. + * + */ + +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]); + } +} diff --git a/src/DI.php b/src/DI.php index 31542e3d52..28df28a325 100644 --- a/src/DI.php +++ b/src/DI.php @@ -435,11 +435,19 @@ abstract class DI } /** - * @return Repository\Introduction + * @return Contact\Introduction\Depository\Introduction */ 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 diff --git a/src/Factory/Api/Mastodon/FollowRequest.php b/src/Factory/Api/Mastodon/FollowRequest.php index f2be0e589a..1d90c55f04 100644 --- a/src/Factory/Api/Mastodon/FollowRequest.php +++ b/src/Factory/Api/Mastodon/FollowRequest.php @@ -23,9 +23,9 @@ namespace Friendica\Factory\Api\Mastodon; use Friendica\App\BaseURL; use Friendica\BaseFactory; +use Friendica\Contact\Introduction\Entity\Introduction; use Friendica\Model\APContact; use Friendica\Model\Contact; -use Friendica\Model\Introduction; use Friendica\Network\HTTPException; use ImagickException; use Psr\Log\LoggerInterface; @@ -49,7 +49,7 @@ class FollowRequest extends BaseFactory */ 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)) { $this->logger->warning('Wrong introduction data', ['Introduction' => $introduction]); diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 06e546d614..0acb6d4d72 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -22,6 +22,7 @@ namespace Friendica\Model; use Friendica\App\BaseURL; +use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException; use Friendica\Content\Pager; use Friendica\Content\Text\HTML; use Friendica\Core\Hook; @@ -1085,9 +1086,11 @@ class Contact ]; if (!empty($contact['pending'])) { - $intro = DBA::selectFirst('intro', ['id'], ['contact-id' => $contact['id']]); - if (DBA::isResult($intro)) { + try { + $intro = DI::intro()->selectForContact($contact['id']); $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']]); if (DBA::isResult($user) && !in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) { // create notification - $hash = Strings::getRandomHex(); - if (is_array($contact_record)) { - DBA::insert('intro', ['uid' => $importer['uid'], 'contact-id' => $contact_record['id'], - 'blocked' => false, 'knowyou' => false, 'note' => $note, - 'hash' => $hash, 'datetime' => DateTimeFormat::utcNow()]); + $intro = DI::introFactory()->createNew( + $importer['uid'], + $contact_record['id'], + $note + ); + DI::intro()->save($intro); } Group::addMember(User::getDefaultGroup($importer['uid'], $contact_record["network"]), $contact_record['id']); diff --git a/src/Model/Introduction.php b/src/Model/Contact/Introduction.php similarity index 56% rename from src/Model/Introduction.php rename to src/Model/Contact/Introduction.php index aa71891214..b85e90eaed 100644 --- a/src/Model/Introduction.php +++ b/src/Model/Contact/Introduction.php @@ -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\Database\Database; +use Friendica\DI; use Friendica\Network\HTTPException; +use Friendica\Model\Contact; +use Friendica\Model\User; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Diaspora; -use Friendica\Repository; use Friendica\Util\DateTimeFormat; -use Psr\Log\LoggerInterface; -/** - * @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 +class Introduction { - /** @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. * - * @param bool $duplex Is it a follow back? - * @param bool|null $hidden Should this contact be hidden? null = no change - * @return bool + * @param Entity\Introduction $introduction + * @param bool $duplex Is it a follow back? + * @param bool|null $hidden Should this contact be hidden? null = no change + * * @throws HTTPException\InternalServerErrorException * @throws HTTPException\NotFoundException * @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) { throw new HTTPException\NotFoundException('Contact record not found.'); } $newRelation = $contact['rel']; - $writable = $contact['writable']; + $writable = $contact['writable']; if (!empty($contact['protocol'])) { $protocol = $contact['protocol']; @@ -117,53 +96,24 @@ class Introduction extends BaseModel if ($newRelation == Contact::FRIEND) { if ($protocol == Protocol::DIASPORA) { $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) { 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. * - * @return bool + * @param Entity\Introduction $introduction + * * @throws HTTPException\InternalServerErrorException - * @throws HTTPException\NotFoundException * @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 - // 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]); + $contact = Contact::selectFirst([], ['id' => $introduction->cid, 'uid' => $introduction->uid]); if (!empty($contact)) { if (!empty($contact['protocol'])) { $protocol = $contact['protocol']; @@ -175,7 +125,5 @@ class Introduction extends BaseModel ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']); } } - - return $this->intro->delete($this); } } diff --git a/src/Module/Api/Mastodon/FollowRequests.php b/src/Module/Api/Mastodon/FollowRequests.php index cb7d5da220..bc7cc31bbe 100644 --- a/src/Module/Api/Mastodon/FollowRequests.php +++ b/src/Module/Api/Mastodon/FollowRequests.php @@ -23,6 +23,7 @@ namespace Friendica\Module\Api\Mastodon; use Friendica\Core\System; use Friendica\DI; +use Friendica\Model\Contact; use Friendica\Module\BaseApi; use Friendica\Network\HTTPException; @@ -34,7 +35,6 @@ class FollowRequests extends BaseApi /** * @param array $parameters * @throws HTTPException\BadRequestException - * @throws HTTPException\ForbiddenException * @throws HTTPException\InternalServerErrorException * @throws HTTPException\NotFoundException * @throws HTTPException\UnauthorizedException @@ -48,25 +48,28 @@ class FollowRequests extends BaseApi self::checkAllowedScope(self::SCOPE_FOLLOW); $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']) { case 'authorize': - $introduction->confirm(); - + Contact\Introduction::confirm($introduction); $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); + + DI::intro()->delete($introduction); break; case 'ignore': $introduction->ignore(); - $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); + + DI::intro()->save($introduction); break; case 'reject': - $introduction->discard(); - + Contact\Introduction::discard($introduction); $relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid); + + DI::intro()->delete($introduction); break; default: 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. ]); - $introductions = DI::intro()->selectByBoundaries( - ['`uid` = ? AND NOT `ignore`', $uid], - ['order' => ['id' => 'DESC']], - $request['min_id'], - $request['max_id'], - $request['limit'] - ); + $introductions = DI::intro()->selectForUser($uid, $request['min_id'], $request['max_id'], $request['limit']); $return = []; diff --git a/src/Module/Delegation.php b/src/Module/Delegation.php index affe5e7b8f..12f8c5074c 100644 --- a/src/Module/Delegation.php +++ b/src/Module/Delegation.php @@ -133,7 +133,7 @@ class Delegation extends BaseModule $params = ['distinct' => true, 'expression' => 'convid']; $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; } diff --git a/src/Module/FollowConfirm.php b/src/Module/FollowConfirm.php index f4e4c5ebf9..75153512b6 100644 --- a/src/Module/FollowConfirm.php +++ b/src/Module/FollowConfirm.php @@ -3,6 +3,7 @@ namespace Friendica\Module; use Friendica\BaseModule; use Friendica\DI; +use Friendica\Model\Contact; /** * Process follow request confirmations @@ -21,12 +22,11 @@ class FollowConfirm extends BaseModule $duplex = intval($_POST['duplex'] ?? 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/' . intval($cid)); + DI::baseUrl()->redirect('contact/' . $intro->cid); } } diff --git a/src/Module/Notifications/Notification.php b/src/Module/Notifications/Notification.php index 19ee410d28..64ab459f12 100644 --- a/src/Module/Notifications/Notification.php +++ b/src/Module/Notifications/Notification.php @@ -24,6 +24,7 @@ namespace Friendica\Module\Notifications; use Friendica\BaseModule; use Friendica\Core\System; use Friendica\DI; +use Friendica\Model\Contact; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; @@ -50,14 +51,16 @@ class Notification extends BaseModule $request_id = $parameters['id'] ?? false; if ($request_id) { - $intro = DI::intro()->selectFirst(['id' => $request_id, 'uid' => local_user()]); + $intro = DI::intro()->selectOneById($request_id, local_user()); switch ($_POST['submit']) { case DI::l10n()->t('Discard'): - $intro->discard(); + Contact\Introduction::discard($intro); + DI::intro()->delete($intro); break; case DI::l10n()->t('Ignore'): $intro->ignore(); + DI::intro()->save($intro); break; } diff --git a/src/Navigation/Notifications/Depository/Notify.php b/src/Navigation/Notifications/Depository/Notify.php index 06de36c638..a380166c61 100644 --- a/src/Navigation/Notifications/Depository/Notify.php +++ b/src/Navigation/Notifications/Depository/Notify.php @@ -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) { $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_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 $fields = ['nickname']; diff --git a/src/Navigation/Notifications/Entity/Notify.php b/src/Navigation/Notifications/Entity/Notify.php index 88cd8ab36e..1a97835b6e 100644 --- a/src/Navigation/Notifications/Entity/Notify.php +++ b/src/Navigation/Notifications/Entity/Notify.php @@ -60,14 +60,14 @@ class Notify extends BaseEntity protected $name_cache; /** @var string */ protected $msg_cache; - /** @var int */ + /** @var int|null */ protected $uriId; - /** @var int */ + /** @var int|null */ protected $parentUriId; /** @var int */ 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->name = $name; diff --git a/src/Profile/ProfileField/Depository/ProfileField.php b/src/Profile/ProfileField/Depository/ProfileField.php index b792edf8f7..a6e441f3b6 100644 --- a/src/Profile/ProfileField/Depository/ProfileField.php +++ b/src/Profile/ProfileField/Depository/ProfileField.php @@ -86,7 +86,6 @@ class ProfileField extends BaseDepository $Entities = new Collection\ProfileFields(); foreach ($rows as $fields) { - $this->logger->warning('row', ['row' => $fields]); $Entities[] = $this->factory->createFromTableRow($fields); } diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 8c511c2cd5..a9918bbd02 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1352,7 +1352,7 @@ class DFRN } // 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; } @@ -1366,10 +1366,13 @@ class DFRN $suggest['title'] = ''; $suggest['body'] = $note; - $hash = Strings::getRandomHex(); - $fields = ['uid' => $suggest['uid'], 'suggest-cid' => $cid, 'contact-id' => $suggest['cid'], - 'note' => $suggest['body'], 'hash' => $hash, 'datetime' => DateTimeFormat::utcNow(), 'blocked' => false]; - DBA::insert('intro', $fields); + DI::intro()->save(DI::introFactory()->createNew( + $suggest['uid'], + $suggest['cid'], + $suggest['body'], + null, + $cid + )); DI::notify()->createFromArray([ 'type' => Notification\Type::SUGGEST, diff --git a/src/Repository/Introduction.php b/src/Repository/Introduction.php deleted file mode 100644 index de95229bd5..0000000000 --- a/src/Repository/Introduction.php +++ /dev/null @@ -1,79 +0,0 @@ -. - * - */ - -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); - } -} diff --git a/src/Worker/Contact/RemoveContent.php b/src/Worker/Contact/RemoveContent.php index 567cab1a86..f0e6e631d7 100644 --- a/src/Worker/Contact/RemoveContent.php +++ b/src/Worker/Contact/RemoveContent.php @@ -24,6 +24,7 @@ namespace Friendica\Worker\Contact; use Friendica\Core\Logger; use Friendica\Database\DBA; use Friendica\Database\DBStructure; +use Friendica\DI; use Friendica\Model\Photo; use Friendica\Model\Post; @@ -84,7 +85,7 @@ class RemoveContent DBA::delete('user-contact', ['cid' => $id]); DBA::delete('group_member', ['contact-id' => $id]); - DBA::delete('intro', ['contact-id' => $id]); + DI::intro()->delete(DI::introFactory()->createDummy($id)); return $contact; } diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 4ed1e04a9b..0019fdf7bf 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1441); + define('DB_UPDATE_VERSION', 1442); } return [ @@ -778,11 +778,11 @@ return [ "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"], "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" => ""], "hash" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "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" => ""], ], "indexes" => [ diff --git a/tests/src/Contact/Introduction/Factory/IntroductionTest.php b/tests/src/Contact/Introduction/Factory/IntroductionTest.php new file mode 100644 index 0000000000..e5efd03ddc --- /dev/null +++ b/tests/src/Contact/Introduction/Factory/IntroductionTest.php @@ -0,0 +1,113 @@ + [ + '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]); + } +} diff --git a/update.php b/update.php index 25e64d659a..85f407d36c 100644 --- a/update.php +++ b/update.php @@ -1039,7 +1039,7 @@ function update_1440() return Update::SUCCESS; } -function update__1441() +function update_1441() { $languages = DI::l10n()->getAvailableLanguages(); @@ -1053,3 +1053,11 @@ function update__1441() return Update::SUCCESS; } + +function update_1442() +{ + // transform blocked intros into ignored intros + DBA::update('intro', ['ignore' => 1, 'blocked' => 0], ['blocked' => 1]); + + return Update::SUCCESS; +}