| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  | declare(strict_types = 1); | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // {{{ 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 | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |  * @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\Core\Event; | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  | use App\Core\GSFile; | 
					
						
							|  |  |  | use App\Core\HTTPClient; | 
					
						
							|  |  |  | use App\Core\Log; | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | use App\Core\Router\Router; | 
					
						
							| 
									
										
										
										
											2021-12-19 19:45:38 +00:00
										 |  |  | use App\Core\UserRoles; | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | use App\Entity\Actor as GSActor; | 
					
						
							|  |  |  | use App\Util\Exception\ServerException; | 
					
						
							|  |  |  | use App\Util\Formatting; | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  | use App\Util\TemporaryFile; | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | use Component\Avatar\Avatar; | 
					
						
							|  |  |  | use DateTime; | 
					
						
							|  |  |  | use DateTimeInterface; | 
					
						
							|  |  |  | use Exception; | 
					
						
							|  |  |  | use InvalidArgumentException; | 
					
						
							|  |  |  | use Plugin\ActivityPub\Entity\ActivitypubActor; | 
					
						
							|  |  |  | use Plugin\ActivityPub\Entity\ActivitypubRsa; | 
					
						
							|  |  |  | use Plugin\ActivityPub\Util\Model; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * This class handles translation between JSON and GSActors | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org | 
					
						
							|  |  |  |  * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class Actor extends Model | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create an Entity from an ActivityStreams 2.0 JSON string | 
					
						
							|  |  |  |      * This will persist a new GSActor, ActivityPubRSA, and ActivityPubActor | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |     public static function fromJson(string|AbstractObject $json, array $options = []): ActivitypubActor | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |         $person = \is_string($json) ? self::jsonToType($json) : $json; | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Actor
 | 
					
						
							|  |  |  |         $actor_map = [ | 
					
						
							|  |  |  |             'nickname' => $person->get('preferredUsername'), | 
					
						
							| 
									
										
										
										
											2021-12-12 06:32:17 +00:00
										 |  |  |             'fullname' => !empty($person->get('name')) ? $person->get('name') : null, | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'created'  => new DateTime($person->get('published') ?? 'now'), | 
					
						
							|  |  |  |             'bio'      => $person->get('summary'), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'is_local' => false, | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'type'     => GSActor::PERSON, | 
					
						
							|  |  |  |             'roles'    => UserRoles::USER, | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'modified' => new DateTime(), | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         $actor = $options['objects']['Actor'] ?? new GSActor(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |         foreach ($actor_map as $prop => $val) { | 
					
						
							|  |  |  |             $set = Formatting::snakeCaseToCamelCase("set_{$prop}"); | 
					
						
							|  |  |  |             $actor->{$set}($val); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         if (!isset($options['objects']['Actor'])) { | 
					
						
							|  |  |  |             DB::persist($actor); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // ActivityPub Actor
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         $ap_actor = ActivitypubActor::create([ | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'inbox_uri'        => $person->get('inbox'), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'inbox_shared_uri' => ($person->has('endpoints') && isset($person->get('endpoints')['sharedInbox'])) ? $person->get('endpoints')['sharedInbox'] : null, | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'uri'              => $person->get('id'), | 
					
						
							|  |  |  |             'actor_id'         => $actor->getId(), | 
					
						
							|  |  |  |             'url'              => $person->get('url') ?? null, | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         ], $options['objects']['ActivitypubActor'] ?? null); | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         if (!isset($options['objects']['ActivitypubActor'])) { | 
					
						
							|  |  |  |             DB::persist($ap_actor); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Public Key
 | 
					
						
							|  |  |  |         $apRSA = ActivitypubRsa::create([ | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'actor_id'   => $actor->getID(), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'public_key' => ($person->has('publicKey') && isset($person->get('publicKey')['publicKeyPem'])) ? $person->get('publicKey')['publicKeyPem'] : null, | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         ], $options['objects']['ActivitypubRsa'] ?? null); | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         if (!isset($options['objects']['ActivitypubRsa'])) { | 
					
						
							|  |  |  |             DB::persist($apRSA); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Avatar
 | 
					
						
							| 
									
										
										
										
											2021-12-08 22:24:52 +00:00
										 |  |  |         if ($person->has('icon') && !empty($person->get('icon'))) { | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 // Retrieve media
 | 
					
						
							|  |  |  |                 $get_response = HTTPClient::get($person->get('icon')->get('url')); | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |                 $media        = $get_response->getContent(); | 
					
						
							|  |  |  |                 $mimetype     = $get_response->getHeaders()['content-type'][0] ?? null; | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |                 unset($get_response); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Only handle if it is an image
 | 
					
						
							|  |  |  |                 if (GSFile::mimetypeMajor($mimetype) === 'image') { | 
					
						
							|  |  |  |                     // Ignore empty files
 | 
					
						
							|  |  |  |                     if (!empty($media)) { | 
					
						
							|  |  |  |                         // Create an attachment for this
 | 
					
						
							|  |  |  |                         $temp_file = new TemporaryFile(); | 
					
						
							|  |  |  |                         $temp_file->write($media); | 
					
						
							|  |  |  |                         $attachment = GSFile::storeFileAsAttachment($temp_file); | 
					
						
							|  |  |  |                         // Delete current avatar if there's one
 | 
					
						
							|  |  |  |                         $avatar = DB::find('avatar', ['actor_id' => $actor->getId()]); | 
					
						
							|  |  |  |                         $avatar?->delete(); | 
					
						
							| 
									
										
										
										
											2021-12-08 22:24:52 +00:00
										 |  |  |                         DB::wrapInTransaction(function () use ($attachment, $actor) { | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |                             DB::persist($attachment); | 
					
						
							|  |  |  |                             DB::persist(\Component\Avatar\Entity\Avatar::create(['actor_id' => $actor->getId(), 'attachment_id' => $attachment->getId()])); | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         Event::handle('AvatarUpdate', [$actor->getId()]); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } catch (Exception $e) { | 
					
						
							|  |  |  |                 // Let the exception go, it isn't a serious issue
 | 
					
						
							|  |  |  |                 Log::warning('ActivityPub Explorer: An error occurred while grabbing remote avatar: ' . $e->getMessage()); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             // Delete existing avatar if any
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 $avatar = DB::findOneBy('avatar', ['actor_id' => $actor->getId()]); | 
					
						
							|  |  |  |                 $avatar->delete(); | 
					
						
							|  |  |  |                 Event::handle('AvatarUpdate', [$actor->getId()]); | 
					
						
							|  |  |  |             } catch (Exception) { | 
					
						
							| 
									
										
										
										
											2021-12-08 22:24:52 +00:00
										 |  |  |                 // No avatar set, so cannot delete
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-05 03:11:08 +00:00
										 |  |  |         return $ap_actor; | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get a JSON | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |      * @param null|int $options PHP JSON options | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |      * @throws ServerException | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function toJson(mixed $object, ?int $options = null): string | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-12-28 16:07:35 +00:00
										 |  |  |         if ($object::class !== GSActor::class) { | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             throw new InvalidArgumentException('First argument type is Actor'); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |         $rsa        = ActivitypubRsa::getByActor($object); | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |         $public_key = $rsa->getPublicKey(); | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |         $uri        = null; | 
					
						
							|  |  |  |         $attr       = [ | 
					
						
							|  |  |  |             '@context'  => 'https://www.w3.org/ns/activitystreams', | 
					
						
							|  |  |  |             'type'      => 'Person', | 
					
						
							|  |  |  |             'id'        => $object->getUri(Router::ABSOLUTE_URL), | 
					
						
							|  |  |  |             'inbox'     => Router::url('activitypub_actor_inbox', ['gsactor_id' => $object->getId()], Router::ABSOLUTE_URL), | 
					
						
							|  |  |  |             'outbox'    => Router::url('activitypub_actor_outbox', ['gsactor_id' => $object->getId()], Router::ABSOLUTE_URL), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'following' => Router::url('actor_subscriptions_id', ['id' => $object->getId()], Router::ABSOLUTE_URL), | 
					
						
							|  |  |  |             'followers' => Router::url('actor_subscribers_id', ['id' => $object->getId()], Router::ABSOLUTE_URL), | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'liked'     => Router::url('favourites_view_by_actor_id', ['id' => $object->getId()], Router::ABSOLUTE_URL), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             //'streams' =>
 | 
					
						
							|  |  |  |             'preferredUsername' => $object->getNickname(), | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'publicKey'         => [ | 
					
						
							|  |  |  |                 'id'           => $uri . '#public-key', | 
					
						
							|  |  |  |                 'owner'        => $uri, | 
					
						
							|  |  |  |                 'publicKeyPem' => $public_key, | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             ], | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'name'      => $object->getFullname(), | 
					
						
							|  |  |  |             'location'  => $object->getLocation(), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             'published' => $object->getCreated()->format(DateTimeInterface::RFC3339), | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'summary'   => $object->getBio(), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             //'tag' => $object->getSelfTags(),
 | 
					
						
							|  |  |  |             'updated' => $object->getModified()->format(DateTimeInterface::RFC3339), | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             'url'     => $object->getUrl(Router::ABSOLUTE_URL), | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |         ]; | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Avatar
 | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |             $avatar       = Avatar::getAvatar($object->getId()); | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |             $attr['icon'] = $attr['image'] = [ | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |                 'type'      => 'Image', | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |                 'mediaType' => $avatar->getAttachment()->getMimetype(), | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  |                 'url'       => $avatar->getUrl(type: Router::ABSOLUTE_URL), | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |             ]; | 
					
						
							|  |  |  |         } catch (Exception) { | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |             // No icon for this actor
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $type = self::jsonToType($attr); | 
					
						
							| 
									
										
										
										
											2021-12-05 03:36:37 +00:00
										 |  |  |         Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]); | 
					
						
							| 
									
										
										
										
											2021-12-04 04:07:08 +00:00
										 |  |  |         return $type->toJson($options); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-12-26 09:48:16 +00:00
										 |  |  | } |