[PLUGIN][ActivityPub][Inbox] Accept Follow Activity
Improve how Core Activity is handled in general
This commit is contained in:
parent
afb7ae0f75
commit
def5f36c25
|
@ -77,7 +77,7 @@ class Attachment extends Component
|
||||||
/**
|
/**
|
||||||
* Populate $note_expr with the criteria for looking for notes with attachments
|
* Populate $note_expr with the criteria for looking for notes with attachments
|
||||||
*/
|
*/
|
||||||
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr)
|
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr): bool
|
||||||
{
|
{
|
||||||
$include_term = str_contains($term, ':') ? explode(':', $term)[1] : $term;
|
$include_term = str_contains($term, ':') ? explode(':', $term)[1] : $term;
|
||||||
if (Formatting::startsWith($term, ['note-types:', 'notes-incude:', 'note-filter:'])) {
|
if (Formatting::startsWith($term, ['note-types:', 'notes-incude:', 'note-filter:'])) {
|
||||||
|
|
|
@ -28,10 +28,10 @@ use App\Core\Event;
|
||||||
use App\Core\Modules\Component;
|
use App\Core\Modules\Component;
|
||||||
use App\Core\Router\RouteLoader;
|
use App\Core\Router\RouteLoader;
|
||||||
use App\Entity\Actor;
|
use App\Entity\Actor;
|
||||||
use App\Entity\Subscription;
|
|
||||||
use App\Util\Formatting;
|
use App\Util\Formatting;
|
||||||
use Component\Feed\Controller as C;
|
use Component\Feed\Controller as C;
|
||||||
use Component\Search\Util\Parser;
|
use Component\Search\Util\Parser;
|
||||||
|
use Component\Subscription\Entity\Subscription;
|
||||||
use Doctrine\Common\Collections\ExpressionBuilder;
|
use Doctrine\Common\Collections\ExpressionBuilder;
|
||||||
use Doctrine\ORM\Query\Expr;
|
use Doctrine\ORM\Query\Expr;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
@ -80,9 +80,9 @@ class Feed extends Component
|
||||||
return ['notes' => $notes ?? null, 'actors' => $actors ?? null];
|
return ['notes' => $notes ?? null, 'actors' => $actors ?? null];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSearchQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb)
|
public function onSearchQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb): bool
|
||||||
{
|
{
|
||||||
$note_qb->leftJoin(Subscription::class, 'subscription', Expr\Join::WITH, 'note.actor_id = subscription.subscribed')
|
$note_qb->leftJoin(Subscription::class, 'subscription', Expr\Join::WITH, 'note.actor_id = subscription.subscribed_id')
|
||||||
->leftJoin(Actor::class, 'note_actor', Expr\Join::WITH, 'note.actor_id = note_actor.id');
|
->leftJoin(Actor::class, 'note_actor', Expr\Join::WITH, 'note.actor_id = note_actor.id');
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ class Feed extends Component
|
||||||
break;
|
break;
|
||||||
case 'note-from':
|
case 'note-from':
|
||||||
case 'notes-from':
|
case 'notes-from':
|
||||||
$subscribed_expr = $eb->eq('subscription.subscriber', $actor->getId());
|
$subscribed_expr = $eb->eq('subscription.subscriber_id', $actor->getId());
|
||||||
$type_consts = [];
|
$type_consts = [];
|
||||||
if ($term[1] === 'subscribed') {
|
if ($term[1] === 'subscribed') {
|
||||||
$type_consts = null;
|
$type_consts = null;
|
||||||
|
|
|
@ -9,18 +9,16 @@
|
||||||
{% endblock stylesheets %}
|
{% endblock stylesheets %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if notes is defined and notes is not empty %}
|
<header class="feed-header">
|
||||||
<header class="feed-header">
|
{% if page_title is defined %}
|
||||||
{% if page_title is defined %}
|
<h1>{{ page_title | trans }}</h1>
|
||||||
<h1>{{ page_title | trans }}</h1>
|
|
||||||
{% endif %}
|
|
||||||
<nav class="feed-actions">
|
|
||||||
{% for block in handle_event('AddFeedActions', app.request) %}
|
|
||||||
{{ block | raw }}
|
|
||||||
{% endfor %}
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<nav class="feed-actions">
|
||||||
|
{% for block in handle_event('AddFeedActions', app.request) %}
|
||||||
|
{{ block | raw }}
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
{# Backwards compatibility with hAtom 0.1 #}
|
{# Backwards compatibility with hAtom 0.1 #}
|
||||||
<main class="feed" tabindex="0" role="feed">
|
<main class="feed" tabindex="0" role="feed">
|
||||||
|
|
|
@ -38,13 +38,13 @@ use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
class Language extends Component
|
class Language extends Component
|
||||||
{
|
{
|
||||||
public function onAddRoute(RouteLoader $r)
|
public function onAddRoute(RouteLoader $r): bool
|
||||||
{
|
{
|
||||||
$r->connect('settings_sort_languages', '/settings/sort_languages', [C\Language::class, 'sortLanguages']);
|
$r->connect('settings_sort_languages', '/settings/sort_languages', [C\Language::class, 'sortLanguages']);
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onFilterNoteList(?Actor $actor, array &$notes, Request $request)
|
public function onFilterNoteList(?Actor $actor, array &$notes, Request $request): bool
|
||||||
{
|
{
|
||||||
if (\is_null($actor)) {
|
if (\is_null($actor)) {
|
||||||
return Event::next;
|
return Event::next;
|
||||||
|
@ -60,7 +60,7 @@ class Language extends Component
|
||||||
/**
|
/**
|
||||||
* Populate $note_expr or $actor_expr with an expression to match a language
|
* Populate $note_expr or $actor_expr with an expression to match a language
|
||||||
*/
|
*/
|
||||||
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr)
|
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr): bool
|
||||||
{
|
{
|
||||||
$search_term = str_contains($term, ':') ? explode(':', $term)[1] : $term;
|
$search_term = str_contains($term, ':') ? explode(':', $term)[1] : $term;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
// {{{ License
|
// {{{ License
|
||||||
// This file is part of GNU social - https://www.gnu.org/software/social
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
//
|
//
|
||||||
|
@ -17,11 +19,13 @@
|
||||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace Component\Subscription\Entity;
|
||||||
|
|
||||||
use App\Core\Entity;
|
use App\Core\Entity;
|
||||||
use DateTimeInterface;
|
use App\Entity\Actor;
|
||||||
|
use App\Entity\LocalUser;
|
||||||
use Component\Group\Entity\LocalGroup;
|
use Component\Group\Entity\LocalGroup;
|
||||||
|
use DateTimeInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity for subscription
|
* Entity for subscription
|
||||||
|
@ -41,51 +45,51 @@ class Subscription extends Entity
|
||||||
{
|
{
|
||||||
// {{{ Autocode
|
// {{{ Autocode
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
private int $subscriber;
|
private int $subscriber_id;
|
||||||
private int $subscribed;
|
private int $subscribed_id;
|
||||||
private \DateTimeInterface $created;
|
private DateTimeInterface $created;
|
||||||
private \DateTimeInterface $modified;
|
private DateTimeInterface $modified;
|
||||||
|
|
||||||
public function setSubscriber(int $subscriber): self
|
public function setSubscriberId(int $subscriber_id): self
|
||||||
{
|
{
|
||||||
$this->subscriber = $subscriber;
|
$this->subscriber_id = $subscriber_id;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubscriber(): int
|
public function getSubscriberId(): int
|
||||||
{
|
{
|
||||||
return $this->subscriber;
|
return $this->subscriber_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSubscribed(int $subscribed): self
|
public function setSubscribedId(int $subscribed_id): self
|
||||||
{
|
{
|
||||||
$this->subscribed = $subscribed;
|
$this->subscribed_id = $subscribed_id;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubscribed(): int
|
public function getSubscribedId(): int
|
||||||
{
|
{
|
||||||
return $this->subscribed;
|
return $this->subscribed_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreated(\DateTimeInterface $created): self
|
public function setCreated(DateTimeInterface $created): self
|
||||||
{
|
{
|
||||||
$this->created = $created;
|
$this->created = $created;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreated(): \DateTimeInterface
|
public function getCreated(): DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->created;
|
return $this->created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setModified(\DateTimeInterface $modified): self
|
public function setModified(DateTimeInterface $modified): self
|
||||||
{
|
{
|
||||||
$this->modified = $modified;
|
$this->modified = $modified;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getModified(): \DateTimeInterface
|
public function getModified(): DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->modified;
|
return $this->modified;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +97,16 @@ class Subscription extends Entity
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
// }}} Autocode
|
// }}} Autocode
|
||||||
|
|
||||||
|
public function getSubscriber(): Actor
|
||||||
|
{
|
||||||
|
return Actor::getById($this->getSubscriberId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubscribed(): Actor
|
||||||
|
{
|
||||||
|
return Actor::getById($this->getSubscribedId());
|
||||||
|
}
|
||||||
|
|
||||||
public static function cacheKeys(LocalUser|LocalGroup|Actor $subject, LocalUser|LocalGroup|Actor $target): array
|
public static function cacheKeys(LocalUser|LocalGroup|Actor $subject, LocalUser|LocalGroup|Actor $target): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -105,15 +119,15 @@ class Subscription extends Entity
|
||||||
return [
|
return [
|
||||||
'name' => 'subscription',
|
'name' => 'subscription',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'subscriber' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'subscrib_subscriber_fkey', 'not null' => true, 'description' => 'actor listening'],
|
'subscriber_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'subscription_subscriber_fkey', 'not null' => true, 'description' => 'actor listening'],
|
||||||
'subscribed' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'subscrib_subscribed_fkey', 'not null' => true, 'description' => 'actor being listened to'],
|
'subscribed_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'subscription_subscribed_fkey', 'not null' => true, 'description' => 'actor being listened to'],
|
||||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
||||||
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
||||||
],
|
],
|
||||||
'primary key' => ['subscriber', 'subscribed'],
|
'primary key' => ['subscriber_id', 'subscribed_id'],
|
||||||
'indexes' => [
|
'indexes' => [
|
||||||
'subscrib_subscriber_idx' => ['subscriber', 'created'],
|
'subscription_subscriber_idx' => ['subscriber_id', 'created'],
|
||||||
'subscrib_subscribed_idx' => ['subscribed', 'created'],
|
'subscription_subscribed_idx' => ['subscribed_id', 'created'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
11
components/Subscription/Subscription.php
Normal file
11
components/Subscription/Subscription.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace Component\Subscription;
|
||||||
|
|
||||||
|
use App\Core\Modules\Component;
|
||||||
|
|
||||||
|
class Subscription extends Component
|
||||||
|
{
|
||||||
|
}
|
|
@ -165,8 +165,11 @@ class Tag extends Component
|
||||||
*
|
*
|
||||||
* $term /^(note|tag|people|actor)/ means we want to match only either a note or an actor
|
* $term /^(note|tag|people|actor)/ means we want to match only either a note or an actor
|
||||||
*/
|
*/
|
||||||
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr)
|
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr): bool
|
||||||
{
|
{
|
||||||
|
if (!str_contains($term, ':')) {
|
||||||
|
return Event::next;
|
||||||
|
}
|
||||||
[$search_type, $search_term] = explode(':', $term);
|
[$search_type, $search_term] = explode(':', $term);
|
||||||
if (str_starts_with($search_term, '#')) {
|
if (str_starts_with($search_term, '#')) {
|
||||||
$search_term = self::ensureValid($search_term);
|
$search_term = self::ensureValid($search_term);
|
||||||
|
|
|
@ -44,6 +44,7 @@ use App\Entity\Actor;
|
||||||
use App\Entity\LocalUser;
|
use App\Entity\LocalUser;
|
||||||
use App\Entity\Note;
|
use App\Entity\Note;
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
|
use App\Util\Exception\BugFoundException;
|
||||||
use App\Util\Exception\NoSuchActorException;
|
use App\Util\Exception\NoSuchActorException;
|
||||||
use App\Util\Nickname;
|
use App\Util\Nickname;
|
||||||
use Component\FreeNetwork\Entity\FreeNetworkActorProtocol;
|
use Component\FreeNetwork\Entity\FreeNetworkActorProtocol;
|
||||||
|
@ -372,26 +373,35 @@ class ActivityPub extends Plugin
|
||||||
*/
|
*/
|
||||||
public static function getUriByObject(mixed $object): string
|
public static function getUriByObject(mixed $object): string
|
||||||
{
|
{
|
||||||
if ($object instanceof Note) {
|
switch ($object::class) {
|
||||||
if ($object->getIsLocal()) {
|
case Note::class:
|
||||||
return $object->getUrl();
|
if ($object->getIsLocal()) {
|
||||||
} else {
|
return $object->getUrl();
|
||||||
// Try known remote objects
|
} else {
|
||||||
$known_object = ActivitypubObject::getByPK(['object_type' => 'note', 'object_id' => $object->getId()]);
|
// Try known remote objects
|
||||||
if ($known_object instanceof ActivitypubObject) {
|
$known_object = ActivitypubObject::getByPK(['object_type' => 'note', 'object_id' => $object->getId()]);
|
||||||
return $known_object->getObjectUri();
|
if ($known_object instanceof ActivitypubObject) {
|
||||||
|
return $known_object->getObjectUri();
|
||||||
|
} else {
|
||||||
|
throw new BugFoundException('ActivityPub cannot generate an URI for a stored note.', [$object, $known_object]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
} elseif ($object instanceof Activity) {
|
case Actor::class:
|
||||||
// Try known remote activities
|
return $object->getUri();
|
||||||
$known_activity = ActivitypubActivity::getByPK(['activity_id' => $object->getId()]);
|
break;
|
||||||
if ($known_activity instanceof ActivitypubActivity) {
|
case Activity::class:
|
||||||
return $known_activity->getActivityUri();
|
// Try known remote activities
|
||||||
} else {
|
$known_activity = ActivitypubActivity::getByPK(['activity_id' => $object->getId()]);
|
||||||
return Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL);
|
if ($known_activity instanceof ActivitypubActivity) {
|
||||||
}
|
return $known_activity->getActivityUri();
|
||||||
|
} else {
|
||||||
|
return Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('ActivityPub::getUriByObject found a limitation with: ' . var_export($object, true));
|
||||||
}
|
}
|
||||||
throw new InvalidArgumentException('ActivityPub::getUriByObject found a limitation with: ' . var_export($object, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,31 +417,38 @@ class ActivityPub extends Plugin
|
||||||
*/
|
*/
|
||||||
public static function getObjectByUri(string $resource, bool $try_online = true)
|
public static function getObjectByUri(string $resource, bool $try_online = true)
|
||||||
{
|
{
|
||||||
// Try known objects
|
// Try known object
|
||||||
$known_object = ActivitypubObject::getByPK(['object_uri' => $resource]);
|
$known_object = ActivitypubObject::getByPK(['object_uri' => $resource]);
|
||||||
if ($known_object instanceof ActivitypubObject) {
|
if ($known_object instanceof ActivitypubObject) {
|
||||||
return $known_object->getObject();
|
return $known_object->getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try known activities
|
// Try known activity
|
||||||
$known_activity = ActivitypubActivity::getByPK(['activity_uri' => $resource]);
|
$known_activity = ActivitypubActivity::getByPK(['activity_uri' => $resource]);
|
||||||
if ($known_activity instanceof ActivitypubActivity) {
|
if ($known_activity instanceof ActivitypubActivity) {
|
||||||
return $known_activity->getActivity();
|
return $known_activity->getActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try local Notes (pretty incomplete effort, I know)
|
// Try local Note
|
||||||
if (Common::isValidHttpUrl($resource)) {
|
if (Common::isValidHttpUrl($resource)) {
|
||||||
// This means $resource is a valid url
|
// This means $resource is a valid url
|
||||||
$resource_parts = parse_url($resource);
|
$resource_parts = parse_url($resource);
|
||||||
// TODO: Use URLMatcher
|
// TODO: Use URLMatcher
|
||||||
if ($resource_parts['host'] === $_ENV['SOCIAL_DOMAIN']) { // XXX: Common::config('site', 'server')) {
|
if ($resource_parts['host'] === $_ENV['SOCIAL_DOMAIN']) { // XXX: Common::config('site', 'server')) {
|
||||||
$local_note = DB::findOneBy('note', ['url' => $resource]);
|
$local_note = DB::findOneBy('note', ['url' => $resource], return_null: true);
|
||||||
if ($local_note instanceof Note) {
|
if ($local_note instanceof Note) {
|
||||||
return $local_note;
|
return $local_note;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try Actor
|
||||||
|
try {
|
||||||
|
return self::getActorByUri($resource, try_online: false);
|
||||||
|
} catch (Exception) {
|
||||||
|
// Ignore, this is brute forcing, it's okay not to find
|
||||||
|
}
|
||||||
|
|
||||||
// Try remote
|
// Try remote
|
||||||
if (!$try_online) {
|
if (!$try_online) {
|
||||||
return;
|
return;
|
||||||
|
@ -457,7 +474,7 @@ class ActivityPub extends Plugin
|
||||||
*
|
*
|
||||||
* @return Actor got from URI
|
* @return Actor got from URI
|
||||||
*/
|
*/
|
||||||
public static function getActorByUri(string $resource): Actor
|
public static function getActorByUri(string $resource, bool $try_online = true): Actor
|
||||||
{
|
{
|
||||||
// Try local
|
// Try local
|
||||||
if (Common::isValidHttpUrl($resource)) {
|
if (Common::isValidHttpUrl($resource)) {
|
||||||
|
@ -478,11 +495,12 @@ class ActivityPub extends Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try remote
|
// Try remote
|
||||||
$aprofile = ActivitypubActor::getByAddr($resource);
|
if ($try_online) {
|
||||||
if ($aprofile instanceof ActivitypubActor) {
|
$aprofile = ActivitypubActor::getByAddr($resource);
|
||||||
return Actor::getById($aprofile->getActorId());
|
if ($aprofile instanceof ActivitypubActor) {
|
||||||
} else {
|
return Actor::getById($aprofile->getActorId());
|
||||||
throw new NoSuchActorException("From URI: {$resource}");
|
}
|
||||||
}
|
}
|
||||||
|
throw new NoSuchActorException("From URI: {$resource}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
// {{{ License
|
// {{{ License
|
||||||
// This file is part of GNU social - https://www.gnu.org/software/social
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
//
|
//
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace Plugin\ActivityPub\Entity;
|
||||||
|
|
||||||
use App\Core\Entity;
|
use App\Core\Entity;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
@ -36,13 +38,13 @@ use DateTimeInterface;
|
||||||
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
|
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
class SubscriptionQueue extends Entity
|
class ActivitypubFollowRequestQueue extends Entity
|
||||||
{
|
{
|
||||||
// {{{ Autocode
|
// {{{ Autocode
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
private int $subscriber;
|
private int $subscriber;
|
||||||
private int $subscribed;
|
private int $subscribed;
|
||||||
private \DateTimeInterface $created;
|
private DateTimeInterface $created;
|
||||||
|
|
||||||
public function setSubscriber(int $subscriber): self
|
public function setSubscriber(int $subscriber): self
|
||||||
{
|
{
|
||||||
|
@ -66,13 +68,13 @@ class SubscriptionQueue extends Entity
|
||||||
return $this->subscribed;
|
return $this->subscribed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreated(\DateTimeInterface $created): self
|
public function setCreated(DateTimeInterface $created): self
|
||||||
{
|
{
|
||||||
$this->created = $created;
|
$this->created = $created;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreated(): \DateTimeInterface
|
public function getCreated(): DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->created;
|
return $this->created;
|
||||||
}
|
}
|
||||||
|
@ -83,17 +85,17 @@ class SubscriptionQueue extends Entity
|
||||||
public static function schemaDef(): array
|
public static function schemaDef(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'subscription_queue',
|
'name' => 'activitypub_follow_request_queue',
|
||||||
'description' => 'Holder for Subscription requests awaiting moderation.',
|
'description' => 'Holder for Subscription requests awaiting moderation.',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'subscriber' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'Subscription_queue_subscriber_fkey', 'not null' => true, 'description' => 'actor making the request'],
|
'subscriber' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'activitypub_follow_request_queue_subscriber_fkey', 'not null' => true, 'description' => 'actor making the request'],
|
||||||
'subscribed' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'Subscription_queue_subscribed_fkey', 'not null' => true, 'description' => 'actor being subscribed'],
|
'subscribed' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'activitypub_follow_request_queue_subscribed_fkey', 'not null' => true, 'description' => 'actor being subscribed'],
|
||||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
||||||
],
|
],
|
||||||
'primary key' => ['subscriber', 'subscribed'],
|
'primary key' => ['subscriber', 'subscribed'],
|
||||||
'indexes' => [
|
'indexes' => [
|
||||||
'subscription_queue_subscriber_created_idx' => ['subscriber', 'created'],
|
'activitypub_follow_request_queue_subscriber_created_idx' => ['subscriber', 'created'],
|
||||||
'subscription_queue_subscribed_created_idx' => ['subscribed', 'created'],
|
'activitypub_follow_request_queue_subscribed_created_idx' => ['subscribed', 'created'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
|
@ -34,16 +34,13 @@ namespace Plugin\ActivityPub\Util\Model;
|
||||||
|
|
||||||
use ActivityPhp\Type;
|
use ActivityPhp\Type;
|
||||||
use ActivityPhp\Type\AbstractObject;
|
use ActivityPhp\Type\AbstractObject;
|
||||||
use App\Core\DB\DB;
|
|
||||||
use App\Core\Event;
|
use App\Core\Event;
|
||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
use App\Entity\Activity as GSActivity;
|
use App\Entity\Activity as GSActivity;
|
||||||
use App\Util\Exception\ClientException;
|
use App\Util\Exception\ClientException;
|
||||||
use App\Util\Exception\NoSuchActorException;
|
use App\Util\Exception\NoSuchActorException;
|
||||||
use App\Util\Exception\NotFoundException;
|
use App\Util\Exception\NotFoundException;
|
||||||
use DateTime;
|
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Exception;
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Plugin\ActivityPub\ActivityPub;
|
use Plugin\ActivityPub\ActivityPub;
|
||||||
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||||
|
@ -105,34 +102,12 @@ class Activity extends Model
|
||||||
|
|
||||||
private static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
|
private static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
|
||||||
{
|
{
|
||||||
if ($type_activity->get('type') === 'Create' && $type_object->get('type') === 'Note') {
|
switch ($type_activity->get('type')) {
|
||||||
if ($type_object instanceof AbstractObject) {
|
case 'Create':
|
||||||
$note = Note::fromJson($type_object, ['test_authority' => true, 'actor_uri' => $type_activity->get('actor'), 'actor' => $actor, 'actor_id' => $actor->getId()]);
|
ActivityCreate::handle_core_activity($actor, $type_activity, $type_object, $ap_act);
|
||||||
} else {
|
break;
|
||||||
if ($type_object instanceof \App\Entity\Note) {
|
case 'Follow':
|
||||||
$note = $type_object;
|
ActivityFollow::handle_core_activity($actor, $type_activity, $type_object, $ap_act);
|
||||||
} else {
|
|
||||||
throw new Exception('dunno bro');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Store Activity
|
|
||||||
$act = GSActivity::create([
|
|
||||||
'actor_id' => $actor->getId(),
|
|
||||||
'verb' => 'create',
|
|
||||||
'object_type' => 'note',
|
|
||||||
'object_id' => $note->getId(),
|
|
||||||
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
|
||||||
'source' => 'ActivityPub',
|
|
||||||
]);
|
|
||||||
DB::persist($act);
|
|
||||||
// Store ActivityPub Activity
|
|
||||||
$ap_act = ActivitypubActivity::create([
|
|
||||||
'activity_id' => $act->getId(),
|
|
||||||
'activity_uri' => $type_activity->get('id'),
|
|
||||||
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
|
||||||
'modified' => new DateTime(),
|
|
||||||
]);
|
|
||||||
DB::persist($ap_act);
|
|
||||||
}
|
}
|
||||||
return $ap_act;
|
return $ap_act;
|
||||||
}
|
}
|
||||||
|
@ -145,27 +120,28 @@ class Activity extends Model
|
||||||
public static function toJson(mixed $object, ?int $options = null): string
|
public static function toJson(mixed $object, ?int $options = null): string
|
||||||
{
|
{
|
||||||
if ($object::class !== GSActivity::class) {
|
if ($object::class !== GSActivity::class) {
|
||||||
throw new InvalidArgumentException('First argument type is Activity');
|
throw new InvalidArgumentException('First argument type must be an Activity.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$gs_verb_to_activity_stream_two_verb = null;
|
$gs_verb_to_activity_streams_two_verb = null;
|
||||||
if (Event::handle('GSVerbToActivityStreamsTwoActivityType', [($verb = $object->getVerb()), &$gs_verb_to_activity_stream_two_verb]) === Event::next) {
|
if (Event::handle('GSVerbToActivityStreamsTwoActivityType', [($verb = $object->getVerb()), &$gs_verb_to_activity_streams_two_verb]) === Event::next) {
|
||||||
$gs_verb_to_activity_stream_two_verb = match ($verb) {
|
$gs_verb_to_activity_streams_two_verb = match ($verb) {
|
||||||
'create' => 'Create',
|
'undo' => 'Undo',
|
||||||
'undo' => 'Undo',
|
'create' => 'Create',
|
||||||
default => throw new ClientException('Invalid verb'),
|
'subscribe' => 'Follow',
|
||||||
|
default => throw new ClientException('Invalid verb'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
$attr = [
|
$attr = [
|
||||||
'type' => $gs_verb_to_activity_stream_two_verb,
|
'type' => $gs_verb_to_activity_streams_two_verb,
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
'id' => Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL),
|
'id' => Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL),
|
||||||
'published' => $object->getCreated()->format(DateTimeInterface::RFC3339),
|
'published' => $object->getCreated()->format(DateTimeInterface::RFC3339),
|
||||||
'actor' => $object->getActor()->getUri(Router::ABSOLUTE_URL),
|
'actor' => $object->getActor()->getUri(Router::ABSOLUTE_URL),
|
||||||
'to' => ['https://www.w3.org/ns/activitystreams#Public'], // TODO: implement proper scope address
|
|
||||||
'cc' => ['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Get object or Tombstone
|
||||||
try {
|
try {
|
||||||
$object = $object->getObject(); // Throws NotFoundException
|
$object = $object->getObject(); // Throws NotFoundException
|
||||||
$attr['object'] = ($attr['type'] === 'Create') ? self::jsonToType(Model::toJson($object)) : ActivityPub::getUriByObject($object);
|
$attr['object'] = ($attr['type'] === 'Create') ? self::jsonToType(Model::toJson($object)) : ActivityPub::getUriByObject($object);
|
||||||
|
@ -181,9 +157,13 @@ class Activity extends Model
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\is_string($attr['object'])) {
|
// If embedded non tombstone Object
|
||||||
$attr['to'] = array_unique(array_merge($attr['to'], $attr['object']->get('to') ?? []));
|
if (!\is_string($attr['object']) && $attr['object']->get('type') !== 'Tombstone') {
|
||||||
$attr['cc'] = array_unique(array_merge($attr['cc'], $attr['object']->get('cc') ?? []));
|
// Little special case
|
||||||
|
if ($attr['type'] === 'Create' && $attr['object']->get('type') === 'Note') {
|
||||||
|
$attr['to'] = $attr['object']->get('to') ?? [];
|
||||||
|
$attr['cc'] = $attr['object']->get('cc') ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = self::jsonToType($attr);
|
$type = self::jsonToType($attr);
|
||||||
|
|
84
plugins/ActivityPub/Util/Model/ActivityCreate.php
Normal file
84
plugins/ActivityPub/Util/Model/ActivityCreate.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
// {{{ License
|
||||||
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
|
//
|
||||||
|
// GNU social is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// GNU social is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ActivityPub implementation for GNU social
|
||||||
|
*
|
||||||
|
* @package GNUsocial
|
||||||
|
* @category ActivityPub
|
||||||
|
*
|
||||||
|
* @author Diogo Peralta Cordeiro <@diogo.site>
|
||||||
|
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Plugin\ActivityPub\Util\Model;
|
||||||
|
|
||||||
|
use _PHPStan_76800bfb5\Nette\NotImplementedException;
|
||||||
|
use ActivityPhp\Type\AbstractObject;
|
||||||
|
use App\Core\DB\DB;
|
||||||
|
use App\Entity\Activity as GSActivity;
|
||||||
|
use DateTime;
|
||||||
|
use Plugin\ActivityPub\ActivityPub;
|
||||||
|
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles translation between JSON and ActivityPub Activities
|
||||||
|
*
|
||||||
|
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
|
*/
|
||||||
|
class ActivityCreate extends Activity
|
||||||
|
{
|
||||||
|
protected static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
|
||||||
|
{
|
||||||
|
if ($type_object instanceof AbstractObject) {
|
||||||
|
if ($type_object->get('type') === 'Note') {
|
||||||
|
$note = Note::fromJson($type_object, ['test_authority' => true, 'actor_uri' => $type_activity->get('actor'), 'actor' => $actor, 'actor_id' => $actor->getId()]);
|
||||||
|
} else {
|
||||||
|
throw new NotImplementedException('ActivityPub plugin can only handle Create with objects of type Note.');
|
||||||
|
}
|
||||||
|
} elseif ($type_object instanceof \App\Entity\Note) {
|
||||||
|
$note = $type_object;
|
||||||
|
} else {
|
||||||
|
throw new \http\Exception\InvalidArgumentException('Create{:Object} should be either an AbstractObject or a Note.');
|
||||||
|
}
|
||||||
|
// Store Activity
|
||||||
|
$act = GSActivity::create([
|
||||||
|
'actor_id' => $actor->getId(),
|
||||||
|
'verb' => 'create',
|
||||||
|
'object_type' => 'note',
|
||||||
|
'object_id' => $note->getId(),
|
||||||
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
|
'source' => 'ActivityPub',
|
||||||
|
]);
|
||||||
|
DB::persist($act);
|
||||||
|
// Store ActivityPub Activity
|
||||||
|
$ap_act = ActivitypubActivity::create([
|
||||||
|
'activity_id' => $act->getId(),
|
||||||
|
'activity_uri' => $type_activity->get('id'),
|
||||||
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
|
'modified' => new DateTime(),
|
||||||
|
]);
|
||||||
|
DB::persist($ap_act);
|
||||||
|
return $ap_act;
|
||||||
|
}
|
||||||
|
}
|
86
plugins/ActivityPub/Util/Model/ActivityFollow.php
Normal file
86
plugins/ActivityPub/Util/Model/ActivityFollow.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
// {{{ License
|
||||||
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
|
//
|
||||||
|
// GNU social is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// GNU social is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ActivityPub implementation for GNU social
|
||||||
|
*
|
||||||
|
* @package GNUsocial
|
||||||
|
* @category ActivityPub
|
||||||
|
*
|
||||||
|
* @author Diogo Peralta Cordeiro <@diogo.site>
|
||||||
|
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Plugin\ActivityPub\Util\Model;
|
||||||
|
|
||||||
|
use ActivityPhp\Type\AbstractObject;
|
||||||
|
use App\Core\DB\DB;
|
||||||
|
use App\Entity\Activity as GSActivity;
|
||||||
|
use Component\Subscription\Entity\Subscription;
|
||||||
|
use DateTime;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles translation between JSON and ActivityPub Activities
|
||||||
|
*
|
||||||
|
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
|
*/
|
||||||
|
class ActivityFollow extends Activity
|
||||||
|
{
|
||||||
|
protected static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
|
||||||
|
{
|
||||||
|
if ($type_object instanceof AbstractObject) {
|
||||||
|
$subscribed = Actor::fromJson($type_object);
|
||||||
|
} elseif ($type_object instanceof \App\Entity\Actor) {
|
||||||
|
$subscribed = $type_object;
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException('Follow{:Object} should be either an AbstractObject or an Actor.');
|
||||||
|
}
|
||||||
|
// Store Subscription
|
||||||
|
DB::persist(Subscription::create([
|
||||||
|
'subscriber_id' => $actor->getId(),
|
||||||
|
'subscribed_id' => $subscribed->getActorId(),
|
||||||
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
|
]));
|
||||||
|
// Store Activity
|
||||||
|
$act = GSActivity::create([
|
||||||
|
'actor_id' => $actor->getId(),
|
||||||
|
'verb' => 'subscribe',
|
||||||
|
'object_type' => 'actor',
|
||||||
|
'object_id' => $subscribed->getActorId(),
|
||||||
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
|
'source' => 'ActivityPub',
|
||||||
|
]);
|
||||||
|
DB::persist($act);
|
||||||
|
// Store ActivityPub Activity
|
||||||
|
$ap_act = ActivitypubActivity::create([
|
||||||
|
'activity_id' => $act->getId(),
|
||||||
|
'activity_uri' => $type_activity->get('id'),
|
||||||
|
'created' => new DateTime($type_activity->get('published') ?? 'now'),
|
||||||
|
'modified' => new DateTime(),
|
||||||
|
]);
|
||||||
|
DB::persist($ap_act);
|
||||||
|
return $ap_act;
|
||||||
|
}
|
||||||
|
}
|
|
@ -177,7 +177,7 @@ class Actor extends Model
|
||||||
public static function toJson(mixed $object, ?int $options = null): string
|
public static function toJson(mixed $object, ?int $options = null): string
|
||||||
{
|
{
|
||||||
if ($object::class !== GSActor::class) {
|
if ($object::class !== GSActor::class) {
|
||||||
throw new InvalidArgumentException('First argument type is Actor');
|
throw new InvalidArgumentException('First argument type must be an Actor.');
|
||||||
}
|
}
|
||||||
$rsa = ActivitypubRsa::getByActor($object);
|
$rsa = ActivitypubRsa::getByActor($object);
|
||||||
$public_key = $rsa->getPublicKey();
|
$public_key = $rsa->getPublicKey();
|
||||||
|
|
|
@ -308,7 +308,7 @@ class Note extends Model
|
||||||
public static function toJson(mixed $object, ?int $options = null): string
|
public static function toJson(mixed $object, ?int $options = null): string
|
||||||
{
|
{
|
||||||
if ($object::class !== GSNote::class) {
|
if ($object::class !== GSNote::class) {
|
||||||
throw new InvalidArgumentException('First argument type is Note');
|
throw new InvalidArgumentException('First argument type must be a Note.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$attr = [
|
$attr = [
|
||||||
|
|
|
@ -121,7 +121,7 @@ class Directory extends FeedController
|
||||||
},
|
},
|
||||||
|
|
||||||
'subscribers' => match ($actor_type) { // select by actors with most/least subscribers/members
|
'subscribers' => match ($actor_type) { // select by actors with most/least subscribers/members
|
||||||
Actor::PERSON => $count_query_fn(table: 'subscription', join_field: 'subscribed', aggregate_field: 'subscriber'),
|
Actor::PERSON => $count_query_fn(table: 'subscription', join_field: 'subscribed_id', aggregate_field: 'subscriber_id'),
|
||||||
Actor::GROUP => $count_query_fn(table: 'group_member', join_field: 'group_id', aggregate_field: 'actor_id'),
|
Actor::GROUP => $count_query_fn(table: 'group_member', join_field: 'group_id', aggregate_field: 'actor_id'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ use App\Core\UserRoles;
|
||||||
use App\Entity\Actor;
|
use App\Entity\Actor;
|
||||||
use App\Entity\Feed;
|
use App\Entity\Feed;
|
||||||
use App\Entity\LocalUser;
|
use App\Entity\LocalUser;
|
||||||
use App\Entity\Subscription;
|
|
||||||
use App\Security\Authenticator;
|
use App\Security\Authenticator;
|
||||||
use App\Security\EmailVerifier;
|
use App\Security\EmailVerifier;
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
|
@ -30,6 +29,7 @@ use App\Util\Exception\NotFoundException;
|
||||||
use App\Util\Exception\ServerException;
|
use App\Util\Exception\ServerException;
|
||||||
use App\Util\Form\FormFields;
|
use App\Util\Form\FormFields;
|
||||||
use App\Util\Nickname;
|
use App\Util\Nickname;
|
||||||
|
use Component\Subscription\Entity\Subscription;
|
||||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
@ -164,7 +164,7 @@ class Security extends Controller
|
||||||
$user,
|
$user,
|
||||||
function (int $id) use ($user) {
|
function (int $id) use ($user) {
|
||||||
// Self subscription
|
// Self subscription
|
||||||
DB::persist(Subscription::create(['subscriber' => $id, 'subscribed' => $id]));
|
DB::persist(Subscription::create(['subscriber_id' => $id, 'subscribed_id' => $id]));
|
||||||
Feed::createDefaultFeeds($id, $user);
|
Feed::createDefaultFeeds($id, $user);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -384,12 +384,12 @@ class Actor extends Entity
|
||||||
|
|
||||||
public function getSubscribersCount(): int
|
public function getSubscribersCount(): int
|
||||||
{
|
{
|
||||||
return $this->getSubCount(which: 'subscriber', column: 'subscribed');
|
return $this->getSubCount(which: 'subscriber', column: 'subscribed_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubscribedCount()
|
public function getSubscribedCount()
|
||||||
{
|
{
|
||||||
return $this->getSubCount(which: 'subscribed', column: 'subscriber');
|
return $this->getSubCount(which: 'subscribed', column: 'subscriber_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -411,8 +411,8 @@ class Actor extends Entity
|
||||||
fn () => DB::dql(
|
fn () => DB::dql(
|
||||||
<<<'EOF'
|
<<<'EOF'
|
||||||
select a from actor a where
|
select a from actor a where
|
||||||
a.id in (select fa.subscribed from subscription fa join actor aa with fa.subscribed = aa.id where fa.subscriber = :actor_id and aa.nickname = :nickname) or
|
a.id in (select fa.subscribed_id from subscription fa join actor aa with fa.subscribed = aa.id where fa.subscriber = :actor_id and aa.nickname = :nickname) or
|
||||||
a.id in (select fb.subscriber from subscription fb join actor ab with fb.subscriber = ab.id where fb.subscribed = :actor_id and ab.nickname = :nickname) or
|
a.id in (select fb.subscriber_id from subscription fb join actor ab with fb.subscriber = ab.id where fb.subscribed = :actor_id and ab.nickname = :nickname) or
|
||||||
a.nickname = :nickname
|
a.nickname = :nickname
|
||||||
EOF,
|
EOF,
|
||||||
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
||||||
|
|
|
@ -238,17 +238,17 @@ class Note extends Entity
|
||||||
|
|
||||||
public function getActor(): Actor
|
public function getActor(): Actor
|
||||||
{
|
{
|
||||||
return Actor::getById($this->actor_id);
|
return Actor::getById($this->getActorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActorNickname(): string
|
public function getActorNickname(): string
|
||||||
{
|
{
|
||||||
return Actor::getNicknameById($this->actor_id);
|
return Actor::getNicknameById($this->getActorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActorFullname(): ?string
|
public function getActorFullname(): ?string
|
||||||
{
|
{
|
||||||
return Actor::getFullnameById($this->actor_id);
|
return Actor::getFullnameById($this->getActorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActorAvatarUrl(string $size = 'medium'): string
|
public function getActorAvatarUrl(string $size = 'medium'): string
|
||||||
|
|
Loading…
Reference in New Issue
Block a user