Merge pull request #8074 from MrPetovan/task/domain-driven
Domain-driven-design implementation 4th iteration
This commit is contained in:
commit
2ea1c95e3d
31 changed files with 1178 additions and 394 deletions
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
|
## 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
|
### Composer
|
||||||
|
|
||||||
Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
|
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)
|
* [Help on Vagrant](help/Vagrant)
|
||||||
* [Bugs and Issues](help/Bugs-and-Issues)
|
* [Bugs and Issues](help/Bugs-and-Issues)
|
||||||
* Code structure
|
* Code structure
|
||||||
|
* [Domain-Driven-Design](help/Developer-Domain-Driven-Design)
|
||||||
* [Addon Development](help/Addons)
|
* [Addon Development](help/Addons)
|
||||||
* [Theme Development](help/themes)
|
* [Theme Development](help/themes)
|
||||||
* [Smarty 3 Templates](help/smarty3-templates)
|
* [Smarty 3 Templates](help/smarty3-templates)
|
||||||
|
|
|
@ -31,14 +31,14 @@ function notifications_post(App $a)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request_id) {
|
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']) {
|
switch ($_POST['submit']) {
|
||||||
case L10n::t('Discard'):
|
case L10n::t('Discard'):
|
||||||
$Intro->discard();
|
$intro->discard();
|
||||||
break;
|
break;
|
||||||
case L10n::t('Ignore'):
|
case L10n::t('Ignore'):
|
||||||
$Intro->ignore();
|
$intro->ignore();
|
||||||
break;
|
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
|
<?php
|
||||||
|
|
||||||
namespace Friendica\Api\Mastodon;
|
namespace Friendica\Api\Entity\Mastodon;
|
||||||
|
|
||||||
|
use Friendica\Api\BaseEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Field
|
* Class Field
|
||||||
*
|
*
|
||||||
* @see https://docs.joinmastodon.org/api/entities/#field
|
* @see https://docs.joinmastodon.org/api/entities/#field
|
||||||
*/
|
*/
|
||||||
class Field
|
class Field extends BaseEntity
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
var $name;
|
protected $name;
|
||||||
/** @var string (HTML) */
|
/** @var string (HTML) */
|
||||||
var $value;
|
protected $value;
|
||||||
/** @var string (Datetime)*/
|
/** @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
|
<?php
|
||||||
|
|
||||||
namespace Friendica\Api\Mastodon;
|
namespace Friendica\Api\Entity\Mastodon;
|
||||||
|
|
||||||
use Friendica\App;
|
use Friendica\Api\BaseEntity;
|
||||||
use Friendica\Core\Config;
|
use Friendica\Core\Config;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Friendica\Model\APContact;
|
|
||||||
use Friendica\Model\User;
|
use Friendica\Model\User;
|
||||||
use Friendica\Module\Register;
|
use Friendica\Module\Register;
|
||||||
|
|
||||||
|
@ -15,42 +14,41 @@ use Friendica\Module\Register;
|
||||||
*
|
*
|
||||||
* @see https://docs.joinmastodon.org/api/entities/#instance
|
* @see https://docs.joinmastodon.org/api/entities/#instance
|
||||||
*/
|
*/
|
||||||
class Instance
|
class Instance extends BaseEntity
|
||||||
{
|
{
|
||||||
/** @var string (URL) */
|
/** @var string (URL) */
|
||||||
var $uri;
|
protected $uri;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
var $title;
|
protected $title;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
var $description;
|
protected $description;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
var $email;
|
protected $email;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
var $version;
|
protected $version;
|
||||||
/** @var array */
|
/** @var array */
|
||||||
var $urls;
|
protected $urls;
|
||||||
/** @var Stats */
|
/** @var Stats */
|
||||||
var $stats;
|
protected $stats;
|
||||||
/** @var string */
|
/** @var string|null */
|
||||||
var $thumbnail;
|
protected $thumbnail = null;
|
||||||
/** @var array */
|
/** @var array */
|
||||||
var $languages;
|
protected $languages;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
var $max_toot_chars;
|
protected $max_toot_chars;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
var $registrations;
|
protected $registrations;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
var $approval_required;
|
protected $approval_required;
|
||||||
/** @var Account|null */
|
/** @var Account|null */
|
||||||
var $contact_account;
|
protected $contact_account = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance record
|
* Creates an instance record
|
||||||
*
|
*
|
||||||
* @param App $app
|
|
||||||
*
|
|
||||||
* @return Instance
|
* @return Instance
|
||||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||||
|
* @throws \ImagickException
|
||||||
*/
|
*/
|
||||||
public static function get()
|
public static function get()
|
||||||
{
|
{
|
||||||
|
@ -77,9 +75,8 @@ class Instance
|
||||||
$adminList = explode(',', str_replace(' ', '', Config::get('config', 'admin_email')));
|
$adminList = explode(',', str_replace(' ', '', Config::get('config', 'admin_email')));
|
||||||
$administrator = User::getByEmail($adminList[0], ['nickname']);
|
$administrator = User::getByEmail($adminList[0], ['nickname']);
|
||||||
if (!empty($administrator)) {
|
if (!empty($administrator)) {
|
||||||
$adminContact = DBA::selectFirst('contact', [], ['nick' => $administrator['nickname'], 'self' => true]);
|
$adminContact = DBA::selectFirst('contact', ['id'], ['nick' => $administrator['nickname'], 'self' => true]);
|
||||||
$apcontact = APContact::getByURL($adminContact['url'], false);
|
$instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id']);
|
||||||
$instance->contact_account = Account::create($baseUrl, $adminContact, $apcontact);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
<?php
|
||||||
|
|
||||||
namespace Friendica\Api\Mastodon;
|
namespace Friendica\Api\Entity\Mastodon;
|
||||||
|
|
||||||
|
use Friendica\Api\BaseEntity;
|
||||||
use Friendica\Core\Config;
|
use Friendica\Core\Config;
|
||||||
use Friendica\Core\Protocol;
|
use Friendica\Core\Protocol;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
@ -11,14 +12,14 @@ use Friendica\Database\DBA;
|
||||||
*
|
*
|
||||||
* @see https://docs.joinmastodon.org/api/entities/#stats
|
* @see https://docs.joinmastodon.org/api/entities/#stats
|
||||||
*/
|
*/
|
||||||
class Stats
|
class Stats extends BaseEntity
|
||||||
{
|
{
|
||||||
/** @var int */
|
/** @var int */
|
||||||
var $user_count;
|
protected $user_count = 0;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
var $status_count;
|
protected $status_count = 0;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
var $domain_count;
|
protected $domain_count = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a stats record
|
* 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;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BaseModel
|
|
||||||
*
|
|
||||||
* The Model classes inheriting from this abstract class are meant to represent a single database record.
|
* 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.
|
* 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
|
abstract class BaseModel
|
||||||
{
|
{
|
||||||
protected static $table_name;
|
|
||||||
|
|
||||||
/** @var Database */
|
/** @var Database */
|
||||||
protected $dba;
|
protected $dba;
|
||||||
/** @var LoggerInterface */
|
/** @var LoggerInterface */
|
||||||
|
@ -32,13 +28,33 @@ abstract class BaseModel
|
||||||
*/
|
*/
|
||||||
private $data = [];
|
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->dba = $dba;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->data = $data;
|
$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:
|
* Magic getter. This allows to retrieve model fields with the following syntax:
|
||||||
* - $model->field (outside of class)
|
* - $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).
|
* @param string $name
|
||||||
*
|
* @param mixed $value
|
||||||
* Chainable.
|
|
||||||
*
|
|
||||||
* @param array $condition
|
|
||||||
* @return BaseModel
|
|
||||||
* @throws HTTPException\NotFoundException
|
|
||||||
*/
|
*/
|
||||||
public function fetch(array $condition)
|
public function __set($name, $value)
|
||||||
{
|
{
|
||||||
$data = $this->dba->selectFirst(static::$table_name, [], $condition);
|
$this->data[$name] = $value;
|
||||||
|
|
||||||
if (!$data) {
|
|
||||||
throw new HTTPException\NotFoundException(static::class . ' record not found.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static($this->dba, $this->logger, $data);
|
public function toArray()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the model record from the database.
|
|
||||||
* Prevents further methods from being called by wiping the internal model data.
|
|
||||||
*/
|
|
||||||
public function delete()
|
|
||||||
{
|
{
|
||||||
if ($this->dba->delete(static::$table_name, ['id' => $this->id])) {
|
return $this->data;
|
||||||
$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\Process process()
|
||||||
* @method static Core\Session\ISession session()
|
* @method static Core\Session\ISession session()
|
||||||
* @method static Database\Database dba()
|
* @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\User\Cookie cookie()
|
||||||
* @method static Model\Notify notify()
|
* @method static Model\Notify notify()
|
||||||
* @method static Model\Introduction intro()
|
* @method static Repository\Introduction intro()
|
||||||
* @method static Protocol\Activity activity()
|
* @method static Protocol\Activity activity()
|
||||||
* @method static Util\ACLFormatter aclFormatter()
|
* @method static Util\ACLFormatter aclFormatter()
|
||||||
* @method static Util\DateTimeFormat dtFormat()
|
* @method static Util\DateTimeFormat dtFormat()
|
||||||
|
@ -62,9 +65,12 @@ abstract class DI
|
||||||
'process' => Core\Process::class,
|
'process' => Core\Process::class,
|
||||||
'session' => Core\Session\ISession::class,
|
'session' => Core\Session\ISession::class,
|
||||||
'dba' => Database\Database::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,
|
'cookie' => Model\User\Cookie::class,
|
||||||
'notify' => Model\Notify::class,
|
'notify' => Model\Notify::class,
|
||||||
'intro' => Model\Introduction::class,
|
'intro' => Repository\Introduction::class,
|
||||||
'activity' => Protocol\Activity::class,
|
'activity' => Protocol\Activity::class,
|
||||||
'aclFormatter' => Util\ACLFormatter::class,
|
'aclFormatter' => Util\ACLFormatter::class,
|
||||||
'dtFormat' => Util\DateTimeFormat::class,
|
'dtFormat' => Util\DateTimeFormat::class,
|
||||||
|
|
|
@ -529,19 +529,50 @@ class DBA
|
||||||
*/
|
*/
|
||||||
public static function buildCondition(array &$condition = [])
|
public static function buildCondition(array &$condition = [])
|
||||||
{
|
{
|
||||||
|
$condition = self::collapseCondition($condition);
|
||||||
|
|
||||||
$condition_string = '';
|
$condition_string = '';
|
||||||
if (count($condition) > 0) {
|
if (count($condition) > 0) {
|
||||||
|
$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);
|
reset($condition);
|
||||||
$first_key = key($condition);
|
$first_key = key($condition);
|
||||||
|
|
||||||
if (is_int($first_key)) {
|
if (is_int($first_key)) {
|
||||||
$condition_string = " WHERE (" . array_shift($condition) . ")";
|
// Already collapsed
|
||||||
} else {
|
return $condition;
|
||||||
$new_values = [];
|
}
|
||||||
|
|
||||||
|
$values = [];
|
||||||
$condition_string = "";
|
$condition_string = "";
|
||||||
foreach ($condition as $field => $value) {
|
foreach ($condition as $field => $value) {
|
||||||
if ($condition_string != "") {
|
if ($condition_string != "") {
|
||||||
$condition_string .= " AND ";
|
$condition_string .= " AND ";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
if (count($value)) {
|
if (count($value)) {
|
||||||
/* Workaround for MySQL Bug #64791.
|
/* Workaround for MySQL Bug #64791.
|
||||||
|
@ -568,7 +599,7 @@ class DBA
|
||||||
unset($ref); //Prevent accidental re-use.
|
unset($ref); //Prevent accidental re-use.
|
||||||
}
|
}
|
||||||
|
|
||||||
$new_values = array_merge($new_values, array_values($value));
|
$values = array_merge($values, array_values($value));
|
||||||
$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
|
$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
|
||||||
$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
|
$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
|
||||||
} else {
|
} else {
|
||||||
|
@ -578,16 +609,14 @@ class DBA
|
||||||
} elseif (is_null($value)) {
|
} elseif (is_null($value)) {
|
||||||
$condition_string .= self::quoteIdentifier($field) . " IS NULL";
|
$condition_string .= self::quoteIdentifier($field) . " IS NULL";
|
||||||
} else {
|
} else {
|
||||||
$new_values[$field] = $value;
|
$values[$field] = $value;
|
||||||
$condition_string .= self::quoteIdentifier($field) . " = ?";
|
$condition_string .= self::quoteIdentifier($field) . " = ?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$condition_string = " WHERE (" . $condition_string . ")";
|
|
||||||
$condition = $new_values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $condition_string;
|
$condition = array_merge([$condition_string], array_values($values));
|
||||||
|
|
||||||
|
return $condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1327,10 +1327,6 @@ class Database
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$table_string = DBA::buildTableString($table);
|
|
||||||
|
|
||||||
$condition_string = DBA::buildCondition($condition);
|
|
||||||
|
|
||||||
if (is_bool($old_fields)) {
|
if (is_bool($old_fields)) {
|
||||||
$do_insert = $old_fields;
|
$do_insert = $old_fields;
|
||||||
|
|
||||||
|
@ -1361,13 +1357,16 @@ class Database
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$table_string = DBA::buildTableString($table);
|
||||||
|
|
||||||
|
$condition_string = DBA::buildCondition($condition);
|
||||||
|
|
||||||
$sql = "UPDATE " . $table_string . " SET "
|
$sql = "UPDATE " . $table_string . " SET "
|
||||||
. implode(" = ?, ", array_map([DBA::class, 'quoteIdentifier'], array_keys($fields))) . " = ?"
|
. implode(" = ?, ", array_map([DBA::class, 'quoteIdentifier'], array_keys($fields))) . " = ?"
|
||||||
. $condition_string;
|
. $condition_string;
|
||||||
|
|
||||||
$params1 = array_values($fields);
|
// Combines the updated fields parameter values with the condition parameter values
|
||||||
$params2 = array_values($condition);
|
$params = array_merge(array_values($fields), $condition);
|
||||||
$params = array_merge_recursive($params1, $params2);
|
|
||||||
|
|
||||||
return $this->e($sql, $params);
|
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\BaseModel;
|
||||||
use Friendica\Core\Protocol;
|
use Friendica\Core\Protocol;
|
||||||
|
use Friendica\Database\Database;
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
use Friendica\Protocol\ActivityPub;
|
use Friendica\Protocol\ActivityPub;
|
||||||
use Friendica\Protocol\Diaspora;
|
use Friendica\Protocol\Diaspora;
|
||||||
|
use Friendica\Repository;
|
||||||
use Friendica\Util\DateTimeFormat;
|
use Friendica\Util\DateTimeFormat;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int uid
|
* @property int uid
|
||||||
|
@ -20,33 +23,40 @@ use Friendica\Util\DateTimeFormat;
|
||||||
* @property string datetime
|
* @property string datetime
|
||||||
* @property bool blocked
|
* @property bool blocked
|
||||||
* @property bool ignored
|
* @property bool ignored
|
||||||
*
|
|
||||||
* @package Friendica\Model
|
|
||||||
*/
|
*/
|
||||||
final class Introduction extends BaseModel
|
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 $duplex Is it a follow back?
|
||||||
* @param bool|null $hidden Should this contact be hidden? null = no change
|
* @param bool|null $hidden Should this contact be hidden? null = no change
|
||||||
|
* @return bool
|
||||||
* @throws HTTPException\InternalServerErrorException
|
* @throws HTTPException\InternalServerErrorException
|
||||||
* @throws \ImagickException
|
|
||||||
* @throws HTTPException\NotFoundException
|
* @throws HTTPException\NotFoundException
|
||||||
|
* @throws \ImagickException
|
||||||
*/
|
*/
|
||||||
public function confirm(bool $duplex = false, bool $hidden = null)
|
public function confirm(bool $duplex = false, bool $hidden = null)
|
||||||
{
|
{
|
||||||
$this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]);
|
$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) {
|
if (!$contact) {
|
||||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$new_relation = $contact['rel'];
|
$newRelation = $contact['rel'];
|
||||||
$writable = $contact['writable'];
|
$writable = $contact['writable'];
|
||||||
|
|
||||||
if (!empty($contact['protocol'])) {
|
if (!empty($contact['protocol'])) {
|
||||||
|
@ -61,12 +71,12 @@ final class Introduction extends BaseModel
|
||||||
|
|
||||||
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||||
if ($duplex) {
|
if ($duplex) {
|
||||||
$new_relation = Contact::FRIEND;
|
$newRelation = Model\Contact::FRIEND;
|
||||||
} else {
|
} else {
|
||||||
$new_relation = Contact::FOLLOWER;
|
$newRelation = Model\Contact::FOLLOWER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($new_relation != Contact::FOLLOWER) {
|
if ($newRelation != Model\Contact::FOLLOWER) {
|
||||||
$writable = 1;
|
$writable = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,43 +89,42 @@ final class Introduction extends BaseModel
|
||||||
'protocol' => $protocol,
|
'protocol' => $protocol,
|
||||||
'writable' => $writable,
|
'writable' => $writable,
|
||||||
'hidden' => $hidden ?? $contact['hidden'],
|
'hidden' => $hidden ?? $contact['hidden'],
|
||||||
'rel' => $new_relation,
|
'rel' => $newRelation,
|
||||||
];
|
];
|
||||||
$this->dba->update('contact', $fields, ['id' => $contact['id']]);
|
$this->dba->update('contact', $fields, ['id' => $contact['id']]);
|
||||||
|
|
||||||
array_merge($contact, $fields);
|
array_merge($contact, $fields);
|
||||||
|
|
||||||
if ($new_relation == Contact::FRIEND) {
|
if ($newRelation == Model\Contact::FRIEND) {
|
||||||
if ($protocol == Protocol::DIASPORA) {
|
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]);
|
$this->logger->info('share returns', ['return' => $ret]);
|
||||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||||
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
|
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->delete();
|
return $this->intro->delete($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
|
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
|
||||||
* additional follow requests.
|
* additional follow requests.
|
||||||
*
|
*
|
||||||
* Chainable
|
* @return bool
|
||||||
*
|
|
||||||
* @return Introduction
|
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function ignore()
|
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.
|
* Discards the introduction and sends a rejection message to AP contacts.
|
||||||
*
|
*
|
||||||
|
* @return bool
|
||||||
* @throws HTTPException\InternalServerErrorException
|
* @throws HTTPException\InternalServerErrorException
|
||||||
* @throws HTTPException\NotFoundException
|
* @throws HTTPException\NotFoundException
|
||||||
* @throws \ImagickException
|
* @throws \ImagickException
|
||||||
|
@ -127,15 +136,15 @@ final class Introduction extends BaseModel
|
||||||
if (!$this->fid) {
|
if (!$this->fid) {
|
||||||
// When the contact entry had been created just for that intro, we want to get rid of it now
|
// 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,
|
$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)) {
|
if ($this->dba->exists('contact', $condition)) {
|
||||||
Contact::remove($this->{'contact-id'});
|
Model\Contact::remove($this->{'contact-id'});
|
||||||
} else {
|
} else {
|
||||||
$this->dba->update('contact', ['pending' => false], ['id' => $this->{'contact-id'}]);
|
$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) {
|
if (!$contact) {
|
||||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
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']);
|
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;
|
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\Core\System;
|
||||||
use Friendica\Database\DBA;
|
|
||||||
use Friendica\Model\APContact;
|
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Contact;
|
||||||
use Friendica\Model\Introduction;
|
|
||||||
use Friendica\Module\Base\Api;
|
use Friendica\Module\Base\Api;
|
||||||
use Friendica\Network\HTTPException;
|
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
|
class FollowRequests extends Api
|
||||||
{
|
{
|
||||||
|
@ -30,8 +28,11 @@ class FollowRequests extends Api
|
||||||
* @param array $parameters
|
* @param array $parameters
|
||||||
* @throws HTTPException\BadRequestException
|
* @throws HTTPException\BadRequestException
|
||||||
* @throws HTTPException\ForbiddenException
|
* @throws HTTPException\ForbiddenException
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
* @throws HTTPException\NotFoundException
|
* @throws HTTPException\NotFoundException
|
||||||
* @throws HTTPException\UnauthorizedException
|
* @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#accept-follow
|
||||||
* @see https://docs.joinmastodon.org/methods/accounts/follow_requests#reject-follow
|
* @see https://docs.joinmastodon.org/methods/accounts/follow_requests#reject-follow
|
||||||
*/
|
*/
|
||||||
|
@ -39,23 +40,25 @@ class FollowRequests extends Api
|
||||||
{
|
{
|
||||||
parent::post($parameters);
|
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'};
|
$contactId = $introduction->{'contact-id'};
|
||||||
|
|
||||||
$relationship = new Mastodon\Relationship();
|
|
||||||
$relationship->id = $contactId;
|
|
||||||
|
|
||||||
switch ($parameters['action']) {
|
switch ($parameters['action']) {
|
||||||
case 'authorize':
|
case 'authorize':
|
||||||
$Intro->confirm();
|
$introduction->confirm();
|
||||||
$relationship = Mastodon\Relationship::createFromContact(Contact::getById($contactId));
|
|
||||||
|
$relationship = DI::mstdnRelationship()->createFromContactId($contactId);
|
||||||
break;
|
break;
|
||||||
case 'ignore':
|
case 'ignore':
|
||||||
$Intro->ignore();
|
$introduction->ignore();
|
||||||
|
|
||||||
|
$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
|
||||||
break;
|
break;
|
||||||
case 'reject':
|
case 'reject':
|
||||||
$Intro->discard();
|
$introduction->discard();
|
||||||
|
|
||||||
|
$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
|
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
|
||||||
|
@ -78,41 +81,23 @@ class FollowRequests extends Api
|
||||||
|
|
||||||
$baseUrl = DI::baseUrl();
|
$baseUrl = DI::baseUrl();
|
||||||
|
|
||||||
if (isset($since_id) && isset($max_id)) {
|
$introductions = DI::intro()->selectByBoundaries(
|
||||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
['`uid` = ? AND NOT `ignore`', self::$current_user_id],
|
||||||
} elseif (isset($since_id)) {
|
['order' => ['id' => 'DESC']],
|
||||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ?', self::$current_user_id, $since_id];
|
$since_id,
|
||||||
} elseif (isset($max_id)) {
|
$max_id,
|
||||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` < ?', self::$current_user_id, $max_id];
|
$limit
|
||||||
} 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]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$return = [];
|
$return = [];
|
||||||
foreach ($intros as $intro) {
|
|
||||||
$cdata = Contact::getPublicAndUserContacID($intro['contact-id'], $intro['uid']);
|
foreach ($introductions as $key => $introduction) {
|
||||||
if (empty($cdata['public'])) {
|
try {
|
||||||
continue;
|
$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 = [];
|
$base_query = [];
|
||||||
|
@ -121,10 +106,10 @@ class FollowRequests extends Api
|
||||||
}
|
}
|
||||||
|
|
||||||
$links = [];
|
$links = [];
|
||||||
if ($count > $limit) {
|
if ($introductions->getTotalCount() > $limit) {
|
||||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $intros[count($intros) - 1]['id']]) . '>; rel="next"';
|
$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));
|
header('Link: ' . implode(', ', $links));
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Friendica\Module\Api\Mastodon;
|
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\Core\System;
|
||||||
use Friendica\Module\Base\Api;
|
use Friendica\Module\Base\Api;
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,11 @@ class FollowConfirm extends BaseModule
|
||||||
$duplex = intval($_POST['duplex'] ?? 0);
|
$duplex = intval($_POST['duplex'] ?? 0);
|
||||||
$hidden = intval($_POST['hidden'] ?? 0);
|
$hidden = intval($_POST['hidden'] ?? 0);
|
||||||
|
|
||||||
$Intro = DI::intro()->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));
|
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