<?php namespace Drupal\islandora\Form; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Site\Settings; use Drupal\Core\Url; use Stomp\Client; use Stomp\Exception\StompException; use Stomp\StatefulStomp; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Config form for Islandora settings. */ class IslandoraSettingsForm extends ConfigFormBase { const CONFIG_NAME = 'islandora.settings'; const BROKER_URL = 'broker_url'; const BROKER_USER = 'broker_user'; const BROKER_PASSWORD = 'broker_password'; const JWT_EXPIRY = 'jwt_expiry'; const UPLOAD_FORM = 'upload_form'; const UPLOAD_FORM_LOCATION = 'upload_form_location'; const UPLOAD_FORM_ALLOWED_MIMETYPES = 'upload_form_allowed_mimetypes'; const GEMINI_PSEUDO = 'gemini_pseudo_bundles'; const FEDORA_URL = 'fedora_url'; const TIME_INTERVALS = [ 'sec', 'second', 'min', 'minute', 'hour', 'day', 'week', 'month', 'year', ]; const GEMINI_PSEUDO_FIELD = 'field_gemini_uri'; const NODE_DELETE_MEDIA_AND_FILES = 'delete_media_and_files'; /** * To list the available bundle types. * * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface */ private $entityTypeBundleInfo; /** * The saved password (if set). * * @var string */ private $brokerPassword; /** * The entity type manager service. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ private $entityTypeManager; /** * Constructs a \Drupal\system\ConfigFormBase object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * The EntityTypeBundleInfo service. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The EntityTypeManager service. */ public function __construct( ConfigFactoryInterface $config_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager ) { $this->setConfigFactory($config_factory); $this->entityTypeBundleInfo = $entity_type_bundle_info; $this->brokerPassword = $this->config(self::CONFIG_NAME)->get(self::BROKER_PASSWORD); $this->entityTypeManager = $entity_type_manager; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), $container->get('entity_type.bundle.info'), $container->get('entity_type.manager') ); } /** * {@inheritdoc} */ public function getFormId() { return 'islandora_admin_settings'; } /** * {@inheritdoc} */ protected function getEditableConfigNames() { return [ self::CONFIG_NAME, ]; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config(self::CONFIG_NAME); $form['broker_info'] = [ '#type' => 'details', '#title' => $this->t('Broker'), '#open' => TRUE, ]; $form['broker_info'][self::BROKER_URL] = [ '#type' => 'textfield', '#title' => $this->t('URL'), '#default_value' => $config->get(self::BROKER_URL), ]; $broker_user = $config->get(self::BROKER_USER); $form['broker_info']['provide_user_creds'] = [ '#type' => 'checkbox', '#title' => $this->t('Provide user identification'), '#default_value' => $broker_user ? TRUE : FALSE, ]; $state_selector = 'input[name="provide_user_creds"]'; $form['broker_info'][self::BROKER_USER] = [ '#type' => 'textfield', '#title' => $this->t('User'), '#default_value' => $broker_user, '#states' => [ 'visible' => [ $state_selector => ['checked' => TRUE], ], 'required' => [ $state_selector => ['checked' => TRUE], ], ], ]; $form['broker_info'][self::BROKER_PASSWORD] = [ '#type' => 'password', '#title' => $this->t('Password'), '#description' => $this->t('If this field is left blank and the user is filled out, the current password will not be changed.'), '#states' => [ 'visible' => [ $state_selector => ['checked' => TRUE], ], ], ]; $form[self::JWT_EXPIRY] = [ '#type' => 'textfield', '#title' => $this->t('JWT Expiry'), '#default_value' => $config->get(self::JWT_EXPIRY), '#description' => $this->t('A positive time interval expression. Eg: "60 secs", "2 days", "10 hours", "7 weeks". Be sure you provide the time units (@unit), plurals are accepted.', ['@unit' => implode(", ", self::TIME_INTERVALS)] ), ]; $form[self::UPLOAD_FORM] = [ '#type' => 'fieldset', '#title' => $this->t('Add Children / Media Form'), ]; $form[self::UPLOAD_FORM][self::UPLOAD_FORM_LOCATION] = [ '#type' => 'textfield', '#title' => $this->t('Upload location'), '#description' => $this->t('Tokenized URI pattern where the uploaded file should go. You may use tokens to provide a pattern (e.g. "fedora://[current-date:custom:Y]-[current-date:custom:m]")'), '#default_value' => $config->get(self::UPLOAD_FORM_LOCATION), '#element_validate' => ['token_element_validate'], '#token_types' => ['system'], ]; $form[self::UPLOAD_FORM]['TOKEN_HELP'] = [ '#theme' => 'token_tree_link', '#token_type' => ['system'], ]; $form[self::UPLOAD_FORM][self::UPLOAD_FORM_ALLOWED_MIMETYPES] = [ '#type' => 'textarea', '#title' => $this->t('Allowed Mimetypes'), '#description' => $this->t('Add mimetypes as a space delimited list with no periods before the extension.'), '#default_value' => $config->get(self::UPLOAD_FORM_ALLOWED_MIMETYPES), ]; $flysystem_config = Settings::get('flysystem'); if ($flysystem_config != NULL) { $fedora_url = $flysystem_config['fedora']['config']['root']; } else { $fedora_url = NULL; } $form[self::NODE_DELETE_MEDIA_AND_FILES] = [ '#type' => 'checkbox', '#title' => $this->t('Node Delete with Media and Files'), '#description' => $this->t('Adds a checkbox in the "Delete" tab of islandora objects to delete media and files associated with the object.' ), '#default_value' => (bool) $config->get(self::NODE_DELETE_MEDIA_AND_FILES), ]; $form[self::FEDORA_URL] = [ '#type' => 'textfield', '#title' => $this->t('Fedora URL'), '#attributes' => ['readonly' => 'readonly'], '#default_value' => $fedora_url, ]; $selected_bundles = $config->get(self::GEMINI_PSEUDO); $options = []; foreach (['node', 'media', 'taxonomy_term'] as $content_entity) { $bundles = $this->entityTypeBundleInfo->getBundleInfo($content_entity); foreach ($bundles as $bundle => $bundle_properties) { $options["{$bundle}:{$content_entity}"] = $this->t('@label (@type)', [ '@label' => $bundle_properties['label'], '@type' => $content_entity, ]); } } $form['bundle_container'] = [ '#type' => 'details', '#title' => $this->t('Fedora URL Display'), '#description' => $this->t('Selected bundles can display the Fedora URL of repository content.'), '#open' => TRUE, self::GEMINI_PSEUDO => [ '#type' => 'checkboxes', '#options' => $options, '#default_value' => $selected_bundles, ], ]; $form['rdf_namespaces'] = [ '#type' => 'link', '#title' => $this->t('Update RDF namespace configurations in the JSON-LD module settings.'), '#url' => Url::fromRoute('system.jsonld_settings'), ]; return parent::buildForm($form, $form_state); } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { // Validate broker url by actually connecting with a stomp client. $brokerUrl = $form_state->getValue(self::BROKER_URL); // Attempt to subscribe to a dummy queue. try { $client = new Client($brokerUrl); if ($form_state->getValue('provide_user_creds')) { $broker_password = $form_state->getValue(self::BROKER_PASSWORD); // When stored password type fields aren't rendered again. if (!$broker_password) { // Use the stored password if it exists. if (!$this->brokerPassword) { $form_state->setErrorByName(self::BROKER_PASSWORD, $this->t('A password must be supplied')); } else { $broker_password = $this->brokerPassword; } } $client->setLogin($form_state->getValue(self::BROKER_USER), $broker_password); } $stomp = new StatefulStomp($client); $stomp->subscribe('dummy-queue-for-validation'); $stomp->unsubscribe(); } // Invalidate the form if there's an issue. catch (StompException $e) { $form_state->setErrorByName( self::BROKER_URL, $this->t( 'Cannot connect to message broker at @broker_url', ['@broker_url' => $brokerUrl] ) ); } // Validate jwt expiry as a valid time string. $expiry = trim($form_state->getValue(self::JWT_EXPIRY)); $expiry = strtolower($expiry); if (strtotime($expiry) === FALSE) { $form_state->setErrorByName( self::JWT_EXPIRY, $this->t( '"@expiry" is not a valid time or interval expression.', ['@expiry' => $expiry] ) ); } elseif (substr($expiry, 0, 1) == "-") { $form_state->setErrorByName( self::JWT_EXPIRY, $this->t('Time or interval expression cannot be negative') ); } elseif (intval($expiry) === 0) { $form_state->setErrorByName( self::JWT_EXPIRY, $this->t('No numeric interval specified, for example "1 day"') ); } else { if (!preg_match("/\b(" . implode("|", self::TIME_INTERVALS) . ")s?\b/", $expiry)) { $form_state->setErrorByName( self::JWT_EXPIRY, $this->t("No time interval found, please include one of (@int). Plurals are also accepted.", ['@int' => implode(", ", self::TIME_INTERVALS)] ) ); } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->configFactory->getEditable(self::CONFIG_NAME); $new_pseudo_types = array_filter($form_state->getValue(self::GEMINI_PSEUDO)); $broker_password = $form_state->getValue(self::BROKER_PASSWORD); // If there's no user set delete what may have been here before as password // fields will also be blank. if (!$form_state->getValue('provide_user_creds')) { $config->clear(self::BROKER_USER); $config->clear(self::BROKER_PASSWORD); } else { $config->set(self::BROKER_USER, $form_state->getValue(self::BROKER_USER)); // If the password has changed update it as well. if ($broker_password && $broker_password != $this->brokerPassword) { $config->set(self::BROKER_PASSWORD, $broker_password); } } // Check for types being unset and remove the field from them first. $current_pseudo_types = $config->get(self::GEMINI_PSEUDO); $this->updateEntityViewConfiguration($current_pseudo_types, $new_pseudo_types); $config ->set(self::BROKER_URL, $form_state->getValue(self::BROKER_URL)) ->set(self::JWT_EXPIRY, $form_state->getValue(self::JWT_EXPIRY)) ->set(self::UPLOAD_FORM_LOCATION, $form_state->getValue(self::UPLOAD_FORM_LOCATION)) ->set(self::UPLOAD_FORM_ALLOWED_MIMETYPES, $form_state->getValue(self::UPLOAD_FORM_ALLOWED_MIMETYPES)) ->set(self::GEMINI_PSEUDO, $new_pseudo_types) ->set(self::NODE_DELETE_MEDIA_AND_FILES, $form_state->getValue(self::NODE_DELETE_MEDIA_AND_FILES)) ->save(); parent::submitForm($form, $form_state); } /** * Removes the Fedora URI field from entity bundles that have be unselected. * * @param array $current_config * The current set of entity types & bundles to have the pseudo field, * format {bundle}:{entity_type}. * @param array $new_config * The new set of entity types & bundles to have the pseudo field, format * {bundle}:{entity_type}. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException * @throws \Drupal\Core\Entity\EntityStorageException */ private function updateEntityViewConfiguration(array $current_config, array $new_config) { $removed = array_diff($current_config, $new_config); $added = array_diff($new_config, $current_config); $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display'); foreach ($removed as $bundle_type) { [$bundle, $type_id] = explode(":", $bundle_type); $results = $entity_view_display->getQuery() ->condition('bundle', $bundle) ->condition('targetEntityType', $type_id) ->exists('content.' . self::GEMINI_PSEUDO_FIELD . '.region') ->execute(); $entities = $entity_view_display->loadMultiple($results); foreach ($entities as $entity) { $entity->removeComponent(self::GEMINI_PSEUDO_FIELD); $entity->save(); } } if (count($removed) > 0 || count($added) > 0) { // If we added or cleared a type then clear the extra_fields cache. // @see Drupal/Core/Entity/EntityFieldManager::getExtraFields Cache::invalidateTags(["entity_field_info"]); } } }