activitypub-1.0.x-dev/src/Services/ActivityPubUtility.php
src/Services/ActivityPubUtility.php
<?php namespace Drupal\activitypub\Services; use ActivityPhp\Server; use Drupal\activitypub\Entity\ActivityPubActivityInterface; use Drupal\activitypub\Entity\ActivityPubActorInterface; use Drupal\activitypub\Event\VisibilityOptionsEvent; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\file\Entity\File; use Drupal\media\MediaInterface; use Drupal\user\UserInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class ActivityPubUtility implements ActivityPubUtilityInterface { Use StringTranslationTrait; /** * The image style storage. * * @var \Drupal\image\ImageStyleStorageInterface */ protected $imageStyleStorage; /** * The actor storage. * * @var \Drupal\activitypub\Entity\Storage\ActivityPubActorStorageInterface */ protected $actorStorage; /** * The activitypub storage. * * @var \Drupal\activitypub\Entity\Storage\ActivityPubActivityStorageInterface */ protected $activityStorage; /** * The request stack * * @var \Symfony\Component\HttpFoundation\RequestStack $requestStack */ protected $requestStack; /** * The config factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $configFactory; /** * The file url generator. * * @var \Drupal\Core\File\FileUrlGeneratorInterface */ protected $fileUrlGenerator; /** * The event dispatcher. * * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface */ protected $eventDispatcher; /** * The ActivityPub signature service. * * @var \Drupal\activitypub\Services\ActivityPubSignatureInterface */ protected $activityPubSignature; /** * ActivityPubUtility constructor * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\activitypub\Services\ActivityPubSignatureInterface $activitypub_signature * The ActivityPub Signature service. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * The request stack. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator * The file url generator. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher service. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function __construct(EntityTypeManagerInterface $entity_type_manager, ActivityPubSignatureInterface $activitypub_signature, RequestStack $request_stack, ConfigFactoryInterface $config_factory, FileUrlGeneratorInterface $file_url_generator, EventDispatcherInterface $event_dispatcher) { $this->imageStyleStorage = $entity_type_manager->getStorage('image_style'); $this->actorStorage = $entity_type_manager->getStorage('activitypub_actor'); $this->activityStorage = $entity_type_manager->getStorage('activitypub_activity'); $this->activityPubSignature = $activitypub_signature; $this->requestStack = $request_stack; $this->configFactory = $config_factory; $this->fileUrlGenerator = $file_url_generator; $this->eventDispatcher = $event_dispatcher; } /** * {@inheritdoc} */ public function getActivityPubName(ActivityPubActorInterface $actor) { return $actor->getName() . '@' . $this->requestStack->getCurrentRequest()->getHost(); } /** * {@inheritdoc} */ public function getActivityPubID(ActivityPubActorInterface $actor) { return $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost() . $this->requestStack->getCurrentRequest()->getBasePath() . '/user/' . $actor->getOwnerId() . '/activitypub/' . $actor->getName(); } /** * {@inheritdoc} */ public function getActivityPubUserImage(UserInterface $user, string $type = 'avatar') { $image_url = NULL; $config = $this->configFactory->get('activitypub.settings'); $image_user_field = $config->get($type . '_user_field'); /** @var \Drupal\file\FileInterface $image */ if ($image = $this->getUserImage($user, $image_user_field)) { /** @var \Drupal\image\ImageStyleInterface $image_style */ $image_style = $this->imageStyleStorage->load($config->get($type . '_user_style')); $image_url = $image_style->buildUrl($image->getFileUri()); } elseif ($type == 'avatar') { $avatar_path = $config->get('avatar_default_path'); if ($avatar_path == 'assets/avatar.png') { $avatar_path = \Drupal::service('extension.list.module')->getPath('activitypub') . '/' . $avatar_path; } $image_url = $this->fileUrlGenerator->generateAbsoluteString($avatar_path); } return $image_url; } /** * Get the file entity for a given user field which can be a file or media * field. * * @param \Drupal\user\UserInterface $user * The user. * @param string $image_user_field * The field name. * * @return \Drupal\file\Entity\File|null */ private function getUserImage(UserInterface $user, string $image_user_field): ?File { $image = NULL; if (!empty($image_user_field) && $user->hasField($image_user_field) && ($image = $user->get($image_user_field)->entity)) { if ($image instanceof MediaInterface) { $fid = $image->getSource()->getSourceFieldValue($image); $image = File::load($fid); } } return $image; } /** * {@inheritdoc} */ public function getServer(array $config = []) { $config += [ 'cache' => [ 'ttl' => activitypub_cache_ttl(), 'stream' => activitypub_cache_path() ], 'logger' => [ 'driver' => '\Psr\Log\NullLogger' ], ]; // Override for tests. if ($test_prefix = drupal_valid_test_ua()) { $config['cache'] = ['enabled' => FALSE]; $config['http']['agent'] = drupal_generate_test_ua($test_prefix); } $config['instance']['types'] = 'ignore'; return new Server($config); } /** * {@inheritdoc} */ public function alterNodeInfo(array &$data) { $data['protocols'] = ['activitypub']; $data['usage']['users']['total'] = $this->actorStorage->getActorCount(['status' => 1]); $data['usage']['localPosts'] = $this->activityStorage->getActivityCount(['collection' => ['outbox', 'liked'], 'status' => 1, 'visibility' => ActivityPubActivityInterface::VISIBILITY_PUBLIC]); } /** * {@inheritdoc} */ public function onEntityDelete(EntityInterface $entity) { if ($entity instanceof ContentEntityInterface && !($entity instanceof ActivityPubActivityInterface)) { if ($entity instanceof UserInterface) { $actors = $this->actorStorage->loadByProperties(['uid' => $entity->id()]); $this->actorStorage->delete($actors); } elseif ($entity instanceof ActivityPubActorInterface) { $this->activityPubSignature->deleteKeys($entity->getName()); $activities = $this->activityStorage->loadByProperties(['uid' => $entity->getOwnerId()]); $this->activityStorage->delete($activities); } elseif (method_exists($entity, 'getOwnerId')) { // Find a single activity for this entity. $conditions = [ 'uid' => $entity->getOwnerId() , 'entity_id' => $entity->id(), 'entity_type_id' => $entity->getEntityTypeId(), 'collection' => ActivityPubActivityInterface::OUTBOX ]; $activities = $this->activityStorage->loadByProperties($conditions); if (count($activities) == 1) { /** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity */ $activity = array_shift($activities); $activity->onEntityDelete($entity); $activity->delete(); } } } } /** * {@inheritdoc} */ public function getVisibilityOptions() { $options = [ ActivityPubActivityInterface::VISIBILITY_PUBLIC => $this->t('Public'), ActivityPubActivityInterface::VISIBILITY_FOLLOWERS => $this->t('Followers'), ActivityPubActivityInterface::VISIBILITY_UNLISTED => $this->t('Unlisted'), ActivityPubActivityInterface::VISIBILITY_PRIVATE => $this->t('Private'), ]; $event = new VisibilityOptionsEvent($options); $this->eventDispatcher->dispatch($event, VisibilityOptionsEvent::ADD_OPTIONS); return $event->getVisibilityOptions(); } /** * {@inheritdoc} */ public function getOutboxIgnoreTypes() { return ['Follow', 'Undo', 'Delete']; } /** * {@inheritdoc} */ public function getTimelineTypes() { return ['Create', 'Like', 'Announce']; } /** * {@inheritdoc} */ public function getNotificationTypes() { return ['Like', 'Announce', 'Follow', 'Create']; } }