activitypub-1.0.x-dev/src/Services/ActivityPubFormAlter.php
src/Services/ActivityPubFormAlter.php
<?php namespace Drupal\activitypub\Services; use Drupal\Core\Cache\Cache; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; use Drupal\user\EntityOwnerInterface; class ActivityPubFormAlter implements ActivityPubFormAlterInterface { use StringTranslationTrait; use DependencySerializationTrait { __sleep as traitSleep; } /** * The ActivityPub Actor storage. * * @var \Drupal\activitypub\Entity\Storage\ActivityPubActorStorageInterface */ protected $actorStorage; /** * The ActivityPub type storage. * * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface */ protected $typeStorage; /** * The ActivityPub Activity storage. * * @var \Drupal\activitypub\Entity\Storage\ActivityPubActivityStorageInterface */ protected $activityStorage; /** * The ActivityPub utility service. * * @var \Drupal\activitypub\Services\ActivityPubUtilityInterface */ protected $activityPubUtility; /** * The ActivityPub signature service. * * @var \Drupal\activitypub\Services\ActivityPubSignatureInterface */ protected $activityPubSignature; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ protected $currentUser; /** * The messenger. * * @var \Drupal\Core\Messenger\MessengerInterface */ protected $messenger; /** * The outbox service. * * @var \Drupal\activitypub\Services\ActivityPubOutboxInterface */ protected $outbox; /** * ActivityPubFormAlter constructor * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\activitypub\Services\ActivityPubUtilityInterface $activitypub_utility * The ActivityPub utility service. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function __construct(EntityTypeManagerInterface $entity_type_manager, ActivityPubUtilityInterface $activitypub_utility, ActivityPubSignatureInterface $activitypub_signature, AccountInterface $current_user, MessengerInterface $messenger, ActivityPubOutboxInterface $outbox) { $this->actorStorage = $entity_type_manager->getStorage('activitypub_actor'); $this->typeStorage = $entity_type_manager->getStorage('activitypub_type'); $this->activityStorage = $entity_type_manager->getStorage('activitypub_activity'); $this->activityPubUtility = $activitypub_utility; $this->activityPubSignature = $activitypub_signature; $this->currentUser = $current_user; $this->messenger = $messenger; $this->outbox = $outbox; } /** * {@inheritdoc} */ public function addActivityPubSettingsFormElement(array &$form, FormStateInterface $form_state, EntityInterface $entity, $type) { if (!\Drupal::hasService('stream_wrapper.private')) { $this->messenger->addWarning($this->t('Warning: the private folder has not been configured.')); } /** @var \Drupal\activitypub\Entity\ActivityPubActorInterface $actor */ $actor = $this->actorStorage->loadActorByEntityIdAndType($entity->id(), $type); $form['activitypub'] = [ '#type' => 'container', ]; $form['activitypub']['entity'] = [ '#type' => 'value', '#value' => $entity, ]; if ($actor) { $form['activitypub']['info'] = [ '#markup' => '<p>' . $this->t('ActivityPub is enabled.') . '</p>', ]; $form['activitypub']['name'] = [ '#markup' => '<p>' . $this->t('Your Fediverse identifier for this site is @name. Try entering @name in Mastodon or Pixelfed search.', ['@name' => $this->activityPubUtility->getActivityPubName($actor)]) . '</p>', ]; $form['activitypub']['activitypub_actor'] = [ '#type' => 'value', '#value' => $actor->id(), ]; } else { $form['activitypub']['activitypub_type'] = [ '#type' => 'value', '#value' => $type, ]; $form['activitypub']['activitypub_enable'] = [ '#type' => 'checkbox', '#title' => $this->t('Enable ActivityPub'), ]; $form['activitypub']['activitypub_name'] = [ '#type' => 'textfield', '#title' => $this->t('Name'), '#description' => $this->t('It must only contain letters and numbers.'), '#states' => [ 'visible' => [':input[name="activitypub_enable"]' => ['checked' => TRUE]] ], ]; $form['#validate'][] = [$this, 'validateSettingsForm']; } $form['activitypub']['activitypub_summary'] = [ '#type' => 'textarea', '#title' => $this->t('Bio'), '#description' => $this->t('Add a summary for this profile.'), ]; $form['activitypub']['activitypub_blocked_domains'] = [ '#type' => 'textarea', '#title' => $this->t('Blocked domains'), '#description' => $this->t('Block domains from sending messages to your Inbox. Enter domains line per line. Wildcards are supported.'), ]; if (!$actor) { $form['activitypub']['activitypub_summary']['#states'] = [ 'visible' => [':input[name="activitypub_enable"]' => ['checked' => TRUE]] ]; $form['activitypub']['activitypub_blocked_domains']['#states'] = [ 'visible' => [':input[name="activitypub_enable"]' => ['checked' => TRUE]] ]; } else { $form['activitypub']['activitypub_summary']['#default_value'] = $actor->getSummary(); $form['activitypub']['activitypub_blocked_domains']['#default_value'] = $actor->getBlockedDomains(); } $form['actions'] = [ '#type' => 'actions', ]; $form['actions']['submit'] = [ '#type' => 'submit', '#button_type' => 'primary', '#value' => $this->t('Save'), ]; if ($actor) { $form['actions']['delete'] = [ '#weight' => 20, '#markup' => Link::fromTextAndUrl($this->t('Delete'), Url::fromRoute('activitypub.actor.delete', ['user' => $actor->getOwnerId(), 'activitypub_actor' => $actor->getName()], ['attributes' => ['class' => ['button--danger', 'button']]]))->toString(), ]; } $form['#submit'][] = [$this, 'submitSettingsForm']; } /** * Validate callback for the ActivityPub settings form element. * * @param $form * @param \Drupal\Core\Form\FormStateInterface $form_state */ public function validateSettingsForm($form, FormStateInterface $form_state) { if ($form_state->getValue('activitypub_enable')) { $name = trim($form_state->getValue('activitypub_name')); if (empty($name)) { $form_state->setErrorByName('activitypub_name', $this->t('Please enter your ActivityPub username.')); } elseif (!ctype_alnum($name)) { $form_state->setErrorByName('activitypub_name', $this->t('The username can only contain letters and numbers.')); } elseif ($this->actorStorage->nameExists($name)) { $form_state->setErrorByName('activitypub_name', $this->t('This username is already taken.')); } } } /** * Submit callback for the ActivityPub settings form element. * * @param $form * @param \Drupal\Core\Form\FormStateInterface $form_state * * @throws \Drupal\Core\Entity\EntityStorageException */ public function submitSettingsForm($form, FormStateInterface $form_state) { $entity = $form_state->getValue('entity'); if ($form_state->getValue('activitypub_enable')) { $values = [ 'entity_id' => $entity->id(), 'type' => $form_state->getValue('activitypub_type'), 'name' => trim($form_state->getValue('activitypub_name')), ]; if (!empty($form_state->getValue('activitypub_blocked_domains'))) { $values['blocked_domains'] = $form_state->getValue('activitypub_blocked_domains'); } if (!empty($form_state->getValue('activitypub_summary'))) { $values['summary'] = $form_state->getValue('activitypub_summary'); } if ($entity instanceof EntityOwnerInterface) { $values['uid'] = $entity->getOwnerId(); } /** @var \Drupal\activitypub\Entity\ActivityPubActorInterface $actor */ $actor = $this->actorStorage->create($values); if ($this->activityPubSignature->generateKeys($actor->getName())) { $actor->save(); } else { $this->messenger->addError($this->t('Error while creating keys: This may be caused by a problem with file or directory permissions. Check the logs or ask an administrator.')); } } elseif ($form_state->getValue('activitypub_actor')) { /** @var \Drupal\activitypub\Entity\ActivityPubActorInterface $actor */ $actor = $this->actorStorage->load($form_state->getValue('activitypub_actor')); $actor->set('blocked_domains', $form_state->getValue('activitypub_blocked_domains')); $actor->set('summary', $form_state->getValue('activitypub_summary')); $actor->save(); $this->messenger->addMessage($this->t('Settings saved')); } Cache::invalidateTags([$entity->getEntityTypeId() . ':' . $entity->id()]); } /** * {@inheritdoc} */ public function addActivityPubOutboxFormElement(array &$form, FormStateInterface $form_state, EntityInterface $entity) { $config = \Drupal::configFactory()->getEditable('activitypub.settings'); $actor = $this->actorStorage->loadActorByEntityIdAndType($this->currentUser->id(), 'person'); // Check if a site-wide actor is configured and the current user has the permission to publish. if (!$actor) { $site_wide_uid = $config->get('site_wide_uid'); if ($site_wide_uid && $this->currentUser->hasPermission('publish to site-wide actor')) { $actor = $this->actorStorage->loadActorByEntityIdAndType($site_wide_uid, 'person'); } } if (!$actor) { return; } $create_options = $update_options = []; $outbox_selected = []; /** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface[] $outboxEntities */ if ($entity->id()) { $outboxEntities = $this->activityStorage->loadByProperties(['entity_type_id' => $entity->getEntityTypeId(), 'entity_id' => $entity->id(), 'collection' => 'outbox']); foreach ($outboxEntities as $outboxEntity) { $outbox_selected[$outboxEntity->getConfigID()] = $outboxEntity->id(); } } /** @var \Drupal\activitypub\Entity\ActivityPubTypeInterface[] $typeEntities */ $typeEntities = $this->typeStorage->loadByProperties(['status' => TRUE, 'plugin.configuration.target_entity_type_id' => $entity->getEntityTypeId(), 'plugin.configuration.target_bundle' => $entity->bundle()]); if (!$typeEntities) { return; } foreach ($typeEntities as $typeEntity) { if (!isset($outbox_selected[$typeEntity->id()])) { $create_options[$typeEntity->id()] = $typeEntity->label(); } elseif ($typeEntity->getPlugin()['id'] == 'activitypub_dynamic_types' && $typeEntity->getPlugin()['configuration']['activity'] == 'Create' && in_array($typeEntity->getPlugin()['configuration']['object'], ['Note', 'Article'])) { $update_options[$outbox_selected[$typeEntity->id()]] = $typeEntity->label(); } } $form['activitypub_actor'] = [ '#type' => 'value', '#value' => $actor, ]; $form['activitypub_wrapper'] = [ '#type' => 'details', '#title' => $this->t('ActivityPub outbox'), '#group' => 'advanced', ]; if ($create_options || $update_options) { $form['activitypub_wrapper']['activitypub_create'] = [ '#type' => 'select', '#title' => $this->t('Create'), '#options' => ['' => $this->t('- Select -')] + $create_options, '#access' => !empty($create_options), ]; $form['activitypub_wrapper']['activitypub_update'] = [ '#type' => 'select', '#title' => $this->t('Update'), '#options' => ['' => $this->t('- Select -')] + $update_options, '#access' => !$entity->isNew() && !empty($update_options), ]; $form['activitypub_wrapper']['activitypub_visibility'] = [ '#type' => 'select', '#title' => $this->t('Post privacy'), '#options' => $this->activityPubUtility->getVisibilityOptions(), '#access' => !empty($create_options), '#states' => [ 'visible' => [ ':input[name="activitypub_create"]' => ['!value' => ''], ] ], ]; $form['activitypub_wrapper']['activitypub_to'] = [ '#type' => 'textarea', '#title' => $this->t('Send to'), '#description' => $this->t('Add URL\'s of (remote) users line per line. Only add those who do not follow you.'), '#default_value' => !empty($_GET['to']) ? $_GET['to'] : '', '#access' => !empty($create_options), '#states' => [ 'visible' => [ ':input[name="activitypub_create"]' => ['!value' => ''], ] ], ]; foreach (array_keys($form['actions']) as $action) { if ($action === 'submit' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') { $form['actions'][$action]['#submit'][] = [$this, 'submitOutboxForm']; } } } else { $form['activitypub_wrapper']['activitypub_sent'] = [ '#markup' => $this->t('This post has been sent to the outbox.'), ]; } } /** * Submit outbox form. * * @param $form * @param \Drupal\Core\Form\FormStateInterface $form_state * * @throws \Drupal\Core\Entity\EntityStorageException */ public function submitOutboxForm($form, FormStateInterface $form_state) { /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $form_state->getFormObject()->getEntity(); if ($type = $form_state->getValue('activitypub_create')) { $this->outbox->createActivity($type, $entity, $form_state->getValue('activitypub_actor'), $form_state->getValue('activitypub_visibility'), $form_state->getValue('activitypub_to')); } if ($update = $form_state->getValue('activitypub_update')) { /** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity */ $activity = $this->activityStorage->load($update); $this->outbox->updateActivity($activity); } } /** * {@inheritdoc} */ public function alterCommentForm(array &$form, FormStateInterface $form_state, EntityInterface $entity) { /** @var \Drupal\comment\CommentInterface $entity */ if (isset($form['activitypub_activity'])) { $actor = $this->actorStorage->loadActorByEntityIdAndType($this->currentUser->id(), 'person'); if (!$actor) { $form['activitypub_activity']['#access'] = $this->currentUser->hasPermission('administer comments'); } else { $form['activitypub_activity']['#access'] = $this->currentUser->hasPermission('administer comments') || ($actor->getOwnerId() == $this->currentUser->id() && $actor->getOwnerId() == $entity->getCommentedEntity()->getOwnerId()); } } } }