Drupal modules for browsing and managing Fedora-based digital repositories.
 
 
 
 

407 lines
14 KiB

<?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"]);
}
}
}