Skip to content
Commits on Source (45)
# JSON API Extras # JSON API Extras
This module provides extra functionality on top of JSON API. You should not need This module provides extra functionality on top of JSON API. You should not
this module to get an spec compliant JSON API. need this module to get an spec compliant JSON API, this module is to
customize the output of JSON API.
This module adds the following features: This module adds the following features:
...@@ -12,4 +13,5 @@ This module adds the following features: ...@@ -12,4 +13,5 @@ This module adds the following features:
- Lets you remove fields from the JSON API output. - Lets you remove fields from the JSON API output.
TODO: TODO:
* Auto calculate the dependency of the provider of the entity type and bundles in the configuration entity. * Auto calculate the dependency of the provider of the entity type and
bundles in the configuration entity.
{
"name": "drupal/jsonapi_extras",
"description": "JSON API Extras provides a means to override and provide limited configurations to the default zero-configuration implementation provided by the JSON API module.",
"type": "drupal-module",
"license": "GPL-2.0+",
"authors": [
{
"name": "Mateu Aguiló Bosch",
"email": "mateu.aguilo.bosch@gmail.com"
}
],
"require": {
"drupal/jsonapi": "^1.12"
}
}
path_prefix: jsonapi path_prefix: jsonapi
include_count: false
...@@ -43,12 +43,21 @@ jsonapi_extras.resource_field: ...@@ -43,12 +43,21 @@ jsonapi_extras.resource_field:
mapping: mapping:
id: id:
type: string type: string
description: 'ID'
description: 'The enhancer plugin ID' description: 'The enhancer plugin ID'
settings: settings:
type: mapping type: jsonapi_extras.enhancer_plugin.[%parent.id]
description: 'Settings'
description: 'Unstructured settings.' jsonapi_extras.enhancer_plugin.date_time:
type: mapping
mapping:
dateTimeFormat:
type: string
jsonapi_extras.enhancer_plugin.nested:
type: mapping
mapping:
path:
type: string
jsonapi_extras.settings: jsonapi_extras.settings:
type: config_object type: config_object
...@@ -58,3 +67,7 @@ jsonapi_extras.settings: ...@@ -58,3 +67,7 @@ jsonapi_extras.settings:
type: string type: string
label: 'Path prefix' label: 'Path prefix'
description: 'The path prefix for JSON API' description: 'The path prefix for JSON API'
include_count:
type: boolean
label: 'Include count in collection responses'
description: 'If activated, all collection responses will return a total record count for the provided query.'
...@@ -4,4 +4,4 @@ description: Builds on top of JSON API to deliver extra functionality. ...@@ -4,4 +4,4 @@ description: Builds on top of JSON API to deliver extra functionality.
core: 8.x core: 8.x
package: Web services package: Web services
dependencies: dependencies:
- jsonapi - jsonapi:jsonapi
<?php <?php
/**
* @file
* Module implementation file.
*/
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
/** /**
...@@ -10,7 +15,7 @@ function jsonapi_extras_help($route_name, RouteMatchInterface $route_match) { ...@@ -10,7 +15,7 @@ function jsonapi_extras_help($route_name, RouteMatchInterface $route_match) {
case 'entity.jsonapi_resource_config.collection': case 'entity.jsonapi_resource_config.collection':
$output = ''; $output = '';
$output .= '<p>' . t('The following table shows the list of JSON API resources available.') . '</p>'; $output .= '<p>' . t('The following table shows the list of JSON API resources available.') . '</p>';
$output .= '<p>' . t('Use the overwrite operation to overwrite a resource\'s configuration. You can revert back to the default configuration using the revert operation.') . '</p>'; $output .= '<p>' . t("Use the overwrite operation to overwrite a resource's configuration. You can revert back to the default configuration using the revert operation.") . '</p>';
return $output; return $output;
} }
} }
services: services:
route_subscriber.alter_jsonapi:
class: Drupal\jsonapi_extras\EventSubscriber\JsonApiExtrasRouteAlterSubscriber
arguments:
- '@jsonapi.resource_type.repository'
- '@config.factory'
tags:
- { name: event_subscriber }
serializer.normalizer.field_item.jsonapi_extras: serializer.normalizer.field_item.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\FieldItemNormalizer class: Drupal\jsonapi_extras\Normalizer\FieldItemNormalizer
arguments: arguments:
...@@ -14,18 +6,18 @@ services: ...@@ -14,18 +6,18 @@ services:
- '@entity_type.manager' - '@entity_type.manager'
- '@plugin.manager.resource_field_enhancer' - '@plugin.manager.resource_field_enhancer'
tags: tags:
- { name: normalizer, priority: 25 } - { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 25 }
serializer.normalizer.entity.jsonapi_extras: serializer.normalizer.entity.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\ContentEntityNormalizer class: Drupal\jsonapi_extras\Normalizer\ContentEntityNormalizer
arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager'] arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager']
tags: tags:
- { name: normalizer, priority: 22 } - { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 22 }
serializer.normalizer.config_entity.jsonapi_extras: serializer.normalizer.config_entity.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\ConfigEntityNormalizer class: Drupal\jsonapi_extras\Normalizer\ConfigEntityNormalizer
arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager'] arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager']
tags: tags:
- { name: normalizer, priority: 22 } - { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 22 }
plugin.manager.resource_field_enhancer: plugin.manager.resource_field_enhancer:
class: Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager class: Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager
......
...@@ -24,6 +24,7 @@ use Drupal\Core\Entity\EntityStorageInterface; ...@@ -24,6 +24,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
* }, * },
* config_prefix = "jsonapi_resource_config", * config_prefix = "jsonapi_resource_config",
* admin_permission = "administer site configuration", * admin_permission = "administer site configuration",
* static_cache = TRUE,
* entity_keys = { * entity_keys = {
* "id" = "id", * "id" = "id",
* "label" = "label", * "label" = "label",
...@@ -77,4 +78,29 @@ class JsonapiResourceConfig extends ConfigEntityBase { ...@@ -77,4 +78,29 @@ class JsonapiResourceConfig extends ConfigEntityBase {
\Drupal::service('router.builder')->setRebuildNeeded(); \Drupal::service('router.builder')->setRebuildNeeded();
} }
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
$id = explode('--', $this->id);
$typeManager = $this->entityTypeManager();
$dependency = $typeManager->getDefinition($id[0])->getBundleConfigDependency($id[1]);
$this->addDependency($dependency['type'], $dependency['name']);
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$uri_route_parameters = parent::urlRouteParameters($rel);
// The add-form route depends on entity_type_id and bundle.
if (in_array($rel, ['add-form'])) {
$parameters = explode('--', $this->id);
$uri_route_parameters['entity_type_id'] = $parameters[0];
$uri_route_parameters['bundle'] = $parameters[1];
}
return $uri_route_parameters;
}
} }
<?php
namespace Drupal\jsonapi_extras\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscriber for overwrite JSON API routes.
*/
class JsonApiExtrasRouteAlterSubscriber implements EventSubscriberInterface {
/**
* The configuration object factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The JSON API resource repository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
*/
protected $resourceTypeRepository;
/**
* JsonApiExtrasRouteAlterSubscriber constructor.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepository $resource_type_repository
* The JSON API resource repository.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration object factory.
*/
public function __construct(ResourceTypeRepository $resource_type_repository, ConfigFactoryInterface $config_factory) {
$this->resourceTypeRepository = $resource_type_repository;
$this->configFactory = $config_factory;
}
/**
* Alters select routes to update the route path.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function onRoutingRouteAlterSetPaths(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
$prefix = $this->configFactory
->get('jsonapi_extras.settings')
->get('path_prefix');
// Overwrite the entry point.
$path = sprintf('/%s', $prefix);
$collection->get('jsonapi.resource_list')
->setPath($path);
/** @var ConfigurableResourceType $resource_type */
foreach ($this->resourceTypeRepository->all() as $resource_type) {
// Overwrite routes.
$paths = $this->getPathsForResourceType($resource_type, $prefix);
foreach ($paths as $route_name => $path) {
$collection->get($route_name)->setPath($path);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = ['onRoutingRouteAlterSetPaths'];
return $events;
}
/**
* Returns paths for a resource type.
*
* @param \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType $resource_type
* The ConfigurableResourceType entity.
* @param string $prefix
* The path prefix.
*
* @return array
* An array of route paths.
*/
protected function getPathsForResourceType(ConfigurableResourceType $resource_type, $prefix) {
$entity_type_id = $resource_type->getEntityTypeId();
$bundle_id = $resource_type->getBundle();
// Callback to build the route name.
$build_route_name = function ($key) use ($resource_type) {
return sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $key);
};
// Base path.
$base_path = sprintf('/%s/%s/%s', $prefix, $entity_type_id, $bundle_id);
if (($resource_config = $resource_type->getJsonapiResourceConfig()) && ($config_path = $resource_config->get('path'))) {
$base_path = sprintf('/%s/%s', $prefix, $config_path);
}
$paths = [];
$paths[$build_route_name('collection')] = $base_path;
$paths[$build_route_name('individual')] = sprintf('%s/{%s}', $base_path, $entity_type_id);
$paths[$build_route_name('related')] = sprintf('%s/{%s}/{related}', $base_path, $entity_type_id);
$paths[$build_route_name('relationship')] = sprintf('%s/{%s}/relationships/{related}', $base_path, $entity_type_id);
return $paths;
}
}
...@@ -2,14 +2,42 @@ ...@@ -2,14 +2,42 @@
namespace Drupal\jsonapi_extras\Form; namespace Drupal\jsonapi_extras\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ProxyClass\Routing\RouteBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Configure JSON API settings for this site. * Configure JSON API settings for this site.
*/ */
class JsonapiExtrasSettingsForm extends ConfigFormBase { class JsonapiExtrasSettingsForm extends ConfigFormBase {
protected $routerBuilder;
/**
* Constructs a \Drupal\system\ConfigFormBase object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\ProxyClass\Routing\RouteBuilder $router_builder
* The router builder to rebuild menus after saving config entity.
*/
public function __construct(ConfigFactoryInterface $config_factory, RouteBuilder $router_builder) {
parent::__construct($config_factory);
$this->routerBuilder = $router_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('router.builder')
);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -39,6 +67,13 @@ class JsonapiExtrasSettingsForm extends ConfigFormBase { ...@@ -39,6 +67,13 @@ class JsonapiExtrasSettingsForm extends ConfigFormBase {
'#default_value' => $config->get('path_prefix'), '#default_value' => $config->get('path_prefix'),
]; ];
$form['include_count'] = [
'#title' => $this->t('Include count in collection queries'),
'#type' => 'checkbox',
'#description' => $this->t('If activated, all collection responses will return a total record count for the provided query.'),
'#default_value' => $config->get('include_count'),
];
return parent::buildForm($form, $form_state); return parent::buildForm($form, $form_state);
} }
...@@ -52,8 +87,12 @@ class JsonapiExtrasSettingsForm extends ConfigFormBase { ...@@ -52,8 +87,12 @@ class JsonapiExtrasSettingsForm extends ConfigFormBase {
->save(); ->save();
} }
$this->config('jsonapi_extras.settings')
->set('include_count', $form_state->getValue('include_count'))
->save();
// Rebuild the router. // Rebuild the router.
\Drupal::service('router.builder')->setRebuildNeeded(); $this->routerBuilder->setRebuildNeeded();
parent::submitForm($form, $form_state); parent::submitForm($form, $form_state);
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\jsonapi_extras\Form; namespace Drupal\jsonapi_extras\Form;
use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityForm;
...@@ -71,6 +72,13 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -71,6 +72,13 @@ class JsonapiResourceConfigForm extends EntityForm {
*/ */
protected $request; protected $request;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/** /**
* JsonapiResourceConfigForm constructor. * JsonapiResourceConfigForm constructor.
* *
...@@ -87,8 +95,11 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -87,8 +95,11 @@ class JsonapiResourceConfigForm extends EntityForm {
* @param \Drupal\Core\Config\ImmutableConfig $config * @param \Drupal\Core\Config\ImmutableConfig $config
* The config instance. * The config instance.
* @param \Symfony\Component\HttpFoundation\Request $request * @param \Symfony\Component\HttpFoundation\Request $request
* The HTTP request.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed config manager.
*/ */
public function __construct(EntityTypeBundleInfoInterface $bundle_info, ResourceTypeRepository $resource_type_repository, EntityFieldManager $field_manager, EntityTypeRepositoryInterface $entity_type_repository, ResourceFieldEnhancerManager $enhancer_manager, ImmutableConfig $config, Request $request) { public function __construct(EntityTypeBundleInfoInterface $bundle_info, ResourceTypeRepository $resource_type_repository, EntityFieldManager $field_manager, EntityTypeRepositoryInterface $entity_type_repository, ResourceFieldEnhancerManager $enhancer_manager, ImmutableConfig $config, Request $request, TypedConfigManagerInterface $typed_config_manager) {
$this->bundleInfo = $bundle_info; $this->bundleInfo = $bundle_info;
$this->resourceTypeRepository = $resource_type_repository; $this->resourceTypeRepository = $resource_type_repository;
$this->fieldManager = $field_manager; $this->fieldManager = $field_manager;
...@@ -96,6 +107,7 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -96,6 +107,7 @@ class JsonapiResourceConfigForm extends EntityForm {
$this->enhancerManager = $enhancer_manager; $this->enhancerManager = $enhancer_manager;
$this->config = $config; $this->config = $config;
$this->request = $request; $this->request = $request;
$this->typedConfigManager = $typed_config_manager;
} }
/** /**
...@@ -109,7 +121,8 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -109,7 +121,8 @@ class JsonapiResourceConfigForm extends EntityForm {
$container->get('entity_type.repository'), $container->get('entity_type.repository'),
$container->get('plugin.manager.resource_field_enhancer'), $container->get('plugin.manager.resource_field_enhancer'),
$container->get('config.factory')->get('jsonapi_extras.settings'), $container->get('config.factory')->get('jsonapi_extras.settings'),
$container->get('request_stack')->getCurrentRequest() $container->get('request_stack')->getCurrentRequest(),
$container->get('config.typed')
); );
} }
...@@ -124,22 +137,18 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -124,22 +137,18 @@ class JsonapiResourceConfigForm extends EntityForm {
$entity_type_id = $this->request->get('entity_type_id'); $entity_type_id = $this->request->get('entity_type_id');
$bundle = $this->request->get('bundle'); $bundle = $this->request->get('bundle');
/** @var JsonapiResourceConfig $entity */ /** @var \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity */
$entity = $this->getEntity(); $entity = $this->getEntity();
$resource_id = $entity->get('id'); $resource_id = $entity->get('id');
// If we are editing an entity we don't want the Entity Type and Bundle // If we are editing an entity we don't want the Entity Type and Bundle
// picker, that info is locked. // picker, that info is locked.
if (!$entity_type_id || !$bundle) { if (!$entity_type_id || !$bundle) {
if ($resource_id) { if (!$resource_id) {
list($entity_type_id, $bundle) = explode('--', $resource_id); // We can't build the form without an entity type and bundle.
$form['#title'] = $this->t('Edit %label resource config', ['%label' => $resource_id]); throw new \InvalidArgumentException('Unable to load entity type or bundle for the overrides form.');
}
else {
list($entity_type_id, $bundle) = $this->buildEntityTypeBundlePicker($form, $form_state);
if (!$entity_type_id) {
return $form;
}
} }
list($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)) { if ($entity_type_id && $resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle)) {
...@@ -161,6 +170,25 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -161,6 +170,25 @@ class JsonapiResourceConfigForm extends EntityForm {
return $form; 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} * {@inheritdoc}
*/ */
...@@ -183,19 +211,6 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -183,19 +211,6 @@ class JsonapiResourceConfigForm extends EntityForm {
$form_state->setRedirectUrl($resource_config->urlInfo('collection')); $form_state->setRedirectUrl($resource_config->urlInfo('collection'));
} }
/**
* Implements callback for Ajax event on entity type or bundle selection.
*
* @param array $form
* From render array.
*
* @return array
* Color selection section of the form.
*/
public function bundleCallback(array &$form) {
return $form['bundle_wrapper'];
}
/** /**
* Builds the part of the form that contains the overrides. * Builds the part of the form that contains the overrides.
* *
...@@ -217,7 +232,8 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -217,7 +232,8 @@ class JsonapiResourceConfigForm extends EntityForm {
}, $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle)); }, $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle));
} }
else { else {
$field_names = array_keys($entity_type->getKeys()); $field_names = array_keys($entity_type->getPropertiesToExport());
array_unshift($field_names, $entity_type->getKey('id'));
} }
$overrides_form['overrides']['entity'] = [ $overrides_form['overrides']['entity'] = [
...@@ -275,17 +291,20 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -275,17 +291,20 @@ class JsonapiResourceConfigForm extends EntityForm {
$overrides_form['overrides']['fields'] = [ $overrides_form['overrides']['fields'] = [
'#type' => 'details', '#type' => 'details',
'#title' => $this->t('Fields'), '#title' => $this->t('Fields'),
'#description' => $this->t('Override configuration for the resource fields.'),
'#open' => TRUE, '#open' => TRUE,
]; ];
$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'] = [ $overrides_form['overrides']['fields']['info'] = [
'#theme' => 'item_list', '#markup' => $markup,
'#items' => [
$this->t('<strong>Disabled</strong>: Check this if you want to disable this field completely. Disabling required fields will cause problems when writing to the resource.'),
$this->t('<strong>Alias</strong>: Overrides the field name with a custom name. Example: Change "field_tags" to "tags".'),
$this->t('<strong>Enhancer</strong>: Select an enhancer to manipulate the public output coming in and out.'),
],
]; ];
$overrides_form['overrides']['fields']['resourceFields'] = [ $overrides_form['overrides']['fields']['resourceFields'] = [
...@@ -315,7 +334,7 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -315,7 +334,7 @@ class JsonapiResourceConfigForm extends EntityForm {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function buildEntity(array $form, FormStateInterface $form_state) { public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var JsonapiResourceConfig $entity */ /** @var \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $entity */
$entity = parent::buildEntity($form, $form_state); $entity = parent::buildEntity($form, $form_state);
// Trim slashes from path. // Trim slashes from path.
...@@ -347,7 +366,7 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -347,7 +366,7 @@ class JsonapiResourceConfigForm extends EntityForm {
$overrides_form['disabled'] = [ $overrides_form['disabled'] = [
'#type' => 'checkbox', '#type' => 'checkbox',
'#title' => $this->t('Disabled'), '#title' => $this->t('Disabled'),
'#title_display' => 'hidden', '#title_display' => 'invisible',
'#default_value' => $resource_field['disabled'], '#default_value' => $resource_field['disabled'],
]; ];
$overrides_form['fieldName'] = [ $overrides_form['fieldName'] = [
...@@ -413,76 +432,6 @@ class JsonapiResourceConfigForm extends EntityForm { ...@@ -413,76 +432,6 @@ class JsonapiResourceConfigForm extends EntityForm {
return $overrides_form; return $overrides_form;
} }
/**
* Build the entity picker widget and return the entity type and bundle IDs.
*
* @param array $form
* The form passed by reference to update it.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The state of the form.
*
* @return array
* The entity types ID and the bundle ID.
*/
protected function buildEntityTypeBundlePicker(array &$form, FormStateInterface $form_state) {
$form['_entity_type_id'] = [
'#title' => $this->t('Entity Type'),
'#type' => 'select',
'#options' => $this->entityTypeRepository->getEntityTypeLabels(TRUE),
'#empty_option' => $this->t('- Select -'),
'#required' => TRUE,
'#ajax' => [
'callback' => '::bundleCallback',
'wrapper' => 'bundle-wrapper',
],
];
if (isset($parameter['entity_type_id'])) {
$form['_entity_type_id'] = [
'#type' => 'hidden',
'#value' => $parameter['entity_type_id'],
];
}
$form['bundle_wrapper'] = [
'#type' => 'container',
'#attributes' => ['id' => 'bundle-wrapper'],
];
if (!$entity_type_id = $form_state->getValue('_entity_type_id')) {
return [$entity_type_id, NULL];
}
$has_bundles = (bool) $this->entityTypeManager
->getDefinition($entity_type_id)->getBundleEntityType();
if ($has_bundles) {
$bundles = [];
$bundle_info = $this->bundleInfo->getBundleInfo($entity_type_id);
foreach ($bundle_info as $bundle_id => $info) {
$bundles[$bundle_id] = $info['translatable'] ? $this->t($info['label']) : $info['label'];
}
$form['bundle_wrapper']['_bundle_id'] = [
'#type' => 'select',
'#empty_option' => $this->t('- Select -'),
'#title' => $this->t('Bundle'),
'#options' => $bundles,
'#required' => TRUE,
'#ajax' => [
'callback' => '::bundleCallback',
'wrapper' => 'bundle-wrapper',
],
];
}
else {
$form['bundle_wrapper']['_bundle_id'] = [
'#type' => 'hidden',
'#value' => $entity_type_id,
];
}
$bundle = $has_bundles
? $form_state->getValue('_bundle_id')
: $entity_type_id;
return [$entity_type_id, $bundle];
}
/** /**
* AJAX callback to get the form settings for the enhancer for a field. * AJAX callback to get the form settings for the enhancer for a field.
* *
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
namespace Drupal\jsonapi_extras; namespace Drupal\jsonapi_extras;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceModifierInterface; use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository; use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
/** /**
* Replace the resource type repository for our own configurable version. * Replace the resource type repository for our own configurable version.
*/ */
class JsonapiExtrasServiceProvider implements ServiceModifierInterface { class JsonapiExtrasServiceProvider extends ServiceProviderBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -24,6 +24,28 @@ class JsonapiExtrasServiceProvider implements ServiceModifierInterface { ...@@ -24,6 +24,28 @@ class JsonapiExtrasServiceProvider implements ServiceModifierInterface {
// plugin manager. // plugin manager.
$definition->addArgument(new Reference('entity.repository')); $definition->addArgument(new Reference('entity.repository'));
$definition->addArgument(new Reference('plugin.manager.resource_field_enhancer')); $definition->addArgument(new Reference('plugin.manager.resource_field_enhancer'));
$definition->addArgument(new Reference('config.factory'));
}
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
$modules = $container->getParameter(('container.modules'));
if (isset($modules['schemata_json_schema'])) {
// Register field definition schema override.
$container
->register('serializer.normalizer.field_definition.schema_json.jsonapi_extras', 'Drupal\jsonapi_extras\Normalizer\SchemaFieldDefinitionNormalizer')
->addTag('normalizer', ['priority' => 32])
->addArgument(new Reference('jsonapi.resource_type.repository'));
// Register top-level schema override.
$container
->register('serializer.normalizer.schemata_schema_normalizer.schema_json.jsonapi_extras', 'Drupal\jsonapi_extras\Normalizer\SchemataSchemaNormalizer')
->addTag('normalizer', ['priority' => 100])
->addArgument(new Reference('jsonapi.resource_type.repository'));
} }
} }
......
...@@ -4,11 +4,8 @@ namespace Drupal\jsonapi_extras; ...@@ -4,11 +4,8 @@ namespace Drupal\jsonapi_extras;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository; use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -35,9 +32,10 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder { ...@@ -35,9 +32,10 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder {
/** /**
* Constructs new JsonapiResourceConfigListBuilder. * Constructs new JsonapiResourceConfigListBuilder.
* *
* @param EntityTypeInterface $entity_type * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type. * The entity type.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage * @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The storage.
* @param \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository $resource_type_repository * @param \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository $resource_type_repository
* The JSON API configurable resource type repository. * The JSON API configurable resource type repository.
* @param \Drupal\Core\Config\ImmutableConfig $config * @param \Drupal\Core\Config\ImmutableConfig $config
...@@ -81,26 +79,14 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder { ...@@ -81,26 +79,14 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function render() { public function render() {
$list = [ $list = [];
'#type' => 'container', $resource_status = [
'#attributes' => [ 'enabled' => t('Enabled Resources'),
'id' => 'jsonapi-resources-list', 'disabled' => t('Disabled resources'),
],
'#attached' => [
'library' => [
'jsonapi_extras/admin'
],
],
];
$list['heading'] = [
'#type' => 'html_tag',
'#tag' => 'h3',
'#value' => $this->t('Resources'),
]; ];
$title = $this->t('Filter resources by name, entity type, bundle or path.'); $title = $this->t('Filter resources by name, entity type, bundle or path.');
$list['filters']['text'] = [ $list['status']['filters']['text'] = [
'#type' => 'search', '#type' => 'search',
'#title' => $this->t('Filter'), '#title' => $this->t('Filter'),
'#title_display' => 'invisible', '#title_display' => 'invisible',
...@@ -114,31 +100,47 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder { ...@@ -114,31 +100,47 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder {
], ],
]; ];
$list['table'] = [ foreach ($resource_status as $status => $label) {
'#type' => 'table', $list[$status] = [
'#header' => [ '#type' => 'details',
'name' => $this->t('Name'), '#title' => $label,
'entity_type' => $this->t('Entity type'), '#open' => $status == 'enabled',
'bundle' => $this->t('Bundle'), '#attributes' => [
'path' => $this->t('Path'), 'id' => 'jsonapi-' . $status . '-resources-list',
'status' => $this->t('Status'),
'state' => $this->t('State'),
'operations' => $this->t('Operations'),
],
'#attributes' => [
'class' => [
'jsonapi-resources-table'
], ],
], '#attached' => [
'#attached' => [ 'library' => [
'library' => [ 'jsonapi_extras/admin',
'jsonapi_extras/admin', ],
] ],
], ];
];
$list[$status]['table'] = [
'#type' => 'table',
'#header' => [
'name' => $this->t('Name'),
'entity_type' => $this->t('Entity type'),
'bundle' => $this->t('Bundle'),
'path' => $this->t('Path'),
'status' => $this->t('Status'),
'state' => $this->t('State'),
'operations' => $this->t('Operations'),
],
'#attributes' => [
'class' => [
'jsonapi-resources-table',
],
],
'#attached' => [
'library' => [
'jsonapi_extras/admin',
],
],
];
}
$prefix = $this->config->get('path_prefix'); $prefix = $this->config->get('path_prefix');
foreach ($this->resourceTypeRepository->getResourceTypes(TRUE) as $resource_type) { foreach ($this->resourceTypeRepository->all() as $resource_type) {
/** @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType $resource_type */ /** @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType $resource_type */
$entity_type_id = $resource_type->getEntityTypeId(); $entity_type_id = $resource_type->getEntityTypeId();
$bundle = $resource_type->getBundle(); $bundle = $resource_type->getBundle();
...@@ -206,11 +208,10 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder { ...@@ -206,11 +208,10 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder {
} }
} }
$list['table'][] = $row; $list[$resource_config->get('disabled') ? 'disabled' : 'enabled']['table'][] = $row;
} }
return $list; return $list;
} }
} }
...@@ -21,6 +21,11 @@ trait EntityNormalizerTrait { ...@@ -21,6 +21,11 @@ trait EntityNormalizerTrait {
* The modified input data. * The modified input data.
*/ */
protected function prepareInput(array $data, ResourceType $resource_type) { protected function prepareInput(array $data, ResourceType $resource_type) {
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions */
$field_storage_definitions = \Drupal::service('entity_field.manager')
->getFieldStorageDefinitions(
$resource_type->getEntityTypeId()
);
$data_internal = []; $data_internal = [];
/** @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType $resource_type */ /** @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType $resource_type */
// Translate the public fields into the entity fields. // Translate the public fields into the entity fields.
...@@ -31,7 +36,19 @@ trait EntityNormalizerTrait { ...@@ -31,7 +36,19 @@ trait EntityNormalizerTrait {
} }
$internal_name = $resource_type->getInternalName($public_field_name); $internal_name = $resource_type->getInternalName($public_field_name);
$enhancer = $resource_type->getFieldEnhancer($public_field_name, 'publicName'); $enhancer = $resource_type->getFieldEnhancer($public_field_name, 'publicName');
$field_value = $enhancer ? $enhancer->prepareForInput($field_value) : $field_value;
if (isset($field_storage_definitions[$internal_name])) {
$field_storage_definition = $field_storage_definitions[$internal_name];
if ($field_storage_definition->getCardinality() === 1) {
$field_value = $enhancer ? $enhancer->prepareForInput($field_value) : $field_value;
}
elseif (is_array($field_value)) {
foreach ($field_value as $key => $individual_field_value) {
$field_value[$key] = $enhancer ? $enhancer->prepareForInput($individual_field_value) : $individual_field_value;
}
}
}
$data_internal[$internal_name] = $field_value; $data_internal[$internal_name] = $field_value;
} }
...@@ -49,9 +66,11 @@ trait EntityNormalizerTrait { ...@@ -49,9 +66,11 @@ trait EntityNormalizerTrait {
$entity_type_id = $resource_type->getEntityTypeId(); $entity_type_id = $resource_type->getEntityTypeId();
$bundle_id = $resource_type->getBundle(); $bundle_id = $resource_type->getBundle();
// The output depends on the configuration entity for caching. // The output depends on the configuration entity for caching.
$context['cacheable_metadata']->addCacheableDependency( if ($resource_config = $this->getResourceConfig($entity_type_id, $bundle_id)) {
$this->getResourceConfig($entity_type_id, $bundle_id) $context['cacheable_metadata']->addCacheableDependency(
); $resource_config
);
}
$context['cacheable_metadata']->addCacheableDependency( $context['cacheable_metadata']->addCacheableDependency(
\Drupal::config('jsonapi_extras.settings') \Drupal::config('jsonapi_extras.settings')
); );
......
...@@ -61,7 +61,7 @@ class FieldItemNormalizer extends NormalizerBase { ...@@ -61,7 +61,7 @@ class FieldItemNormalizer extends NormalizerBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function normalize($object, $format = NULL, array $context = array()) { public function normalize($object, $format = NULL, array $context = []) {
// First get the regular output. // First get the regular output.
$normalized_output = $this->subject->normalize($object, $format, $context); $normalized_output = $this->subject->normalize($object, $format, $context);
// Then detect if there is any enhancer to be applied here. // Then detect if there is any enhancer to be applied here.
......
<?php
namespace Drupal\jsonapi_extras\Normalizer;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType;
use Drupal\schemata_json_schema\Normalizer\jsonapi\FieldDefinitionNormalizer as SchemataJsonSchemaFieldDefinitionNormalizer;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
/**
* Applies field enhancer schema changes to field schema.
*/
class SchemaFieldDefinitionNormalizer extends SchemataJsonSchemaFieldDefinitionNormalizer {
/**
* The JSON API resource type repository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
*/
protected $resourceTypeRepository;
/**
* Constructs a SchemaFieldDefinitionNormalizer object.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepository $resource_type_repository
* A resource type repository.
*/
public function __construct(ResourceTypeRepository $resource_type_repository) {
$this->resourceTypeRepository = $resource_type_repository;
}
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []) {
$normalized = parent::normalize($entity, $format, $context);
// Load the resource type for this entity type and bundle.
$resource_type = $this->resourceTypeRepository->get($context['entityTypeId'], $context['bundleId']);
if (!$resource_type || !$resource_type instanceof ConfigurableResourceType) {
return $normalized;
}
$field_name = $context['name'];
$enhancer = $resource_type->getFieldEnhancer($field_name);
if (!$enhancer) {
return $normalized;
}
$original_field_schema = $normalized['properties']['attributes']['properties'][$field_name];
$field_schema = &$normalized['properties']['attributes']['properties'][$field_name];
$field_schema = $enhancer->getJsonSchema();
// Copy *some* properties from the original.
$copied_properties = ['title', 'description'];
foreach ($copied_properties as $property_name) {
if (!empty($original_field_schema[$property_name])) {
$field_schema[$property_name] = $original_field_schema[$property_name];
}
}
return $normalized;
}
}
<?php
namespace Drupal\jsonapi_extras\Normalizer;
use Drupal\schemata_json_schema\Normalizer\jsonapi\SchemataSchemaNormalizer as SchemataJsonSchemaSchemataSchemaNormalizer;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
/**
* Applies JSONAPI Extras attribute overrides to entity schemas.
*/
class SchemataSchemaNormalizer extends SchemataJsonSchemaSchemataSchemaNormalizer {
/**
* The JSON API resource type repository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
*/
protected $resourceTypeRepository;
/**
* Constructs a SchemataSchemaNormalizer object.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepository $resource_type_repository
* A resource repository.
*/
public function __construct(ResourceTypeRepository $resource_type_repository) {
$this->resourceTypeRepository = $resource_type_repository;
}
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []) {
$normalized = parent::normalize($entity, $format, $context);
// Load the resource type for this entity type and bundle.
$resource_type = $this->resourceTypeRepository->get($entity->getEntityTypeId(), $entity->getBundleId());
if (!$resource_type) {
return $normalized;
}
// Alter the attributes according to the resource config.
foreach (['attributes', 'relationships'] as $property_type) {
foreach ($normalized['properties'][$property_type]['properties'] as $fieldname => $schema) {
$properties = &$normalized['properties'][$property_type]['properties'];
unset($properties[$fieldname]);
if (!$resource_type->isFieldEnabled($fieldname)) {
// If the field is disabled, do nothing after removal.
continue;
}
else {
// Otherwise, substitute the public name.
$public_name = $resource_type->getPublicName($fieldname);
$properties[$public_name] = $schema;
}
}
}
return $normalized;
}
}
<?php
namespace Drupal\jsonapi_extras\Plugin;
/**
* Base class for date and time based resourceFieldEnhancer plugins.
*/
abstract class DateTimeEnhancerBase extends ResourceFieldEnhancerBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'dateTimeFormat' => \DateTime::ISO8601,
];
}
/**
* {@inheritdoc}
*/
abstract public function postProcess($value);
/**
* {@inheritdoc}
*/
abstract public function prepareForInput($value);
/**
* {@inheritdoc}
*/
public function getJsonSchema() {
return [
'type' => 'string',
];
}
/**
* {@inheritdoc}
*/
public function getSettingsForm(array $resource_field_info) {
$settings = empty($resource_field_info['enhancer']['settings'])
? $this->getConfiguration()
: $resource_field_info['enhancer']['settings'];
return [
'dateTimeFormat' => [
'#type' => 'textfield',
'#title' => $this->t('Format'),
'#description' => $this->t('Use a valid date format.'),
'#default_value' => $settings['dateTimeFormat'],
],
];
}
}
...@@ -23,6 +23,13 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF ...@@ -23,6 +23,13 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF
*/ */
protected $configuration; protected $configuration;
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [];
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -48,4 +55,11 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF ...@@ -48,4 +55,11 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF
return $this->configuration; return $this->configuration;
} }
/**
* {@inheritdoc}
*/
public function getSettingsForm(array $resource_field_info) {
return [];
}
} }