Merge pull request #8074 from MrPetovan/task/domain-driven
Domain-driven-design implementation 4th iteration
This commit is contained in:
commit
2ea1c95e3d
239
doc/Developer-Domain-Driven-Design.md
Normal file
239
doc/Developer-Domain-Driven-Design.md
Normal file
|
@ -0,0 +1,239 @@
|
|||
Domain-Driven-Design
|
||||
==============
|
||||
|
||||
* [Home](help)
|
||||
* [Developer Intro](help/Developers-Intro)
|
||||
|
||||
Friendica uses class structures inspired by Domain-Driven-Design programming patterns.
|
||||
This page is meant to explain what it means in practical terms for Friendica development.
|
||||
|
||||
## Inspiration
|
||||
|
||||
- https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
|
||||
- https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
|
||||
- https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
|
||||
- https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
|
||||
- https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
|
||||
|
||||
## Core concepts
|
||||
|
||||
### Models and Collections
|
||||
|
||||
Instead of anonymous arrays of arrays of database field values, we have Models and collections to take full advantage of PHP type hints.
|
||||
|
||||
Before:
|
||||
```php
|
||||
function doSomething(array $intros)
|
||||
{
|
||||
foreach ($intros as $intro) {
|
||||
$introId = $intro['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
|
||||
|
||||
doSomething($intros);
|
||||
```
|
||||
|
||||
After:
|
||||
```php
|
||||
function doSomething(\Friendica\Collection\Introductions $intros)
|
||||
{
|
||||
foreach ($intros as $intro) {
|
||||
/** @var $intro \Friendica\Model\Introduction */
|
||||
$introId = $intro->id;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var $intros \Friendica\Collection\Introductions */
|
||||
$intros = \Friendica\DI::intro()->select(['uid' => local_user()]);
|
||||
|
||||
doSomething($intros);
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Under this concept, we want class objects to carry with them the dependencies they will use.
|
||||
Instead of calling global/static function/methods, objects use their own class members.
|
||||
|
||||
Before:
|
||||
```php
|
||||
class Model
|
||||
{
|
||||
public $id;
|
||||
|
||||
function save()
|
||||
{
|
||||
return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
```php
|
||||
class Model
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Database\Database
|
||||
*/
|
||||
protected $dba;
|
||||
|
||||
public $id;
|
||||
|
||||
function __construct(\Friendica\Database\Database $dba)
|
||||
{
|
||||
$this->dba = $dba;
|
||||
}
|
||||
|
||||
function save()
|
||||
{
|
||||
return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The main advantage is testability.
|
||||
Another one is avoiding dependency circles and avoid implicit initializing.
|
||||
In the first example the method `save()` has to be tested with the `DBA::update()` method, which may or may not have dependencies itself.
|
||||
|
||||
In the second example we can mock `\Friendica\Database\Database`, e.g. overload the class by replacing its methods by placeholders, which allows us to test only `Model::save()` and nothing else implicitly.
|
||||
|
||||
The main drawback is lengthy constructors for dependency-heavy classes.
|
||||
To alleviate this issue we are using [DiCe](https://r.je/dice) to simplify the instantiation of the higher level objects Friendica uses.
|
||||
|
||||
We also added a convenience factory named `\Friendica\DI` that creates some of the most common objects used in modules.
|
||||
|
||||
### Factories
|
||||
|
||||
Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome.
|
||||
To keep it simple, we are using Factories.
|
||||
Factories are classes used to generate other objects, centralizing the dependencies required in their constructor.
|
||||
Factories encapsulate more or less complex creation of objects and create them redundancy free.
|
||||
|
||||
Before:
|
||||
```php
|
||||
$model = new Model(\Friendica\DI::dba());
|
||||
$model->id = 1;
|
||||
$model->key = 'value';
|
||||
|
||||
$model->save();
|
||||
```
|
||||
|
||||
After:
|
||||
```php
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Database\Database
|
||||
*/
|
||||
protected $dba;
|
||||
|
||||
function __construct(\Friendica\Database\Database $dba)
|
||||
{
|
||||
$this->dba;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return new Model($this->dba);
|
||||
}
|
||||
}
|
||||
|
||||
$model = \Friendica\DI::factory()->create();
|
||||
$model->id = 1;
|
||||
$model->key = 'value';
|
||||
|
||||
$model->save();
|
||||
```
|
||||
|
||||
Here, `DI::factory()` returns an instance of `Factory` that can then be used to create a `Model` object without having to care about its dependencies.
|
||||
|
||||
### Repositories
|
||||
|
||||
Last building block of our code architecture, repositories are meant as the interface between models and how they are stored.
|
||||
In Friendica they are stored in a relational database but repositories allow models not to have to care about it.
|
||||
Repositories also act as factories for the Model they are managing.
|
||||
|
||||
Before:
|
||||
```php
|
||||
class Model
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Database\Database
|
||||
*/
|
||||
protected $dba;
|
||||
|
||||
public $id;
|
||||
|
||||
function __construct(\Friendica\Database\Database $dba)
|
||||
{
|
||||
$this->dba = $dba;
|
||||
}
|
||||
|
||||
function save()
|
||||
{
|
||||
return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
|
||||
}
|
||||
}
|
||||
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Database\Database
|
||||
*/
|
||||
protected $dba;
|
||||
|
||||
function __construct(\Friendica\Database\Database $dba)
|
||||
{
|
||||
$this->dba;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return new Model($this->dba);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$model = \Friendica\DI::factory()->create();
|
||||
$model->id = 1;
|
||||
$model->key = 'value';
|
||||
|
||||
$model->save();
|
||||
```
|
||||
|
||||
After:
|
||||
```php
|
||||
class Model {
|
||||
public $id;
|
||||
}
|
||||
|
||||
class Repository extends Factory
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Database\Database
|
||||
*/
|
||||
protected $dba;
|
||||
|
||||
function __construct(\Friendica\Database\Database $dba)
|
||||
{
|
||||
$this->dba;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return new Model($this->dba);
|
||||
}
|
||||
|
||||
public function save(Model $model)
|
||||
{
|
||||
return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
|
||||
}
|
||||
}
|
||||
|
||||
$model = \Friendica\DI::repository()->create();
|
||||
$model->id = 1;
|
||||
$model->key = 'value';
|
||||
|
||||
\Friendica\DI::repository()->save($model);
|
||||
```
|
|
@ -41,6 +41,8 @@ If you have seen Friendica you probably have ideas to improve it, haven't you?
|
|||
|
||||
## Programming
|
||||
|
||||
Friendica uses an implementation of [Domain-Driven-Design](help/Developer-Domain-Driven-Design), please make sure to check out the provided links for hints at the expected code architecture.
|
||||
|
||||
### Composer
|
||||
|
||||
Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
|
||||
|
|
|
@ -45,6 +45,7 @@ Friendica Documentation and Resources
|
|||
* [Help on Vagrant](help/Vagrant)
|
||||
* [Bugs and Issues](help/Bugs-and-Issues)
|
||||
* Code structure
|
||||
* [Domain-Driven-Design](help/Developer-Domain-Driven-Design)
|
||||
* [Addon Development](help/Addons)
|
||||
* [Theme Development](help/themes)
|
||||
* [Smarty 3 Templates](help/smarty3-templates)
|
||||
|
|
|
@ -31,14 +31,14 @@ function notifications_post(App $a)
|
|||
}
|
||||
|
||||
if ($request_id) {
|
||||
$Intro = DI::intro()->fetch(['id' => $request_id, 'uid' => local_user()]);
|
||||
$intro = DI::intro()->selectFirst(['id' => $request_id, 'uid' => local_user()]);
|
||||
|
||||
switch ($_POST['submit']) {
|
||||
case L10n::t('Discard'):
|
||||
$Intro->discard();
|
||||
$intro->discard();
|
||||
break;
|
||||
case L10n::t('Ignore'):
|
||||
$Intro->ignore();
|
||||
$intro->ignore();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
18
src/Api/BaseEntity.php
Normal file
18
src/Api/BaseEntity.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api;
|
||||
|
||||
/**
|
||||
* The API entity classes are meant as data transfer objects. As such, their member should be protected.
|
||||
* Then the JsonSerializable interface ensures the protected members will be included in a JSON encode situation.
|
||||
*
|
||||
* Constructors are supposed to take as arguments the Friendica dependencies/model/collection/data it needs to
|
||||
* populate the class members.
|
||||
*/
|
||||
abstract class BaseEntity implements \JsonSerializable
|
||||
{
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return get_object_vars($this);
|
||||
}
|
||||
}
|
108
src/Api/Entity/Mastodon/Account.php
Normal file
108
src/Api/Entity/Mastodon/Account.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\Api\BaseEntity;
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/entities/account
|
||||
*/
|
||||
class Account extends BaseEntity
|
||||
{
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $username;
|
||||
/** @var string */
|
||||
protected $acct;
|
||||
/** @var string */
|
||||
protected $display_name;
|
||||
/** @var bool */
|
||||
protected $locked;
|
||||
/** @var string (Datetime) */
|
||||
protected $created_at;
|
||||
/** @var int */
|
||||
protected $followers_count;
|
||||
/** @var int */
|
||||
protected $following_count;
|
||||
/** @var int */
|
||||
protected $statuses_count;
|
||||
/** @var string */
|
||||
protected $note;
|
||||
/** @var string (URL)*/
|
||||
protected $url;
|
||||
/** @var string (URL) */
|
||||
protected $avatar;
|
||||
/** @var string (URL) */
|
||||
protected $avatar_static;
|
||||
/** @var string (URL) */
|
||||
protected $header;
|
||||
/** @var string (URL) */
|
||||
protected $header_static;
|
||||
/** @var Emoji[] */
|
||||
protected $emojis;
|
||||
/** @var Account|null */
|
||||
protected $moved = null;
|
||||
/** @var Field[]|null */
|
||||
protected $fields = null;
|
||||
/** @var bool|null */
|
||||
protected $bot = null;
|
||||
/** @var bool */
|
||||
protected $group;
|
||||
/** @var bool */
|
||||
protected $discoverable;
|
||||
/** @var string|null (Datetime) */
|
||||
protected $last_status_at = null;
|
||||
|
||||
/**
|
||||
* Creates an account record from a public contact record. Expects all contact table fields to be set.
|
||||
*
|
||||
* @param BaseURL $baseUrl
|
||||
* @param array $publicContact Full contact table record with uid = 0
|
||||
* @param array $apcontact Optional full apcontact table record
|
||||
* @param array $userContact Optional full contact table record with uid != 0
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(BaseURL $baseUrl, array $publicContact, array $apcontact = [], array $userContact = [])
|
||||
{
|
||||
$this->id = $publicContact['id'];
|
||||
$this->username = $publicContact['nick'];
|
||||
$this->acct =
|
||||
strpos($publicContact['url'], $baseUrl->get() . '/') === 0 ?
|
||||
$publicContact['nick'] :
|
||||
$publicContact['addr'];
|
||||
$this->display_name = $publicContact['name'];
|
||||
$this->locked = !empty($apcontact['manually-approve']);
|
||||
$this->created_at = DateTimeFormat::utc($publicContact['created'], DateTimeFormat::ATOM);
|
||||
$this->followers_count = $apcontact['followers_count'] ?? 0;
|
||||
$this->following_count = $apcontact['following_count'] ?? 0;
|
||||
$this->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||
$this->note = BBCode::convert($publicContact['about'], false);
|
||||
$this->url = $publicContact['url'];
|
||||
$this->avatar = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
$this->avatar_static = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
// No header picture in Friendica
|
||||
$this->header = '';
|
||||
$this->header_static = '';
|
||||
// No custom emojis per account in Friendica
|
||||
$this->emojis = [];
|
||||
// No metadata fields in Friendica
|
||||
$this->fields = [];
|
||||
$this->bot = ($publicContact['contact-type'] == Contact::TYPE_NEWS);
|
||||
$this->group = ($publicContact['contact-type'] == Contact::TYPE_COMMUNITY);
|
||||
$this->discoverable = !$publicContact['unsearchable'];
|
||||
|
||||
$publicContactLastItem = $publicContact['last-item'] ?: DBA::NULL_DATETIME;
|
||||
$userContactLastItem = $userContact['last-item'] ?? DBA::NULL_DATETIME;
|
||||
|
||||
$lastItem = $userContactLastItem > $publicContactLastItem ? $userContactLastItem : $publicContactLastItem;
|
||||
$this->last_status_at = $lastItem != DBA::NULL_DATETIME ? DateTimeFormat::utc($lastItem, DateTimeFormat::ATOM) : null;
|
||||
}
|
||||
}
|
22
src/Api/Entity/Mastodon/Emoji.php
Normal file
22
src/Api/Entity/Mastodon/Emoji.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\Api\BaseEntity;
|
||||
|
||||
/**
|
||||
* Class Emoji
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#emoji
|
||||
*/
|
||||
class Emoji extends BaseEntity
|
||||
{
|
||||
/** @var string */
|
||||
protected $shortcode;
|
||||
/** @var string (URL)*/
|
||||
protected $static_url;
|
||||
/** @var string (URL)*/
|
||||
protected $url;
|
||||
/** @var bool */
|
||||
protected $visible_in_picker;
|
||||
}
|
|
@ -1,18 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\Api\BaseEntity;
|
||||
|
||||
/**
|
||||
* Class Field
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#field
|
||||
*/
|
||||
class Field
|
||||
class Field extends BaseEntity
|
||||
{
|
||||
/** @var string */
|
||||
var $name;
|
||||
protected $name;
|
||||
/** @var string (HTML) */
|
||||
var $value;
|
||||
protected $value;
|
||||
/** @var string (Datetime)*/
|
||||
var $verified_at;
|
||||
protected $verified_at;
|
||||
}
|
32
src/Api/Entity/Mastodon/FollowRequest.php
Normal file
32
src/Api/Entity/Mastodon/FollowRequest.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Model\Introduction;
|
||||
|
||||
/**
|
||||
* Virtual entity to separate Accounts from Follow Requests.
|
||||
* In the Mastodon API they are one and the same.
|
||||
*/
|
||||
class FollowRequest extends Account
|
||||
{
|
||||
/**
|
||||
* Creates a follow request entity from an introduction record.
|
||||
*
|
||||
* The account ID is set to the Introduction ID to allow for later interaction with follow requests.
|
||||
*
|
||||
* @param BaseURL $baseUrl
|
||||
* @param int $introduction_id Introduction record id
|
||||
* @param array $publicContact Full contact table record with uid = 0
|
||||
* @param array $apcontact Optional full apcontact table record
|
||||
* @param array $userContact Optional full contact table record with uid != 0
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(BaseURL $baseUrl, int $introduction_id, array $publicContact, array $apcontact = [], array $userContact = [])
|
||||
{
|
||||
parent::__construct($baseUrl, $publicContact, $apcontact, $userContact);
|
||||
|
||||
$this->id = $introduction_id;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Api\BaseEntity;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Register;
|
||||
|
||||
|
@ -15,42 +14,41 @@ use Friendica\Module\Register;
|
|||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#instance
|
||||
*/
|
||||
class Instance
|
||||
class Instance extends BaseEntity
|
||||
{
|
||||
/** @var string (URL) */
|
||||
var $uri;
|
||||
protected $uri;
|
||||
/** @var string */
|
||||
var $title;
|
||||
protected $title;
|
||||
/** @var string */
|
||||
var $description;
|
||||
protected $description;
|
||||
/** @var string */
|
||||
var $email;
|
||||
protected $email;
|
||||
/** @var string */
|
||||
var $version;
|
||||
protected $version;
|
||||
/** @var array */
|
||||
var $urls;
|
||||
protected $urls;
|
||||
/** @var Stats */
|
||||
var $stats;
|
||||
/** @var string */
|
||||
var $thumbnail;
|
||||
protected $stats;
|
||||
/** @var string|null */
|
||||
protected $thumbnail = null;
|
||||
/** @var array */
|
||||
var $languages;
|
||||
protected $languages;
|
||||
/** @var int */
|
||||
var $max_toot_chars;
|
||||
protected $max_toot_chars;
|
||||
/** @var bool */
|
||||
var $registrations;
|
||||
protected $registrations;
|
||||
/** @var bool */
|
||||
var $approval_required;
|
||||
protected $approval_required;
|
||||
/** @var Account|null */
|
||||
var $contact_account;
|
||||
protected $contact_account = null;
|
||||
|
||||
/**
|
||||
* Creates an instance record
|
||||
*
|
||||
* @param App $app
|
||||
*
|
||||
* @return Instance
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function get()
|
||||
{
|
||||
|
@ -77,9 +75,8 @@ class Instance
|
|||
$adminList = explode(',', str_replace(' ', '', Config::get('config', 'admin_email')));
|
||||
$administrator = User::getByEmail($adminList[0], ['nickname']);
|
||||
if (!empty($administrator)) {
|
||||
$adminContact = DBA::selectFirst('contact', [], ['nick' => $administrator['nickname'], 'self' => true]);
|
||||
$apcontact = APContact::getByURL($adminContact['url'], false);
|
||||
$instance->contact_account = Account::create($baseUrl, $adminContact, $apcontact);
|
||||
$adminContact = DBA::selectFirst('contact', ['id'], ['nick' => $administrator['nickname'], 'self' => true]);
|
||||
$instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id']);
|
||||
}
|
||||
}
|
||||
|
60
src/Api/Entity/Mastodon/Relationship.php
Normal file
60
src/Api/Entity/Mastodon/Relationship.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\Api\BaseEntity;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Class Relationship
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#relationship
|
||||
*/
|
||||
class Relationship extends BaseEntity
|
||||
{
|
||||
/** @var int */
|
||||
protected $id;
|
||||
/** @var bool */
|
||||
protected $following = false;
|
||||
/** @var bool */
|
||||
protected $followed_by = false;
|
||||
/** @var bool */
|
||||
protected $blocking = false;
|
||||
/** @var bool */
|
||||
protected $muting = false;
|
||||
/** @var bool */
|
||||
protected $muting_notifications = false;
|
||||
/** @var bool */
|
||||
protected $requested = false;
|
||||
/** @var bool */
|
||||
protected $domain_blocking = false;
|
||||
/**
|
||||
* Unsupported
|
||||
* @var bool
|
||||
*/
|
||||
protected $showing_reblogs = true;
|
||||
/**
|
||||
* Unsupported
|
||||
* @var bool
|
||||
*/
|
||||
protected $endorsed = false;
|
||||
|
||||
/**
|
||||
* @param int $userContactId Contact row Id with uid != 0
|
||||
* @param array $userContact Full Contact table record with uid != 0
|
||||
*/
|
||||
public function __construct(int $userContactId, array $userContact = [])
|
||||
{
|
||||
$this->id = $userContactId;
|
||||
$this->following = in_array($userContact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
|
||||
$this->followed_by = in_array($userContact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$this->blocking = (bool)$userContact['blocked'] ?? false;
|
||||
$this->muting = (bool)$userContact['readonly'] ?? false;
|
||||
$this->muting_notifications = (bool)$userContact['readonly'] ?? false;
|
||||
$this->requested = (bool)$userContact['pending'] ?? false;
|
||||
$this->domain_blocking = Network::isUrlBlocked($userContact['url'] ?? '');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
namespace Friendica\Api\Entity\Mastodon;
|
||||
|
||||
use Friendica\Api\BaseEntity;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
|
@ -11,14 +12,14 @@ use Friendica\Database\DBA;
|
|||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#stats
|
||||
*/
|
||||
class Stats
|
||||
class Stats extends BaseEntity
|
||||
{
|
||||
/** @var int */
|
||||
var $user_count;
|
||||
protected $user_count = 0;
|
||||
/** @var int */
|
||||
var $status_count;
|
||||
protected $status_count = 0;
|
||||
/** @var int */
|
||||
var $domain_count;
|
||||
protected $domain_count = 0;
|
||||
|
||||
/**
|
||||
* Creates a stats record
|
|
@ -1,111 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/entities/account
|
||||
*/
|
||||
class Account
|
||||
{
|
||||
/** @var string */
|
||||
var $id;
|
||||
/** @var string */
|
||||
var $username;
|
||||
/** @var string */
|
||||
var $acct;
|
||||
/** @var string */
|
||||
var $display_name;
|
||||
/** @var bool */
|
||||
var $locked;
|
||||
/** @var string (Datetime) */
|
||||
var $created_at;
|
||||
/** @var int */
|
||||
var $followers_count;
|
||||
/** @var int */
|
||||
var $following_count;
|
||||
/** @var int */
|
||||
var $statuses_count;
|
||||
/** @var string */
|
||||
var $note;
|
||||
/** @var string (URL)*/
|
||||
var $url;
|
||||
/** @var string (URL) */
|
||||
var $avatar;
|
||||
/** @var string (URL) */
|
||||
var $avatar_static;
|
||||
/** @var string (URL) */
|
||||
var $header;
|
||||
/** @var string (URL) */
|
||||
var $header_static;
|
||||
/** @var Emoji[] */
|
||||
var $emojis;
|
||||
/** @var Account|null */
|
||||
var $moved = null;
|
||||
/** @var Field[]|null */
|
||||
var $fields = null;
|
||||
/** @var bool|null */
|
||||
var $bot = null;
|
||||
/** @var bool */
|
||||
var $group;
|
||||
/** @var bool */
|
||||
var $discoverable;
|
||||
/** @var string|null (Datetime) */
|
||||
var $last_status_at = null;
|
||||
|
||||
/**
|
||||
* Creates an account record from a public contact record. Expects all contact table fields to be set.
|
||||
*
|
||||
* @param BaseURL $baseUrl
|
||||
* @param array $publicContact Full contact table record with uid = 0
|
||||
* @param array $apcontact Optional full apcontact table record
|
||||
* @param array $userContact Optional full contact table record with uid = local_user()
|
||||
* @return Account
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function create(BaseURL $baseUrl, array $publicContact, array $apcontact = [], array $userContact = [])
|
||||
{
|
||||
$account = new Account();
|
||||
$account->id = $publicContact['id'];
|
||||
$account->username = $publicContact['nick'];
|
||||
$account->acct =
|
||||
strpos($publicContact['url'], $baseUrl->get() . '/') === 0 ?
|
||||
$publicContact['nick'] :
|
||||
$publicContact['addr'];
|
||||
$account->display_name = $publicContact['name'];
|
||||
$account->locked = !empty($apcontact['manually-approve']);
|
||||
$account->created_at = DateTimeFormat::utc($publicContact['created'], DateTimeFormat::ATOM);
|
||||
$account->followers_count = $apcontact['followers_count'] ?? 0;
|
||||
$account->following_count = $apcontact['following_count'] ?? 0;
|
||||
$account->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||
$account->note = BBCode::convert($publicContact['about'], false);
|
||||
$account->url = $publicContact['url'];
|
||||
$account->avatar = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
$account->avatar_static = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
// No header picture in Friendica
|
||||
$account->header = '';
|
||||
$account->header_static = '';
|
||||
// No custom emojis per account in Friendica
|
||||
$account->emojis = [];
|
||||
// No metadata fields in Friendica
|
||||
$account->fields = [];
|
||||
$account->bot = ($publicContact['contact-type'] == Contact::TYPE_NEWS);
|
||||
$account->group = ($publicContact['contact-type'] == Contact::TYPE_COMMUNITY);
|
||||
$account->discoverable = !$publicContact['unsearchable'];
|
||||
|
||||
$publicContactLastItem = $publicContact['last-item'] ?: DBA::NULL_DATETIME;
|
||||
$userContactLastItem = $userContact['last-item'] ?? DBA::NULL_DATETIME;
|
||||
|
||||
$lastItem = $userContactLastItem > $publicContactLastItem ? $userContactLastItem : $publicContactLastItem;
|
||||
$account->last_status_at = $lastItem != DBA::NULL_DATETIME ? DateTimeFormat::utc($lastItem, DateTimeFormat::ATOM) : null;
|
||||
|
||||
return $account;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
/**
|
||||
* Class Emoji
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#emoji
|
||||
*/
|
||||
class Emoji
|
||||
{
|
||||
/** @var string */
|
||||
var $shortcode;
|
||||
/** @var string (URL)*/
|
||||
var $static_url;
|
||||
/** @var string (URL)*/
|
||||
var $url;
|
||||
/** @var bool */
|
||||
var $visible_in_picker;
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Class Relationship
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#relationship
|
||||
*/
|
||||
class Relationship
|
||||
{
|
||||
/** @var int */
|
||||
var $id;
|
||||
/** @var bool */
|
||||
var $following = false;
|
||||
/** @var bool */
|
||||
var $followed_by = false;
|
||||
/** @var bool */
|
||||
var $blocking = false;
|
||||
/** @var bool */
|
||||
var $muting = false;
|
||||
/** @var bool */
|
||||
var $muting_notifications = false;
|
||||
/** @var bool */
|
||||
var $requested = false;
|
||||
/** @var bool */
|
||||
var $domain_blocking = false;
|
||||
/** @var bool */
|
||||
var $showing_reblogs = false;
|
||||
/** @var bool */
|
||||
var $endorsed = false;
|
||||
|
||||
/**
|
||||
* @param array $contact Full Contact table record
|
||||
* @return Relationship
|
||||
*/
|
||||
public static function createFromContact(array $contact)
|
||||
{
|
||||
$relationship = new self();
|
||||
|
||||
$relationship->id = $contact['id'];
|
||||
$relationship->following = in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]);
|
||||
$relationship->followed_by = in_array($contact['rel'], [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$relationship->blocking = (bool)$contact['blocked'];
|
||||
$relationship->muting = (bool)$contact['readonly'];
|
||||
$relationship->muting_notifications = (bool)$contact['readonly'];
|
||||
$relationship->requested = (bool)$contact['pending'];
|
||||
$relationship->domain_blocking = Network::isUrlBlocked($contact['url']);
|
||||
// Unsupported
|
||||
$relationship->showing_reblogs = true;
|
||||
// Unsupported
|
||||
$relationship->endorsed = false;
|
||||
|
||||
return $relationship;
|
||||
}
|
||||
}
|
38
src/BaseCollection.php
Normal file
38
src/BaseCollection.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica;
|
||||
|
||||
/**
|
||||
* The Collection classes inheriting from this abstract class are meant to represent a list of database record.
|
||||
* The associated model class has to be provided in the child classes.
|
||||
*
|
||||
* Collections can be used with foreach(), accessed like an array and counted.
|
||||
*/
|
||||
abstract class BaseCollection extends \ArrayIterator
|
||||
{
|
||||
/**
|
||||
* This property is used with paginated results to hold the total number of items satisfying the paginated request.
|
||||
* @var int
|
||||
*/
|
||||
protected $totalCount = 0;
|
||||
|
||||
/**
|
||||
* @param BaseModel[] $models
|
||||
* @param int|null $totalCount
|
||||
*/
|
||||
public function __construct(array $models = [], int $totalCount = null)
|
||||
{
|
||||
parent::__construct($models);
|
||||
|
||||
$this->models = $models;
|
||||
$this->totalCount = $totalCount ?? count($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTotalCount()
|
||||
{
|
||||
return $this->totalCount;
|
||||
}
|
||||
}
|
22
src/BaseFactory.php
Normal file
22
src/BaseFactory.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Factories act as an intermediary to avoid direct Entitiy instanciation.
|
||||
*
|
||||
* @see BaseModel
|
||||
* @see BaseCollection
|
||||
*/
|
||||
abstract class BaseFactory
|
||||
{
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@ use Friendica\Network\HTTPException;
|
|||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class BaseModel
|
||||
*
|
||||
* The Model classes inheriting from this abstract class are meant to represent a single database record.
|
||||
* The associated table name has to be provided in the child class, and the table is expected to have a unique `id` field.
|
||||
*
|
||||
|
@ -16,8 +14,6 @@ use Psr\Log\LoggerInterface;
|
|||
*/
|
||||
abstract class BaseModel
|
||||
{
|
||||
protected static $table_name;
|
||||
|
||||
/** @var Database */
|
||||
protected $dba;
|
||||
/** @var LoggerInterface */
|
||||
|
@ -32,13 +28,33 @@ abstract class BaseModel
|
|||
*/
|
||||
private $data = [];
|
||||
|
||||
public function __construct(Database $dba, LoggerInterface $logger, $data = [])
|
||||
/**
|
||||
* @param Database $dba
|
||||
* @param LoggerInterface $logger
|
||||
* @param array $data Table row attributes
|
||||
*/
|
||||
public function __construct(Database $dba, LoggerInterface $logger, array $data = [])
|
||||
{
|
||||
$this->dba = $dba;
|
||||
$this->logger = $logger;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance-improved model creation in a loop
|
||||
*
|
||||
* @param BaseModel $prototype
|
||||
* @param array $data
|
||||
* @return BaseModel
|
||||
*/
|
||||
public static function createFromPrototype(BaseModel $prototype, array $data)
|
||||
{
|
||||
$model = clone $prototype;
|
||||
$model->data = $data;
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter. This allows to retrieve model fields with the following syntax:
|
||||
* - $model->field (outside of class)
|
||||
|
@ -62,33 +78,16 @@ abstract class BaseModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
|
||||
*
|
||||
* Chainable.
|
||||
*
|
||||
* @param array $condition
|
||||
* @return BaseModel
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function fetch(array $condition)
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$data = $this->dba->selectFirst(static::$table_name, [], $condition);
|
||||
|
||||
if (!$data) {
|
||||
throw new HTTPException\NotFoundException(static::class . ' record not found.');
|
||||
}
|
||||
|
||||
return new static($this->dba, $this->logger, $data);
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the model record from the database.
|
||||
* Prevents further methods from being called by wiping the internal model data.
|
||||
*/
|
||||
public function delete()
|
||||
public function toArray()
|
||||
{
|
||||
if ($this->dba->delete(static::$table_name, ['id' => $this->id])) {
|
||||
$this->data = [];
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
|
|
200
src/BaseRepository.php
Normal file
200
src/BaseRepository.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica;
|
||||
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Repositories are Factories linked to one or more database tables.
|
||||
*
|
||||
* @see BaseModel
|
||||
* @see BaseCollection
|
||||
*/
|
||||
abstract class BaseRepository extends BaseFactory
|
||||
{
|
||||
const LIMIT = 30;
|
||||
|
||||
/** @var Database */
|
||||
protected $dba;
|
||||
|
||||
/** @var string */
|
||||
protected static $table_name;
|
||||
|
||||
/** @var BaseModel */
|
||||
protected static $model_class;
|
||||
|
||||
/** @var BaseCollection */
|
||||
protected static $collection_class;
|
||||
|
||||
public function __construct(Database $dba, LoggerInterface $logger)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->dba = $dba;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
|
||||
*
|
||||
* Chainable.
|
||||
*
|
||||
* @param array $condition
|
||||
* @return BaseModel
|
||||
* @throws HTTPException\NotFoundException
|
||||
*/
|
||||
public function selectFirst(array $condition)
|
||||
{
|
||||
$data = $this->dba->selectFirst(static::$table_name, [], $condition);
|
||||
|
||||
if (!$data) {
|
||||
throw new HTTPException\NotFoundException(static::class . ' record not found.');
|
||||
}
|
||||
|
||||
return $this->create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a Collection according to the condition.
|
||||
*
|
||||
* Chainable.
|
||||
*
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
* @return BaseCollection
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function select(array $condition = [], array $params = [])
|
||||
{
|
||||
$models = $this->selectModels($condition, $params);
|
||||
|
||||
return new static::$collection_class($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the collection according to the condition. Retrieves a limited subset of models depending on the boundaries
|
||||
* and the limit. The total count of rows matching the condition is stored in the collection.
|
||||
*
|
||||
* Chainable.
|
||||
*
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
* @param int? $max_id
|
||||
* @param int? $since_id
|
||||
* @param int $limit
|
||||
* @return BaseCollection
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT)
|
||||
{
|
||||
$condition = DBA::collapseCondition($condition);
|
||||
|
||||
$boundCondition = $condition;
|
||||
|
||||
if (isset($max_id)) {
|
||||
$boundCondition[0] .= " AND `id` < ?";
|
||||
$boundCondition[] = $max_id;
|
||||
}
|
||||
|
||||
if (isset($since_id)) {
|
||||
$boundCondition[0] .= " AND `id` > ?";
|
||||
$boundCondition[] = $since_id;
|
||||
}
|
||||
|
||||
$params['limit'] = $limit;
|
||||
|
||||
$models = $this->selectModels($boundCondition, $params);
|
||||
|
||||
$totalCount = DBA::count(static::$table_name, $condition);
|
||||
|
||||
return new static::$collection_class($models, $totalCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method updates the database row from the model.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(BaseModel $model)
|
||||
{
|
||||
return $this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a new database row and returns a model if it was successful.
|
||||
*
|
||||
* @param array $fields
|
||||
* @return BaseModel|bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function insert(array $fields)
|
||||
{
|
||||
$return = $this->dba->insert(static::$table_name, $fields);
|
||||
|
||||
if ($return) {
|
||||
$fields['id'] = $this->dba->lastInsertId();
|
||||
$return = $this->create($fields);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the model record from the database.
|
||||
*
|
||||
* @param BaseModel $model
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(BaseModel &$model)
|
||||
{
|
||||
if ($success = $this->dba->delete(static::$table_name, ['id' => $model->id])) {
|
||||
$model = null;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base instantiation method, can be overriden to add specific dependencies
|
||||
*
|
||||
* @param array $data
|
||||
* @return BaseModel
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
return new static::$model_class($this->dba, $this->logger, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $condition Query condition
|
||||
* @param array $params Additional query parameters
|
||||
* @return BaseModel[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function selectModels(array $condition, array $params = [])
|
||||
{
|
||||
$result = $this->dba->select(static::$table_name, [], $condition, $params);
|
||||
|
||||
/** @var BaseModel $prototype */
|
||||
$prototype = null;
|
||||
|
||||
$models = [];
|
||||
|
||||
while ($record = $this->dba->fetch($result)) {
|
||||
if ($prototype === null) {
|
||||
$prototype = $this->create($record);
|
||||
$models[] = $prototype;
|
||||
} else {
|
||||
$models[] = static::$model_class::createFromPrototype($prototype, $record);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
}
|
14
src/Collection/Introductions.php
Normal file
14
src/Collection/Introductions.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Collection;
|
||||
|
||||
use Friendica\BaseCollection;
|
||||
use Friendica\Model\Introduction;
|
||||
|
||||
/**
|
||||
* @property Introduction[] $models
|
||||
*/
|
||||
class Introductions extends BaseCollection
|
||||
{
|
||||
|
||||
}
|
10
src/DI.php
10
src/DI.php
|
@ -28,9 +28,12 @@ use Psr\Log\LoggerInterface;
|
|||
* @method static Core\Process process()
|
||||
* @method static Core\Session\ISession session()
|
||||
* @method static Database\Database dba()
|
||||
* @method static Factory\Mastodon\Account mstdnAccount()
|
||||
* @method static Factory\Mastodon\FollowRequest mstdnFollowRequest()
|
||||
* @method static Factory\Mastodon\Relationship mstdnRelationship()
|
||||
* @method static Model\User\Cookie cookie()
|
||||
* @method static Model\Notify notify()
|
||||
* @method static Model\Introduction intro()
|
||||
* @method static Repository\Introduction intro()
|
||||
* @method static Protocol\Activity activity()
|
||||
* @method static Util\ACLFormatter aclFormatter()
|
||||
* @method static Util\DateTimeFormat dtFormat()
|
||||
|
@ -62,9 +65,12 @@ abstract class DI
|
|||
'process' => Core\Process::class,
|
||||
'session' => Core\Session\ISession::class,
|
||||
'dba' => Database\Database::class,
|
||||
'mstdnAccount' => Factory\Mastodon\Account::class,
|
||||
'mstdnFollowRequest' => Factory\Mastodon\FollowRequest::class,
|
||||
'mstdnRelationship' => Factory\Mastodon\Relationship::class,
|
||||
'cookie' => Model\User\Cookie::class,
|
||||
'notify' => Model\Notify::class,
|
||||
'intro' => Model\Introduction::class,
|
||||
'intro' => Repository\Introduction::class,
|
||||
'activity' => Protocol\Activity::class,
|
||||
'aclFormatter' => Util\ACLFormatter::class,
|
||||
'dtFormat' => Util\DateTimeFormat::class,
|
||||
|
|
|
@ -529,67 +529,96 @@ class DBA
|
|||
*/
|
||||
public static function buildCondition(array &$condition = [])
|
||||
{
|
||||
$condition = self::collapseCondition($condition);
|
||||
|
||||
$condition_string = '';
|
||||
if (count($condition) > 0) {
|
||||
reset($condition);
|
||||
$first_key = key($condition);
|
||||
if (is_int($first_key)) {
|
||||
$condition_string = " WHERE (" . array_shift($condition) . ")";
|
||||
} else {
|
||||
$new_values = [];
|
||||
$condition_string = "";
|
||||
foreach ($condition as $field => $value) {
|
||||
if ($condition_string != "") {
|
||||
$condition_string .= " AND ";
|
||||
}
|
||||
if (is_array($value)) {
|
||||
if (count($value)) {
|
||||
/* Workaround for MySQL Bug #64791.
|
||||
* Never mix data types inside any IN() condition.
|
||||
* In case of mixed types, cast all as string.
|
||||
* Logic needs to be consistent with DBA::p() data types.
|
||||
*/
|
||||
$is_int = false;
|
||||
$is_alpha = false;
|
||||
foreach ($value as $single_value) {
|
||||
if (is_int($single_value)) {
|
||||
$is_int = true;
|
||||
} else {
|
||||
$is_alpha = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_int && $is_alpha) {
|
||||
foreach ($value as &$ref) {
|
||||
if (is_int($ref)) {
|
||||
$ref = (string)$ref;
|
||||
}
|
||||
}
|
||||
unset($ref); //Prevent accidental re-use.
|
||||
}
|
||||
|
||||
$new_values = array_merge($new_values, array_values($value));
|
||||
$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
|
||||
$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
|
||||
} else {
|
||||
// Empty value array isn't supported by IN and is logically equivalent to no match
|
||||
$condition_string .= "FALSE";
|
||||
}
|
||||
} elseif (is_null($value)) {
|
||||
$condition_string .= self::quoteIdentifier($field) . " IS NULL";
|
||||
} else {
|
||||
$new_values[$field] = $value;
|
||||
$condition_string .= self::quoteIdentifier($field) . " = ?";
|
||||
}
|
||||
}
|
||||
$condition_string = " WHERE (" . $condition_string . ")";
|
||||
$condition = $new_values;
|
||||
}
|
||||
$condition_string = " WHERE (" . array_shift($condition) . ")";
|
||||
}
|
||||
|
||||
return $condition_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse an associative array condition into a SQL string + parameters condition array.
|
||||
*
|
||||
* ['uid' => 1, 'network' => ['dspr', 'apub']]
|
||||
*
|
||||
* gets transformed into
|
||||
*
|
||||
* ["`uid` = ? AND `network` IN (?, ?)", 1, 'dspr', 'apub']
|
||||
*
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public static function collapseCondition(array $condition)
|
||||
{
|
||||
// Ensures an always true condition is returned
|
||||
if (count($condition) < 1) {
|
||||
return ['1'];
|
||||
}
|
||||
|
||||
reset($condition);
|
||||
$first_key = key($condition);
|
||||
|
||||
if (is_int($first_key)) {
|
||||
// Already collapsed
|
||||
return $condition;
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$condition_string = "";
|
||||
foreach ($condition as $field => $value) {
|
||||
if ($condition_string != "") {
|
||||
$condition_string .= " AND ";
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
if (count($value)) {
|
||||
/* Workaround for MySQL Bug #64791.
|
||||
* Never mix data types inside any IN() condition.
|
||||
* In case of mixed types, cast all as string.
|
||||
* Logic needs to be consistent with DBA::p() data types.
|
||||
*/
|
||||
$is_int = false;
|
||||
$is_alpha = false;
|
||||
foreach ($value as $single_value) {
|
||||
if (is_int($single_value)) {
|
||||
$is_int = true;
|
||||
} else {
|
||||
$is_alpha = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_int && $is_alpha) {
|
||||
foreach ($value as &$ref) {
|
||||
if (is_int($ref)) {
|
||||
$ref = (string)$ref;
|
||||
}
|
||||
}
|
||||
unset($ref); //Prevent accidental re-use.
|
||||
}
|
||||
|
||||
$values = array_merge($values, array_values($value));
|
||||
$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
|
||||
$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
|
||||
} else {
|
||||
// Empty value array isn't supported by IN and is logically equivalent to no match
|
||||
$condition_string .= "FALSE";
|
||||
}
|
||||
} elseif (is_null($value)) {
|
||||
$condition_string .= self::quoteIdentifier($field) . " IS NULL";
|
||||
} else {
|
||||
$values[$field] = $value;
|
||||
$condition_string .= self::quoteIdentifier($field) . " = ?";
|
||||
}
|
||||
}
|
||||
|
||||
$condition = array_merge([$condition_string], array_values($values));
|
||||
|
||||
return $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the SQL parameter string built from the provided parameter array
|
||||
*
|
||||
|
|
|
@ -1327,10 +1327,6 @@ class Database
|
|||
return false;
|
||||
}
|
||||
|
||||
$table_string = DBA::buildTableString($table);
|
||||
|
||||
$condition_string = DBA::buildCondition($condition);
|
||||
|
||||
if (is_bool($old_fields)) {
|
||||
$do_insert = $old_fields;
|
||||
|
||||
|
@ -1361,13 +1357,16 @@ class Database
|
|||
return true;
|
||||
}
|
||||
|
||||
$table_string = DBA::buildTableString($table);
|
||||
|
||||
$condition_string = DBA::buildCondition($condition);
|
||||
|
||||
$sql = "UPDATE " . $table_string . " SET "
|
||||
. implode(" = ?, ", array_map([DBA::class, 'quoteIdentifier'], array_keys($fields))) . " = ?"
|
||||
. $condition_string;
|
||||
|
||||
$params1 = array_values($fields);
|
||||
$params2 = array_values($condition);
|
||||
$params = array_merge_recursive($params1, $params2);
|
||||
// Combines the updated fields parameter values with the condition parameter values
|
||||
$params = array_merge(array_values($fields), $condition);
|
||||
|
||||
return $this->e($sql, $params);
|
||||
}
|
||||
|
|
46
src/Factory/Mastodon/Account.php
Normal file
46
src/Factory/Mastodon/Account.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Factory\Mastodon;
|
||||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\BaseFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Account extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $contactId
|
||||
* @param int $uid User Id
|
||||
* @return \Friendica\Api\Entity\Mastodon\Account
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromContactId(int $contactId, $uid = 0)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||
if (!empty($cdata)) {
|
||||
$publicContact = Contact::getById($cdata['public']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
} else {
|
||||
$publicContact = Contact::getById($contactId);
|
||||
$userContact = [];
|
||||
}
|
||||
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
|
||||
return new \Friendica\Api\Entity\Mastodon\Account($this->baseUrl, $publicContact, $apcontact, $userContact);
|
||||
}
|
||||
}
|
47
src/Factory/Mastodon/FollowRequest.php
Normal file
47
src/Factory/Mastodon/FollowRequest.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Factory\Mastodon;
|
||||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Introduction;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\BaseFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FollowRequest extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Introduction $introduction
|
||||
* @return \Friendica\Api\Entity\Mastodon\FollowRequest
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromIntroduction(Introduction $introduction)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($introduction->{'contact-id'}, $introduction->uid);
|
||||
|
||||
if (empty($cdata)) {
|
||||
$this->logger->warning('Wrong introduction data', ['Introduction' => $introduction]);
|
||||
throw new HTTPException\InternalServerErrorException('Wrong introduction data');
|
||||
}
|
||||
|
||||
$publicContact = Contact::getById($cdata['public']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
|
||||
return new \Friendica\Api\Entity\Mastodon\FollowRequest($this->baseUrl, $introduction->id, $publicContact, $apcontact, $userContact);
|
||||
}
|
||||
}
|
38
src/Factory/Mastodon/Relationship.php
Normal file
38
src/Factory/Mastodon/Relationship.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Factory\Mastodon;
|
||||
|
||||
use Friendica\Api\Entity\Mastodon\Relationship as RelationshipEntity;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
class Relationship extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* @param int $userContactId Contact row id with uid != 0
|
||||
* @return RelationshipEntity
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createFromContactId(int $userContactId)
|
||||
{
|
||||
return $this->createFromContact(Contact::getById($userContactId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $userContact Full contact row record with uid != 0
|
||||
* @return RelationshipEntity
|
||||
*/
|
||||
public function createFromContact(array $userContact)
|
||||
{
|
||||
return new RelationshipEntity($userContact['id'], $userContact);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userContactId Contact row id with uid != 0
|
||||
* @return RelationshipEntity
|
||||
*/
|
||||
public function createDefaultFromContactId(int $userContactId)
|
||||
{
|
||||
return new RelationshipEntity($userContactId);
|
||||
}
|
||||
}
|
|
@ -4,10 +4,13 @@ namespace Friendica\Model;
|
|||
|
||||
use Friendica\BaseModel;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Repository;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @property int uid
|
||||
|
@ -20,33 +23,40 @@ use Friendica\Util\DateTimeFormat;
|
|||
* @property string datetime
|
||||
* @property bool blocked
|
||||
* @property bool ignored
|
||||
*
|
||||
* @package Friendica\Model
|
||||
*/
|
||||
final class Introduction extends BaseModel
|
||||
{
|
||||
static $table_name = 'intro';
|
||||
/** @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 notic 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 bool|null $hidden Should this contact be hidden? null = no change
|
||||
* @param bool $duplex Is it a follow back?
|
||||
* @param bool|null $hidden Should this contact be hidden? null = no change
|
||||
* @return bool
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function confirm(bool $duplex = false, bool $hidden = null)
|
||||
{
|
||||
$this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]);
|
||||
|
||||
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
$contact = Model\Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
|
||||
if (!$contact) {
|
||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||
}
|
||||
|
||||
$new_relation = $contact['rel'];
|
||||
$newRelation = $contact['rel'];
|
||||
$writable = $contact['writable'];
|
||||
|
||||
if (!empty($contact['protocol'])) {
|
||||
|
@ -61,12 +71,12 @@ final class Introduction extends BaseModel
|
|||
|
||||
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||
if ($duplex) {
|
||||
$new_relation = Contact::FRIEND;
|
||||
$newRelation = Model\Contact::FRIEND;
|
||||
} else {
|
||||
$new_relation = Contact::FOLLOWER;
|
||||
$newRelation = Model\Contact::FOLLOWER;
|
||||
}
|
||||
|
||||
if ($new_relation != Contact::FOLLOWER) {
|
||||
if ($newRelation != Model\Contact::FOLLOWER) {
|
||||
$writable = 1;
|
||||
}
|
||||
}
|
||||
|
@ -79,43 +89,42 @@ final class Introduction extends BaseModel
|
|||
'protocol' => $protocol,
|
||||
'writable' => $writable,
|
||||
'hidden' => $hidden ?? $contact['hidden'],
|
||||
'rel' => $new_relation,
|
||||
'rel' => $newRelation,
|
||||
];
|
||||
$this->dba->update('contact', $fields, ['id' => $contact['id']]);
|
||||
|
||||
array_merge($contact, $fields);
|
||||
|
||||
if ($new_relation == Contact::FRIEND) {
|
||||
if ($newRelation == Model\Contact::FRIEND) {
|
||||
if ($protocol == Protocol::DIASPORA) {
|
||||
$ret = Diaspora::sendShare(User::getById($contact['uid']), $contact);
|
||||
$ret = Diaspora::sendShare(Model\Contact::getById($contact['uid']), $contact);
|
||||
$this->logger->info('share returns', ['return' => $ret]);
|
||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
return $this->intro->delete($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
|
||||
* additional follow requests.
|
||||
*
|
||||
* Chainable
|
||||
*
|
||||
* @return Introduction
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ignore()
|
||||
{
|
||||
$this->dba->update('intro', ['ignore' => true], ['id' => $this->id]);
|
||||
$this->ignored = true;
|
||||
|
||||
return $this;
|
||||
return $this->intro->update($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the introduction and sends a rejection message to AP contacts.
|
||||
*
|
||||
* @return bool
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws \ImagickException
|
||||
|
@ -127,15 +136,15 @@ final class Introduction extends BaseModel
|
|||
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]];
|
||||
'self' => false, 'pending' => true, 'rel' => [0, Model\Contact::FOLLOWER]];
|
||||
if ($this->dba->exists('contact', $condition)) {
|
||||
Contact::remove($this->{'contact-id'});
|
||||
Model\Contact::remove($this->{'contact-id'});
|
||||
} else {
|
||||
$this->dba->update('contact', ['pending' => false], ['id' => $this->{'contact-id'}]);
|
||||
}
|
||||
}
|
||||
|
||||
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
$contact = Model\Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
|
||||
if (!$contact) {
|
||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||
|
@ -151,6 +160,6 @@ final class Introduction extends BaseModel
|
|||
ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']);
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
return $this->intro->delete($this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Api\Mastodon;
|
||||
use Friendica\Api\Entity\Mastodon;
|
||||
use Friendica\Api\Entity\Mastodon\Relationship;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Introduction;
|
||||
use Friendica\Module\Base\Api;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/api/rest/follow-requests/
|
||||
* @see https://docs.joinmastodon.org/methods/accounts/follow_requests
|
||||
*/
|
||||
class FollowRequests extends Api
|
||||
{
|
||||
|
@ -30,8 +28,11 @@ class FollowRequests extends Api
|
|||
* @param array $parameters
|
||||
* @throws HTTPException\BadRequestException
|
||||
* @throws HTTPException\ForbiddenException
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws HTTPException\UnauthorizedException
|
||||
* @throws \ImagickException
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/methods/accounts/follow_requests#accept-follow
|
||||
* @see https://docs.joinmastodon.org/methods/accounts/follow_requests#reject-follow
|
||||
*/
|
||||
|
@ -39,23 +40,25 @@ class FollowRequests extends Api
|
|||
{
|
||||
parent::post($parameters);
|
||||
|
||||
$Intro = DI::intro()->fetch(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
|
||||
$introduction = DI::intro()->selectFirst(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
|
||||
|
||||
$contactId = $Intro->{'contact-id'};
|
||||
|
||||
$relationship = new Mastodon\Relationship();
|
||||
$relationship->id = $contactId;
|
||||
$contactId = $introduction->{'contact-id'};
|
||||
|
||||
switch ($parameters['action']) {
|
||||
case 'authorize':
|
||||
$Intro->confirm();
|
||||
$relationship = Mastodon\Relationship::createFromContact(Contact::getById($contactId));
|
||||
$introduction->confirm();
|
||||
|
||||
$relationship = DI::mstdnRelationship()->createFromContactId($contactId);
|
||||
break;
|
||||
case 'ignore':
|
||||
$Intro->ignore();
|
||||
$introduction->ignore();
|
||||
|
||||
$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
|
||||
break;
|
||||
case 'reject':
|
||||
$Intro->discard();
|
||||
$introduction->discard();
|
||||
|
||||
$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
|
||||
break;
|
||||
default:
|
||||
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
|
||||
|
@ -78,41 +81,23 @@ class FollowRequests extends Api
|
|||
|
||||
$baseUrl = DI::baseUrl();
|
||||
|
||||
if (isset($since_id) && isset($max_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
||||
} elseif (isset($since_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ?', self::$current_user_id, $since_id];
|
||||
} elseif (isset($max_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` < ?', self::$current_user_id, $max_id];
|
||||
} else {
|
||||
$condition = ['`uid` = ? AND NOT `ignore`', self::$current_user_id];
|
||||
}
|
||||
|
||||
$count = DBA::count('intro', $condition);
|
||||
|
||||
$intros = DBA::selectToArray(
|
||||
'intro',
|
||||
[],
|
||||
$condition,
|
||||
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
||||
$introductions = DI::intro()->selectByBoundaries(
|
||||
['`uid` = ? AND NOT `ignore`', self::$current_user_id],
|
||||
['order' => ['id' => 'DESC']],
|
||||
$since_id,
|
||||
$max_id,
|
||||
$limit
|
||||
);
|
||||
|
||||
$return = [];
|
||||
foreach ($intros as $intro) {
|
||||
$cdata = Contact::getPublicAndUserContacID($intro['contact-id'], $intro['uid']);
|
||||
if (empty($cdata['public'])) {
|
||||
continue;
|
||||
|
||||
foreach ($introductions as $key => $introduction) {
|
||||
try {
|
||||
$return[] = DI::mstdnFollowRequest()->createFromIntroduction($introduction);
|
||||
} catch (HTTPException\InternalServerErrorException $exception) {
|
||||
DI::intro()->delete($introduction);
|
||||
unset($introductions[$key]);
|
||||
}
|
||||
|
||||
$publicContact = Contact::getById($cdata['public']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
$account = Mastodon\Account::create($baseUrl, $publicContact, $apcontact, $userContact);
|
||||
|
||||
// Not ideal, the same "account" can have multiple ids depending on the context
|
||||
$account->id = $intro['id'];
|
||||
|
||||
$return[] = $account;
|
||||
}
|
||||
|
||||
$base_query = [];
|
||||
|
@ -121,10 +106,10 @@ class FollowRequests extends Api
|
|||
}
|
||||
|
||||
$links = [];
|
||||
if ($count > $limit) {
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $intros[count($intros) - 1]['id']]) . '>; rel="next"';
|
||||
if ($introductions->getTotalCount() > $limit) {
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $introductions[count($introductions) - 1]->id]) . '>; rel="next"';
|
||||
}
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $intros[0]['id']]) . '>; rel="prev"';
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $introductions[0]->id]) . '>; rel="prev"';
|
||||
|
||||
header('Link: ' . implode(', ', $links));
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Api\Mastodon\Instance as InstanceEntity;
|
||||
use Friendica\Api\Entity\Mastodon\Instance as InstanceEntity;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Module\Base\Api;
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ class FollowConfirm extends BaseModule
|
|||
$duplex = intval($_POST['duplex'] ?? 0);
|
||||
$hidden = intval($_POST['hidden'] ?? 0);
|
||||
|
||||
$Intro = DI::intro()->fetch(['id' => $intro_id, 'uid' => local_user()]);
|
||||
$intro = DI::intro()->selectFirst(['id' => $intro_id, 'uid' => local_user()]);
|
||||
|
||||
$cid = $Intro->{'contact-id'};
|
||||
$cid = $intro->{'contact-id'};
|
||||
|
||||
$Intro->confirm($duplex, $hidden);
|
||||
$intro->confirm($duplex, $hidden);
|
||||
|
||||
DI::baseUrl()->redirect('contact/' . intval($cid));
|
||||
}
|
||||
|
|
60
src/Repository/Introduction.php
Normal file
60
src/Repository/Introduction.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
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 $max_id
|
||||
* @param int|null $since_id
|
||||
* @param int $limit
|
||||
* @return Collection\Introductions
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT)
|
||||
{
|
||||
return parent::selectByBoundaries($condition, $params, $max_id, $since_id, $limit);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue