activitypub-1.0.x-dev/src/Services/Type/TypePluginBase.php
src/Services/Type/TypePluginBase.php
<?php
namespace Drupal\activitypub\Services\Type;
use Drupal\activitypub\Entity\ActivityPubActivity;
use Drupal\activitypub\Entity\ActivityPubActivityInterface;
use Drupal\activitypub\Entity\ActivityPubTypeInterface;
use Drupal\activitypub\Event\ActivityAudienceEvent;
use Drupal\activitypub\Services\ActivityPubProcessClientInterface;
use Drupal\activitypub\Services\ActivityPubUtilityInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* A base class for ActivityPub types that implements most of the boilerplate.
*/
abstract class TypePluginBase extends PluginBase implements TypePluginInterface, ContainerFactoryPluginInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The date formatter.
*
* @var \Drupal\Core\DateTime\DateFormatInterface
*/
protected $dateFormatter;
/**
* The ActivityPub process client.
*
* @var \Drupal\activitypub\Services\ActivityPubProcessClient
*/
protected $activityPubProcessClient;
/**
* The ActivityPub utility service.
*
* @var \Drupal\activitypub\Services\ActivityPubUtilityInterface
*/
protected $activityPubUtility;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The file url generator.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* The event dispatcher.
*
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, ActivityPubProcessClientInterface $activitypub_process_client, ActivityPubUtilityInterface $activitypub_utility, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, LoggerInterface $logger, FileUrlGeneratorInterface $file_url_generator, EventDispatcherInterface $event_dispatcher) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setConfiguration($configuration);
$this->entityTypeManager = $entity_type_manager;
$this->renderer = $renderer;
$this->dateFormatter = $date_formatter;
$this->activityPubProcessClient = $activitypub_process_client;
$this->activityPubUtility = $activitypub_utility;
$this->entityFieldManager = $entity_field_manager;
$this->configFactory = $config_factory;
$this->logger = $logger;
$this->fileUrlGenerator = $file_url_generator;
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('renderer'),
$container->get('date.formatter'),
$container->get('activitypub.process.client'),
$container->get('activitypub.utility'),
$container->get('entity_field.manager'),
$container->get('config.factory'),
$container->get('logger.channel.activitypub'),
$container->get('file_url_generator'),
$container->get('event_dispatcher')
);
}
/**
* {@inheritdoc}
*/
public function isExposed() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function build(ActivityPubActivityInterface $activity, EntityInterface $entity = NULL) {
return [];
}
/**
* {@inheritdoc}
*/
public function getActivities() {
return [];
}
/**
* {@inheritdoc}
*/
public function getObjects() {
return [];
}
/**
* {@inheritdoc}
*/
public function getProperties($object) {
return [];
}
/**
* {@inheritdoc}
*/
public function onActivityInboxPreSave(ActivityPubActivityInterface $activity, &$doSave) {}
/**
* {@inheritdoc}
*/
public function onActivityOutboxPreSave(ActivityPubActivityInterface $activity, EntityInterface $entity, ActivityPubTypeInterface $activityPubType, &$doSave) {}
/**
* {@inheritdoc}
*/
public function onActivityPostSave(ActivityPubActivityInterface $activity, $update = TRUE) {}
/**
* {@inheritdoc}
*/
public function doInboxProcess(ActivityPubActivityInterface $activity, EntityInterface $entity = NULL) {}
/**
* {@inheritdoc}
*/
public function onEntityDelete(ActivityPubActivityInterface $activity, EntityInterface $entity) {}
/**
* {@inheritdoc}
*/
public function onActivityDelete(ActivityPubActivityInterface $activity) {}
/**
* Get value for a property.
*
* @param $property
* @param $value
* @param $field_type
*
* @return array|mixed|null
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getValue($property, $value, $field_type = NULL) {
$return = NULL;
switch ($property) {
case 'published':
$date_timestamp = $value[0]['value'];
if (!is_numeric($date_timestamp)) {
$date_timestamp = strtotime($value[0]['value']);
}
$return = $this->dateFormatter->format($date_timestamp, 'custom', 'Y-m-d\TH:i:s\Z');
break;
case 'name':
$return = trim($value[0]['value']);
break;
case 'content':
case 'summary':
$return = trim(check_markup($value[0]['value'], $this->configFactory->get('activitypub.settings')->get('filter_format')));
break;
case 'object':
case 'inReplyTo':
// This can either be a link field or just a text field.
if (!empty($value[0]['uri'])) {
$return = $value[0]['uri'];
}
if (!empty($value[0]['value'])) {
$return = $value[0]['value'];
}
break;
case 'attachment':
$return = [];
foreach ($value as $v) {
$file = NULL;
// Check if field_type is coming from media entity reference field
// type.
if ($field_type == 'entity_reference') {
/** @var \Drupal\media\MediaInterface $media */
$media = $this->entityTypeManager->getStorage('media')->load($v['target_id']);
$fid = $media->getSource()->getSourceFieldValue($media);
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager->getStorage('file')->load($fid);
}
else {
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager->getStorage('file')->load($v['target_id']);
}
if ($file instanceof FileInterface) {
$type = $this->getMediaType($file);
$return[] = (object) [
'type' => $type,
'mediaType' => $file->getMimeType(),
'url' => $this->getFileUrl($file, $type),
];
}
}
break;
}
return $return;
}
/**
* Get the media type.
*
* @param \Drupal\file\FileInterface $file
*
* @return string
*/
protected function getMediaType(FileInterface $file) {
$type = 'Document';
switch ($file->getMimeType()) {
case 'image/jpg':
case 'image/jpeg':
case 'image/webp':
case 'image/png':
case 'image/gif':
$type = 'Image';
break;
case 'video/mpeg':
case 'video/mp4':
case 'video/quicktime':
case 'video/ogg':
$type = 'Video';
break;
case 'audio/ogg':
case 'audio/mp3':
$type = 'Audio';
break;
}
return $type;
}
/**
* Gets the file URL.
*
* @param \Drupal\file\FileInterface $file
* @param string $type
*
* @return string
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getFileUrl(FileInterface $file, string $type) {
$config = $this->configFactory->get('activitypub.settings');
$attachment_content_style = $config->get('attachment_content_style');
if (!empty($attachment_content_style) && $type == 'Image') {
/** @var \Drupal\image\ImageStyleInterface $image_style */
$storage = $this->entityTypeManager->getStorage('image_style');
$image_style = $storage->load($attachment_content_style);
return $image_style->buildUrl($file->getFileUri());
}
else {
return $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
}
}
/**
* Render a URL from an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
*
* @return string
*/
protected function renderEntityUrl(EntityInterface $entity) {
return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($entity) {
return $entity->toUrl('canonical', ['absolute' => TRUE])->toString();
});
}
/**
* Render a Url.
*
* @param \Drupal\Core\Url $url
*
* @return string
*/
protected function renderUrl(Url $url) {
return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($url) {
return $url->toString();
});
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'activity' => '',
];
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = NestedArray::mergeDeep(
$this->defaultConfiguration(),
$configuration
);
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$entity_type_id = $this->getConfiguration()['target_entity_type_id'];
$entity_types = ['node' => $this->t('Content')];
if (\Drupal::moduleHandler()->moduleExists('comment')) {
$entity_types['comment'] = $this->t('Comment');
}
$form['target_entity_type_id'] = [
'#type' => 'select',
'#title' => $this->t('Entity type'),
'#options' => $entity_types,
'#default_value' => $entity_type_id,
'#ajax' => [
'trigger_as' => ['name' => 'type_configure'],
'callback' => '::buildAjaxTypeForm',
'wrapper' => 'type-config-form',
'method' => 'replace',
'effect' => 'fade',
],
];
if ($entity_type_id == 'comment') {
$bundle_options = activitypub_comment_get_names();
}
else {
$bundle_options = node_type_get_names();
}
$form['target_bundle'] = [
'#type' => 'select',
'#title' => $this->t('Type'),
'#options' => ['' => $this->t('- Select -')] + $bundle_options,
'#default_value' => $this->getConfiguration()['target_bundle'],
'#ajax' => [
'trigger_as' => ['name' => 'type_configure'],
'callback' => '::buildAjaxTypeForm',
'wrapper' => 'type-config-form',
'method' => 'replace',
'effect' => 'fade',
],
];
$activitypub_activity_options = ['' => $this->t('- Select -')];
$activitypub_object_options = ['' => $this->t('- Select -')];
foreach ($this->getActivities() as $a) {
$activitypub_activity_options[$a] = $a;
}
foreach ($this->getObjects() as $o) {
$option_id = $o;
$activitypub_object_options[$option_id] = $o;
}
$form['activity'] = [
'#type' => 'select',
'#title' => $this->t('ActivityPub activity'),
'#default_value' => $this->getConfiguration()['activity'],
'#options' => $activitypub_activity_options,
];
$form['object'] = [
'#type' => 'select',
'#title' => $this->t('ActivityPub object'),
'#default_value' =>$this->getConfiguration()['object'],
'#options' => $activitypub_object_options,
];
return $form;
}
/**
* Gets an entity from an url.
*
* @param $url string
* The string url to obtain an entity.
*
* @return \Drupal\Core\Entity\EntityInterface $entity|NULL
* Returns an entity or NULL.
*/
public static function getEntityFromUrl(string $url) {
$host = \Drupal::request()->getSchemeAndHttpHost();
try {
if (UrlHelper::externalIsLocal($url, $host)) {
$path = str_replace($host . base_path(), '', $url);
/** @var \Drupal\Core\Path\PathValidatorInterface $validator */
$validator = \Drupal::service('path.validator');
$url_object = $validator->getUrlIfValidWithoutAccessCheck($path);
if ($url_object && in_array($url_object->getRouteName(), ["entity.node.canonical", "activitypub.user.self", "entity.comment.canonical"])) {
$entity_type_id = $entity_id = NULL;
switch ($url_object->getRouteName()) {
case 'entity.node.canonical':
$entity_type_id = 'node';
$entity_id = $url_object->getRouteParameters()['node'];
break;
case 'entity.comment.canonical':
$entity_type_id = 'comment';
$entity_id = $url_object->getRouteParameters()['comment'];
break;
case 'activitypub.user.self':
$entity_type_id = 'user';
$entity_id = $url_object->getRouteParameters()['user'];
break;
}
if ($entity_type_id && $entity_id) {
$entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
if ($entity) {
return $entity;
}
}
}
}
}
catch (\Exception $ignored) {}
return NULL;
}
/**
* Build audience for an activity.
*
* @param $to
* @param $cc
* @param $mention
* @param \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function buildAudience(&$to, &$cc, &$mention, ActivityPubActivityInterface $activity) {
if ($activity->isUnlisted()) {
return;
}
$to = $activity->getTo();
if ($activity->isPublic()) {
array_unshift($to, ActivityPubActivityInterface::PUBLIC_URL);
}
if ($activity->isPublic() || $activity->isFollowers()) {
/** @var \Drupal\activitypub\Entity\ActivityPubActorInterface $actor */
$actor = $this->entityTypeManager->getStorage('activitypub_actor')->loadActorByEntityIdAndType($activity->getOwnerId(), 'person');
$followers_url = $this->renderUrl(Url::fromRoute('activitypub.followers', ['user' => $actor->getOwnerId(), 'activitypub_actor' => $actor->getName()], ['absolute' => TRUE]));
if ($activity->isPublic()) {
$cc = [$followers_url];
}
else {
$to = [$followers_url];
}
}
if ($activity->isPrivate()) {
if (!empty($to[0])) {
$href = $to[0];
$parsed = parse_url($href);
if (!empty($parsed['host']) && !empty($parsed['path'])) {
// A bit naive, but let's fix this later.
$ex = explode('/', $parsed['path']);
$username = array_pop($ex);
$name = '@' . $username . '@' . $parsed['host'];
}
else {
$name = $href;
}
$mention = [
'type' => 'Mention',
'href' => $href,
'name' => $name,
];
}
}
$audience = ['to' => $to, 'cc' => $cc, 'mention' => $mention];
$event = new ActivityAudienceEvent($activity, $audience);
$this->eventDispatcher->dispatch($event, ActivityAudienceEvent::ALTER_AUDIENCE);
$audience = $event->getAudience();
$to = $audience['to'];
$cc = $audience['cc'];
$mention = $audience['mention'];
}
}
