516 lines
17 KiB
PHP
516 lines
17 KiB
PHP
<?php
|
|
|
|
namespace Drupal\jsonapi_extras\Form;
|
|
|
|
use Drupal\Component\Plugin\Exception\PluginException;
|
|
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
|
use Drupal\Component\Utility\NestedArray;
|
|
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
|
use Drupal\Core\Entity\EntityForm;
|
|
use Drupal\Core\Entity\EntityTypeInterface;
|
|
use Drupal\Core\Entity\FieldableEntityInterface;
|
|
use Drupal\Core\Form\FormStateInterface;
|
|
use Drupal\jsonapi\ResourceType\ResourceType;
|
|
use Drupal\jsonapi_extras\Entity\JsonapiResourceConfig;
|
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
|
|
/**
|
|
* Base form for jsonapi_resource_config.
|
|
*/
|
|
class JsonapiResourceConfigForm extends EntityForm {
|
|
|
|
/**
|
|
* The bundle information service.
|
|
*
|
|
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
|
*/
|
|
protected $bundleInfo;
|
|
|
|
/**
|
|
* The JSON:API resource type repository.
|
|
*
|
|
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
|
|
*/
|
|
protected $resourceTypeRepository;
|
|
|
|
/**
|
|
* The field manager.
|
|
*
|
|
* @var \Drupal\Core\Entity\EntityFieldManager
|
|
*/
|
|
protected $fieldManager;
|
|
|
|
/**
|
|
* The entity type repository.
|
|
*
|
|
* @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
|
|
*/
|
|
protected $entityTypeRepository;
|
|
|
|
/**
|
|
* The field enhancer manager.
|
|
*
|
|
* @var \Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager
|
|
*/
|
|
protected $enhancerManager;
|
|
|
|
/**
|
|
* The JSON:API extras config.
|
|
*
|
|
* @var \Drupal\Core\Config\ImmutableConfig
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* The current route match.
|
|
*
|
|
* @var \Symfony\Component\HttpFoundation\Request
|
|
*/
|
|
protected $request;
|
|
|
|
/**
|
|
* The typed config manager.
|
|
*
|
|
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
|
*/
|
|
protected $typedConfigManager;
|
|
|
|
/**
|
|
* A logger instance.
|
|
*
|
|
* @var \Psr\Log\LoggerInterface
|
|
*/
|
|
protected $logger;
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public static function create(ContainerInterface $container) {
|
|
$instance = parent::create($container);
|
|
|
|
$instance->bundleInfo = $container->get('entity_type.bundle.info');
|
|
$instance->resourceTypeRepository = $container->get('jsonapi.resource_type.repository');
|
|
$instance->fieldManager = $container->get('entity_field.manager');
|
|
$instance->entityTypeRepository = $container->get('entity_type.repository');
|
|
$instance->enhancerManager = $container->get('plugin.manager.resource_field_enhancer');
|
|
$instance->config = $container->get('config.factory')->get('jsonapi_extras.settings');
|
|
$instance->request = $container->get('request_stack')->getCurrentRequest();
|
|
$instance->typedConfigManager = $container->get('config.typed');
|
|
$instance->logger = $container->get('logger.channel.jsonapi_extras');
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function form(array $form, FormStateInterface $form_state) {
|
|
$form = parent::form($form, $form_state);
|
|
// Disable caching on this form.
|
|
$form_state->setCached(FALSE);
|
|
|
|
$entity_type_id = $this->request->get('entity_type_id');
|
|
$bundle = $this->request->get('bundle');
|
|
|
|
/** @var \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity */
|
|
$entity = $this->getEntity();
|
|
$resource_id = $entity->get('id');
|
|
// If we are editing an entity we don't want the Entity Type and Bundle
|
|
// picker, that info is locked.
|
|
if (!$entity_type_id || !$bundle) {
|
|
if (!$resource_id) {
|
|
// We can't build the form without an entity type and bundle.
|
|
throw new \InvalidArgumentException('Unable to load entity type or bundle for the overrides form.');
|
|
}
|
|
[$entity_type_id, $bundle] = explode('--', $resource_id);
|
|
$form['#title'] = $this->t('Edit %label resource config', ['%label' => $resource_id]);
|
|
}
|
|
|
|
if ($entity_type_id && $resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle)) {
|
|
// Get the JSON:API resource type.
|
|
$resource_config_id = sprintf('%s--%s', $entity_type_id, $bundle);
|
|
$existing_entity = $this->entityTypeManager
|
|
->getStorage('jsonapi_resource_config')->load($resource_config_id);
|
|
if ($existing_entity && $entity->isNew()) {
|
|
$this->messenger()->addStatus($this->t('This override already exists, please edit it instead.'));
|
|
return $form;
|
|
}
|
|
try {
|
|
$fields_wrapper = $this->buildOverridesForm($resource_type, $entity);
|
|
$form['bundle_wrapper']['fields_wrapper'] = $fields_wrapper;
|
|
}
|
|
catch (PluginNotFoundException $exception) {
|
|
$this->logger->error($exception);
|
|
}
|
|
$form['id'] = ['#type' => 'hidden', '#value' => $resource_config_id];
|
|
}
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function validateForm(array &$form, FormStateInterface $form_state) {
|
|
if (!method_exists($this->typedConfigManager, 'createFromNameAndData')) {
|
|
// Versions of Drupal before 8.4 have poor support for constraints. In
|
|
// those scenarios we don't validate the form submission.
|
|
return;
|
|
}
|
|
$typed_config = $this->typedConfigManager
|
|
->createFromNameAndData($this->entity->id(), $this->entity->toArray());
|
|
$constraints = $typed_config->validate();
|
|
/** @var \Symfony\Component\Validator\ConstraintViolation $violation */
|
|
foreach ($constraints as $violation) {
|
|
$form_path = str_replace('.', '][', $violation->getPropertyPath());
|
|
$form_state->setErrorByName($form_path, $violation->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function save(array $form, FormStateInterface $form_state) {
|
|
$resource_config = $this->entity;
|
|
$status = $resource_config->save();
|
|
$type = $resource_config->get('resourceType') ?? '';
|
|
|
|
switch ($status) {
|
|
case SAVED_NEW:
|
|
$this->messenger()->addStatus($this->t('Created the %type JSON:API Resource overwrites.', [
|
|
'%type' => $type,
|
|
]));
|
|
break;
|
|
|
|
default:
|
|
$this->messenger()->addStatus($this->t('Saved the %type JSON:API Resource overwrites.', [
|
|
'%type' => $type,
|
|
]));
|
|
}
|
|
$form_state->setRedirectUrl($resource_config->toUrl('collection'));
|
|
}
|
|
|
|
/**
|
|
* Builds the part of the form that contains the overrides.
|
|
*
|
|
* @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
|
|
* The resource type being overridden.
|
|
* @param \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity
|
|
* The configuration entity backing this form.
|
|
*
|
|
* @return array
|
|
* The partial form.
|
|
*
|
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
|
*/
|
|
protected function buildOverridesForm(ResourceType $resource_type, JsonapiResourceConfig $entity) {
|
|
$entity_type_id = $resource_type->getEntityTypeId();
|
|
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
|
|
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
|
$bundle = $resource_type->getBundle();
|
|
$field_names = $this->getAllFieldNames($entity_type, $bundle);
|
|
|
|
$overrides_form['overrides']['entity'] = [
|
|
'#type' => 'fieldset',
|
|
'#title' => $this->t('Resource'),
|
|
'#description' => $this->t('Override configuration for the resource entity.'),
|
|
'#open' => !$entity->get('resourceType') || !$entity->get('path'),
|
|
'#weight' => 0,
|
|
];
|
|
|
|
$overrides_form['overrides']['entity']['disabled'] = [
|
|
'#type' => 'checkbox',
|
|
'#title' => $this->t('Disabled'),
|
|
'#description' => $this->t('Check this if you want to disable this resource. Disabling a resource can have unexpected results when following relationships belonging to that resource.'),
|
|
'#default_value' => $entity->get('disabled'),
|
|
];
|
|
|
|
$resource_type_name = $entity->get('resourceType');
|
|
if (!$resource_type_name) {
|
|
$resource_type_name = sprintf('%s--%s', $entity_type_id, $bundle);
|
|
}
|
|
$overrides_form['overrides']['entity']['resourceType'] = [
|
|
'#type' => 'textfield',
|
|
'#title' => $this->t('Resource Type'),
|
|
'#description' => $this->t('Overrides the type of the resource. Example: Change "node--article" to "articles".'),
|
|
'#default_value' => $resource_type_name,
|
|
'#states' => [
|
|
'visible' => [
|
|
':input[name="disabled"]' => ['checked' => FALSE],
|
|
],
|
|
],
|
|
];
|
|
|
|
$path = $entity->get('path');
|
|
if (!$path) {
|
|
$path = sprintf('%s/%s', $entity_type_id, $bundle);
|
|
}
|
|
|
|
$prefix = $this->config->get('path_prefix');
|
|
$overrides_form['overrides']['entity']['path'] = [
|
|
'#type' => 'textfield',
|
|
'#title' => $this->t('Resource Path'),
|
|
'#field_prefix' => sprintf('/%s/', $prefix),
|
|
'#description' => $this->t('Overrides the path of the resource. Example: Use "articles" to change "/@prefix/node/article" to "/@prefix/articles".', [
|
|
'@prefix' => $prefix,
|
|
]),
|
|
'#default_value' => $path,
|
|
'#required' => TRUE,
|
|
'#states' => [
|
|
'visible' => [
|
|
':input[name="disabled"]' => ['checked' => FALSE],
|
|
],
|
|
],
|
|
];
|
|
|
|
$overrides_form['overrides']['fields'] = [
|
|
'#type' => 'details',
|
|
'#title' => $this->t('Fields'),
|
|
'#open' => TRUE,
|
|
'#weight' => 1,
|
|
];
|
|
|
|
$markup = '';
|
|
$markup .= '<dl>';
|
|
$markup .= '<dt>' . $this->t('Disabled') . '</dt>';
|
|
$markup .= '<dd>' . $this->t('Check this if you want to disable this field completely. Disabling required fields will cause problems when writing to the resource.') . '</dd>';
|
|
$markup .= '<dt>' . $this->t('Alias') . '</dt>';
|
|
$markup .= '<dd>' . $this->t('Overrides the field name with a custom name. Example: Change "field_tags" to "tags".') . '</dd>';
|
|
$markup .= '<dt>' . $this->t('Enhancer') . '</dt>';
|
|
$markup .= '<dd>' . $this->t('Select an enhancer to manipulate the public output coming in and out.') . '</dd>';
|
|
$markup .= '</dl>';
|
|
$overrides_form['overrides']['fields']['info'] = [
|
|
'#markup' => $markup,
|
|
];
|
|
|
|
$overrides_form['overrides']['fields']['resourceFields'] = [
|
|
'#type' => 'table',
|
|
'#theme' => 'expandable_rows_table',
|
|
'#header' => [
|
|
'disabled' => $this->t('Disabled'),
|
|
'fieldName' => $this->t('Field name'),
|
|
'publicName' => $this->t('Alias'),
|
|
'advancedOptions' => '',
|
|
],
|
|
'#empty' => $this->t('No fields available.'),
|
|
'#states' => [
|
|
'visible' => [
|
|
':input[name="disabled"]' => ['checked' => FALSE],
|
|
],
|
|
],
|
|
'#attached' => [
|
|
'library' => [
|
|
'jsonapi_extras/expandable_rows_table',
|
|
],
|
|
],
|
|
];
|
|
|
|
foreach ($field_names as $field_name) {
|
|
try {
|
|
$overrides = $this->buildOverridesField($field_name, $entity);
|
|
}
|
|
catch (PluginException $exception) {
|
|
// Log exception and continue.
|
|
$this->logger->error($exception);
|
|
continue;
|
|
}
|
|
NestedArray::setValue(
|
|
$overrides_form,
|
|
['overrides', 'fields', 'resourceFields', $field_name],
|
|
$overrides
|
|
);
|
|
}
|
|
|
|
return $overrides_form;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function buildEntity(array $form, FormStateInterface $form_state) {
|
|
/** @var \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity */
|
|
$entity = parent::buildEntity($form, $form_state);
|
|
|
|
// Trim slashes from path.
|
|
$path = trim($form_state->getValue('path'), '/');
|
|
if (strlen($path) > 0) {
|
|
$entity->set('path', $path);
|
|
}
|
|
|
|
return $entity;
|
|
}
|
|
|
|
/**
|
|
* Builds the part of the form that overrides the field.
|
|
*
|
|
* @param string $field_name
|
|
* The field name of the field being overridden.
|
|
* @param \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity
|
|
* The config entity backed by this form.
|
|
*
|
|
* @return array
|
|
* The partial form.
|
|
*
|
|
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
|
*/
|
|
protected function buildOverridesField($field_name, JsonapiResourceConfig $entity) {
|
|
$rfs = $entity->get('resourceFields') ?: [];
|
|
$resource_fields = array_filter($rfs, function (array $resource_field) use ($field_name) {
|
|
return $resource_field['fieldName'] == $field_name;
|
|
});
|
|
$resource_field = array_shift($resource_fields);
|
|
$overrides_form = [];
|
|
$overrides_form['disabled'] = [
|
|
'#type' => 'checkbox',
|
|
'#title' => $this->t('Disabled'),
|
|
'#title_display' => 'invisible',
|
|
'#default_value' => empty($resource_field['disabled']) ? NULL : $resource_field['disabled'],
|
|
];
|
|
$overrides_form['fieldName'] = [
|
|
'#type' => 'hidden',
|
|
'#value' => $field_name,
|
|
'#prefix' => $field_name,
|
|
];
|
|
$overrides_form['publicName'] = [
|
|
'#type' => 'textfield',
|
|
'#title' => $this->t('Override Public Name'),
|
|
'#title_display' => 'hidden',
|
|
'#default_value' => empty($resource_field['publicName']) ? $field_name : $resource_field['publicName'],
|
|
'#states' => [
|
|
'visible' => [
|
|
':input[name="resourceFields[' . $field_name . '][disabled]"]' => [
|
|
'checked' => FALSE,
|
|
],
|
|
],
|
|
],
|
|
];
|
|
$overrides_form['advancedOptions'] = [
|
|
'#markup' => t('Advanced'),
|
|
];
|
|
|
|
$overrides_form['advancedOptionsIcon'] = [
|
|
// Here we are just printing an arrow.
|
|
'#markup' => '↳',
|
|
];
|
|
|
|
$overrides_form['enhancer_label'] = [
|
|
'#markup' => $this->t('Enhancer for: %name', ['%name' => $field_name]),
|
|
];
|
|
|
|
// Build the select field for the list of enhancers.
|
|
$overrides_form['enhancer'] = [
|
|
'#wrapper_attributes' => ['colspan' => 2],
|
|
'#type' => 'fieldgroup',
|
|
'#states' => [
|
|
'visible' => [
|
|
':input[name="resourceFields[' . $field_name . '][disabled]"]' => [
|
|
'checked' => FALSE,
|
|
],
|
|
],
|
|
],
|
|
];
|
|
$options = array_reduce(
|
|
$this->enhancerManager->getDefinitions(),
|
|
function (array $carry, array $definition) {
|
|
$carry[$definition['id']] = $definition['label'];
|
|
return $carry;
|
|
},
|
|
['' => $this->t('- None -')]
|
|
);
|
|
$id = empty($resource_field['enhancer']['id'])
|
|
? ''
|
|
: $resource_field['enhancer']['id'];
|
|
$overrides_form['enhancer']['id'] = [
|
|
'#type' => 'select',
|
|
'#options' => $options,
|
|
'#ajax' => [
|
|
'callback' => '::getEnhancerSettings',
|
|
'wrapper' => $field_name . '-settings-wrapper',
|
|
],
|
|
'#default_value' => $id,
|
|
];
|
|
$overrides_form['enhancer']['settings'] = [
|
|
'#type' => 'container',
|
|
'#attributes' => ['id' => $field_name . '-settings-wrapper'],
|
|
];
|
|
if (!empty($resource_field['enhancer']['id'])) {
|
|
/** @var \Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerInterface $enhancer */
|
|
$enhancer = $this->enhancerManager
|
|
->createInstance($resource_field['enhancer']['id'], []);
|
|
$overrides_form['enhancer']['settings'] += $enhancer
|
|
->getSettingsForm($resource_field);
|
|
}
|
|
return $overrides_form;
|
|
}
|
|
|
|
/**
|
|
* AJAX callback to get the form settings for the enhancer for a field.
|
|
*
|
|
* @param array $form
|
|
* The reference to the form.
|
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
|
* The form state.
|
|
*
|
|
* @return mixed
|
|
* The specific form sub-tree in the form.
|
|
*/
|
|
public static function getEnhancerSettings(array &$form, FormStateInterface $form_state) {
|
|
// Find what is the field name that triggered the AJAX request.
|
|
$user_input = $form_state->getUserInput();
|
|
$parts = explode('[', $user_input['_triggering_element_name']);
|
|
$field_name = rtrim($parts[1], ']');
|
|
// Now return the sub-tree for the settings on the enhancer plugin.
|
|
return $form['bundle_wrapper']['fields_wrapper']['overrides']['fields']['resourceFields'][$field_name]['enhancer']['settings'];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
protected function actionsElement(array $form, FormStateInterface $form_state) {
|
|
// We want to display "Revert" instead of "Delete" on the Resource Config
|
|
// Form.
|
|
$element = parent::actionsElement($form, $form_state);
|
|
if (isset($element['delete'])) {
|
|
$element['delete']['#title'] = $this->t('Revert');
|
|
}
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Gets all field names for a given entity type and bundle.
|
|
*
|
|
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
|
* The entity type for which to get all field names.
|
|
* @param string $bundle
|
|
* The bundle for which to get all field names.
|
|
*
|
|
* @todo This is a copy of ResourceTypeRepository::getAllFieldNames. We can't
|
|
* reuse that code because it's protected.
|
|
*
|
|
* @return string[]
|
|
* All field names.
|
|
*/
|
|
protected function getAllFieldNames(EntityTypeInterface $entity_type, $bundle) {
|
|
if (is_a($entity_type->getClass(), FieldableEntityInterface::class, TRUE)) {
|
|
$field_definitions = $this->fieldManager->getFieldDefinitions(
|
|
$entity_type->id(),
|
|
$bundle
|
|
);
|
|
return array_keys($field_definitions);
|
|
}
|
|
elseif (is_a($entity_type->getClass(), ConfigEntityInterface::class, TRUE)) {
|
|
// @todo Uncomment the first line, remove everything else once https://www.drupal.org/project/drupal/issues/2483407 lands.
|
|
// return array_keys($entity_type->getPropertiesToExport());
|
|
$export_properties = $entity_type->getPropertiesToExport();
|
|
if ($export_properties !== NULL) {
|
|
return array_keys($export_properties);
|
|
}
|
|
else {
|
|
return ['id', 'type', 'uuid', '_core'];
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
}
|