Skip to content
Commits on Source (17)
{
"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"
}
}
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:
class: Drupal\jsonapi_extras\Normalizer\FieldItemNormalizer
arguments:
......@@ -14,18 +6,18 @@ services:
- '@entity_type.manager'
- '@plugin.manager.resource_field_enhancer'
tags:
- { name: normalizer, priority: 25 }
- { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 25 }
serializer.normalizer.entity.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\ContentEntityNormalizer
arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager']
tags:
- { name: normalizer, priority: 22 }
- { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 22 }
serializer.normalizer.config_entity.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\ConfigEntityNormalizer
arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager']
tags:
- { name: normalizer, priority: 22 }
- { name: jsonapi_normalizer_do_not_use_removal_imminent, priority: 22 }
plugin.manager.resource_field_enhancer:
class: Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager
......
......@@ -89,4 +89,18 @@ class JsonapiResourceConfig extends ConfigEntityBase {
$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 \Drupal\jsonapi_extras\ResourceType\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;
}
}
......@@ -5,7 +5,7 @@ namespace Drupal\jsonapi_extras\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteBuilder;
use Drupal\Core\ProxyClass\Routing\RouteBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -20,7 +20,7 @@ class JsonapiExtrasSettingsForm extends ConfigFormBase {
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Routing\RouteBuilder $router_builder
* @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) {
......
......@@ -143,16 +143,12 @@ class JsonapiResourceConfigForm extends EntityForm {
// 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) {
list($entity_type_id, $bundle) = explode('--', $resource_id);
$form['#title'] = $this->t('Edit %label resource config', ['%label' => $resource_id]);
}
else {
list($entity_type_id, $bundle) = $this->buildEntityTypeBundlePicker($form, $form_state);
if (!$entity_type_id) {
return $form;
}
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.');
}
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)) {
......@@ -215,19 +211,6 @@ class JsonapiResourceConfigForm extends EntityForm {
$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.
*
......@@ -249,7 +232,8 @@ class JsonapiResourceConfigForm extends EntityForm {
}, $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle));
}
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'] = [
......@@ -448,78 +432,6 @@ class JsonapiResourceConfigForm extends EntityForm {
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('%label', ['%label' => $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.
*
......
......@@ -140,7 +140,7 @@ class JsonapiResourceConfigListBuilder extends ConfigEntityListBuilder {
}
$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 */
$entity_type_id = $resource_type->getEntityTypeId();
$bundle = $resource_type->getBundle();
......
......@@ -69,9 +69,9 @@ class SingleNestedEnhancer extends ResourceFieldEnhancerBase {
* {@inheritdoc}
*/
public function getSettingsForm(array $resource_field_info) {
$settings = empty($resource_field_info['settings'])
$settings = empty($resource_field_info['enhancer']['settings'])
? $this->getConfiguration()
: $resource_field_info['settings'];
: $resource_field_info['enhancer']['settings'];
return [
'path' => [
......
......@@ -51,7 +51,12 @@ class ConfigurableResourceType extends ResourceType {
* The configuration factory.
*/
public function __construct($entity_type_id, $bundle, $deserialization_target_class, JsonapiResourceConfig $resource_config, ResourceFieldEnhancerManager $enhancer_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($entity_type_id, $bundle, $deserialization_target_class);
parent::__construct(
$entity_type_id,
$bundle,
$deserialization_target_class,
(bool) $resource_config->get('disabled')
);
$this->jsonapiResourceConfig = $resource_config;
$this->enhancerManager = $enhancer_manager;
......@@ -106,6 +111,21 @@ class ConfigurableResourceType extends ResourceType {
->get('include_count');
}
/**
* {@inheritdoc}
*/
public function getPath() {
$resource_config = $this->getJsonapiResourceConfig();
if (!$resource_config) {
return parent::getPath();
}
$config_path = $resource_config->get('path');
if (!$config_path) {
return parent::getPath();
}
return $config_path;
}
/**
* Get the resource field configuration.
*
......
......@@ -7,8 +7,8 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
/**
* Provides a repository of JSON API configurable resource types.
......@@ -29,6 +29,13 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
*/
protected $enhancerManager;
/**
* The bundle manager.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $bundleManager;
/**
* The configuration factory.
*
......@@ -37,20 +44,36 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
protected $configFactory;
/**
* A list of resource types.
* A list of all resource types.
*
* @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType[]
*/
protected $resourceTypes;
/**
* A list of only enabled resource types.
*
* @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceType[]
*/
protected $enabledResourceTypes;
/**
* A list of all resource configuration entities.
*
* @var \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig[]
*/
protected $resourceConfigs;
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_manager, EntityRepositoryInterface $entity_repository, ResourceFieldEnhancerManager $enhancer_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($entity_type_manager, $bundle_manager);
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_manager, EntityFieldManagerInterface $entity_field_manager, EntityRepositoryInterface $entity_repository, ResourceFieldEnhancerManager $enhancer_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($entity_type_manager, $bundle_manager, $entity_field_manager);
$this->entityRepository = $entity_repository;
$this->enhancerManager = $enhancer_manager;
$this->configFactory = $config_factory;
$this->entityFieldManager = $entity_field_manager;
$this->bundleManager = $bundle_manager;
}
/**
......@@ -58,78 +81,92 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
*/
public function all() {
if (!$this->all) {
$this->all = $this->getResourceTypes(FALSE);
foreach ($this->getEntityTypeBundleTuples() as $tuple) {
list($entity_type_id, $bundle) = $tuple;
$resource_config_id = sprintf('%s--%s', $entity_type_id, $bundle);
$this->all[] = new ConfigurableResourceType(
$entity_type_id,
$bundle,
$this->entityTypeManager->getDefinition($entity_type_id)->getClass(),
$this->getResourceConfig($resource_config_id),
$this->enhancerManager,
$this->configFactory
);
}
foreach ($this->all as $resource_type) {
$relatable_resource_types = $this->calculateRelatableResourceTypes($resource_type);
$resource_type->setRelatableResourceTypes($relatable_resource_types);
}
}
return $this->all;
}
/**
* {@inheritdoc}
* Get a single resource configuration entity by its ID.
*
* @param string $resource_config_id
* The configuration entity ID.
*
* @return \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function get($entity_type_id, $bundle) {
if (empty($entity_type_id)) {
throw new PreconditionFailedHttpException('Server error. The current route is malformed.');
}
protected function getResourceConfig($resource_config_id) {
$resource_configs = $this->getResourceConfigs();
return isset($resource_configs[$resource_config_id]) ?
$resource_configs[$resource_config_id] :
new NullJsonapiResourceConfig([], '');
}
foreach ($this->getResourceTypes() as $resource) {
if ($resource->getEntityTypeId() == $entity_type_id && $resource->getBundle() == $bundle) {
return $resource;
/**
* Load all resource configuration entities.
*
* @return \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig[]
* The resource config entities.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
function getResourceConfigs() {
if (!$this->resourceConfigs) {
$resource_config_ids = [];
foreach ($this->getEntityTypeBundleTuples() as $tuple) {
list($entity_type_id, $bundle) = $tuple;
$resource_config_ids[] = sprintf('%s--%s', $entity_type_id, $bundle);
}
$this->resourceConfigs = $this->entityTypeManager
->getStorage('jsonapi_resource_config')
->loadMultiple($resource_config_ids);
}
return NULL;
return $this->resourceConfigs;
}
/**
* Returns an array of resource types.
*
* @param bool $include_disabled
* TRUE to included disabled resource types.
* Entity type ID and bundle iterator.
*
* @return array
* An array of resource types.
* A list of entity type ID and bundle tuples.
*/
public function getResourceTypes($include_disabled = TRUE) {
if (isset($this->resourceTypes)) {
return $this->resourceTypes;
}
protected function getEntityTypeBundleTuples() {
$entity_type_ids = array_keys($this->entityTypeManager->getDefinitions());
$resource_types = [];
$resource_config_ids = [];
foreach ($entity_type_ids as $entity_type_id) {
// For each entity type return as many tuples as bundles.
return array_reduce($entity_type_ids, function ($carry, $entity_type_id) {
$bundles = array_keys($this->bundleManager->getBundleInfo($entity_type_id));
$resource_config_ids = array_merge($resource_config_ids, array_map(function ($bundle) use ($entity_type_id) {
return sprintf('%s--%s', $entity_type_id, $bundle);
}, $bundles));
}
$resource_configs = $this->entityTypeManager->getStorage('jsonapi_resource_config')->loadMultiple($resource_config_ids);
foreach ($entity_type_ids as $entity_type_id) {
$bundles = array_keys($this->bundleManager->getBundleInfo($entity_type_id));
$current_types = array_map(function ($bundle) use ($entity_type_id, $include_disabled, $resource_configs) {
$resource_config_id = sprintf('%s--%s', $entity_type_id, $bundle);
$resource_config = isset($resource_configs[$resource_config_id]) ? $resource_configs[$resource_config_id] : new NullJsonapiResourceConfig([], '');
if (!$include_disabled && $resource_config->get('disabled')) {
return NULL;
}
return new ConfigurableResourceType(
$entity_type_id,
$bundle,
$this->entityTypeManager->getDefinition($entity_type_id)->getClass(),
$resource_config,
$this->enhancerManager,
$this->configFactory
);
// Get all the tuples for the current entity type.
$tuples = array_map(function ($bundle) use ($entity_type_id) {
return [$entity_type_id, $bundle];
}, $bundles);
$resource_types = array_merge($resource_types, $current_types);
}
// Append the tuples to the aggregated list.
return array_merge($carry, $tuples);
}, []);
}
$this->resourceTypes = array_filter($resource_types);
return $this->resourceTypes;
/**
* {@inheritdoc}
*/
public function getPathPrefix() {
return $this->configFactory
->get('jsonapi_extras.settings')
->get('path_prefix');
}
}
......@@ -3,7 +3,10 @@
namespace Drupal\Tests\jsonapi_extras\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\jsonapi_extras\Entity\JsonapiResourceConfig;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
......@@ -28,6 +31,43 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
*/
protected function setUp() {
parent::setUp();
// Add vocabs field to the tags.
$this->createEntityReferenceField(
'taxonomy_term',
'tags',
'vocabs',
'Vocabularies',
'taxonomy_vocabulary',
'default',
[
'target_bundles' => [
'tags' => 'taxonomy_vocabulary',
],
'auto_create' => TRUE,
],
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
FieldStorageConfig::create([
'field_name' => 'field_timestamp',
'entity_type' => 'node',
'type' => 'timestamp',
'settings' => [],
'cardinality' => 1,
])->save();
$field_config = FieldConfig::create([
'field_name' => 'field_timestamp',
'label' => 'Timestamp',
'entity_type' => 'node',
'bundle' => 'article',
'required' => FALSE,
'settings' => [],
'description' => '',
]);
$field_config->save();
$config = \Drupal::configFactory()->getEditable('jsonapi_extras.settings');
$config->set('path_prefix', 'api');
$config->set('include_count', TRUE);
......@@ -35,6 +75,8 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
$this->grantPermissions(Role::load(Role::ANONYMOUS_ID), ['access jsonapi resource list']);
static::overrideResources();
$this->resetAll();
$role = $this->user->get('roles')[0]->entity;
$this->grantPermissions($role, ['administer nodes', 'administer site configuration']);
}
/**
......@@ -58,6 +100,8 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
// Make the link for node/3 to point to an entity.
$this->nodes[3]->field_link->setValue(['uri' => 'entity:node/' . $this->nodes[2]->id()]);
$this->nodes[3]->save();
$this->nodes[40]->uid->set(0, 1);
$this->nodes[40]->save();
// 1. Make sure the api root is under '/api' and not '/jsonapi'.
/** @var \Symfony\Component\Routing\RouteCollection $route_collection */
......@@ -76,7 +120,7 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
$this->assertSession()->statusCodeEquals(200);
// 3. Check disabled resources.
$this->drupalGet('/api/action/action');
$this->drupalGet('/api/taxonomy_vocabulary/taxonomy_vocabulary');
$this->assertSession()->statusCodeEquals(404);
// 4. Check renamed fields.
......@@ -84,6 +128,9 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
$this->assertArrayNotHasKey('type', $output['data']['attributes']);
$this->assertArrayHasKey('contentType', $output['data']['relationships']);
$this->assertSame('contentTypes', $output['data']['relationships']['contentType']['data']['type']);
$output = Json::decode($this->drupalGet('/api/contentTypes/' . $this->nodes[0]->type->entity->uuid()));
$this->assertArrayNotHasKey('type', $output['data']['attributes']);
$this->assertSame('article', $output['data']['attributes']['machineName']);
// 5. Check disabled fields.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[1]->uuid()));
......@@ -103,6 +150,74 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
// 8. Test the field enhancers: SingleNestedEnhancer.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[3]->uuid()));
$this->assertInternalType('string', $output['data']['attributes']['body']);
// 9. Test the related endpoint.
// This tests the overridden resource name, the overridden field names and
// the disabled fields.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[4]->uuid() . '/contentType'));
$this->assertArrayNotHasKey('type', $output['data']['attributes']);
$this->assertSame('article', $output['data']['attributes']['machineName']);
$this->assertSame('contentTypes', $output['data']['type']);
$this->assertArrayNotHasKey('uuid', $output['data']['attributes']);
// 10. Test the relationships endpoint.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[4]->uuid() . '/relationships/contentType'));
$this->assertSame('contentTypes', $output['data']['type']);
$this->assertArrayHasKey('id', $output['data']);
// 11. Test the related endpoint on a multiple cardinality relationship.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[5]->uuid() . '/tags'));
$this->assertCount(count($this->nodes[5]->get('field_tags')->getValue()), $output['data']);
$this->assertSame('taxonomy_term--tags', $output['data'][0]['type']);
// 12. Test the relationships endpoint.
$output = Json::decode($this->drupalGet('/api/articles/' . $this->nodes[5]->uuid() . '/relationships/tags'));
$this->assertCount(count($this->nodes[5]->get('field_tags')->getValue()), $output['data']);
$this->assertArrayHasKey('id', $output['data'][0]);
// 13. Test a disabled related resource of single cardinality.
$this->drupalGet('/api/taxonomy_term/tags/' . $this->tags[0]->uuid() . '/vid');
$this->assertSession()->statusCodeEquals(404);
$this->drupalGet('/api/taxonomy_term/tags/' . $this->tags[0]->uuid() . '/relationships/vid');
$this->assertSession()->statusCodeEquals(404);
// 14. Test a disabled related resource of multiple cardinality.
$this->tags[1]->vocabs->set(0, 'tags');
$this->tags[1]->save();
$output = Json::decode($this->drupalGet('/api/taxonomy_term/tags/' . $this->tags[0]->uuid() . '/vocabs'));
$this->assertTrue(empty($output['data']));
$output = Json::decode($this->drupalGet('/api/taxonomy_term/tags/' . $this->tags[0]->uuid() . '/relationships/vocabs'));
$this->assertTrue(empty($output['data']));
// 15. Test included resource.
$output = Json::decode($this->drupalGet(
'/api/articles/' . $this->nodes[6]->uuid(),
['query' => ['include' => 'owner']]
));
$this->assertSame('user--user', $output['included'][0]['type']);
// 16. Test disabled included resources.
$output = Json::decode($this->drupalGet(
'/api/taxonomy_term/tags/' . $this->tags[0]->uuid(),
['query' => ['include' => 'vocabs,vid']]
));
$this->assertArrayNotHasKey('included', $output);
// 17. Test nested filters with renamed field.
$output = Json::decode($this->drupalGet(
'/api/articles',
[
'query' => [
'filter' => [
'owner.name' => [
'value' => User::load(1)->getAccountName(),
],
],
],
]
));
// There is only one article for the admin.
$this->assertSame($this->nodes[40]->uuid(), $output['data'][0]['id']);
}
/**
......@@ -118,11 +233,9 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
'attributes' => [
'langcode' => 'en',
'title' => 'My custom title',
'isPublished' => '1',
'isPromoted' => '1',
'default_langcode' => '1',
'body' => 'Custom value',
'updatedAt' => '2017-12-23T08:45:17+0100',
'timestamp' => '2017-12-23T08:45:17+0100',
],
'relationships' => [
'contentType' => [
......@@ -153,9 +266,25 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
$this->assertArrayHasKey('internalId', $created_response['data']['attributes']);
$this->assertCount(2, $created_response['data']['relationships']['tags']['data']);
$this->assertSame($created_response['data']['links']['self'], $response->getHeader('Location')[0]);
$date = new \DateTime($body['data']['attributes']['updatedAt']);
$date = new \DateTime($body['data']['attributes']['timestamp']);
$created_node = Node::load($created_response['data']['attributes']['internalId']);
$this->assertSame((int) $date->format('U'), (int) $created_node->getChangedTime());
$this->assertSame((int) $date->format('U'), (int) $created_node->get('field_timestamp')->value);
// 2. Successful relationships PATCH.
$uuid = $created_response['data']['id'];
$relationships_url = Url::fromUserInput('/api/articles/' . $uuid . '/relationships/tags');
$body = [
'data' => [
['type' => 'taxonomy_term--tags', 'id' => $this->tags[2]->uuid()]
],
];
$response = $this->request('POST', $relationships_url, [
'body' => Json::encode($body),
'auth' => [$this->user->getUsername(), $this->user->pass_raw],
'headers' => ['Content-Type' => 'application/vnd.api+json'],
]);
$created_response = Json::decode((string) $response->getBody());
$this->assertCount(3, $created_response['data']);
}
/**
......@@ -164,10 +293,10 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
protected static function overrideResources() {
// Disable the user resource.
JsonapiResourceConfig::create([
'id' => 'action--action',
'id' => 'taxonomy_vocabulary--taxonomy_vocabulary',
'disabled' => TRUE,
'path' => 'action/action',
'resourceType' => 'action--action',
'path' => 'taxonomy_vocabulary/taxonomy_vocabulary',
'resourceType' => 'taxonomy_vocabulary--taxonomy_vocabulary',
])->save();
// Override paths and fields in the articles resource.
JsonapiResourceConfig::create([
......@@ -302,6 +431,15 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
'enhancer' => ['id' => 'uuid_link'],
'disabled' => FALSE,
],
'field_timestamp' => [
'fieldName' => 'field_timestamp',
'publicName' => 'timestamp',
'enhancer' => [
'id' => 'date_time',
'settings' => ['dateTimeFormat' => 'Y-m-d\TH:i:sO'],
],
'disabled' => FALSE,
],
'comment' => [
'fieldName' => 'comment',
'publicName' => 'comment',
......@@ -335,27 +473,15 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
'path' => 'contentTypes',
'resourceType' => 'contentTypes',
'resourceFields' => [
'id' => [
'fieldName' => 'id',
'type' => [
'fieldName' => 'type',
'publicName' => 'machineName',
'enhancer' => ['id' => ''],
'disabled' => FALSE,
],
'label' => [
'fieldName' => 'label',
'publicName' => 'label',
'enhancer' => ['id' => ''],
'disabled' => FALSE,
],
'revision' => [
'fieldName' => 'revision',
'publicName' => 'revision',
'enhancer' => ['id' => ''],
'disabled' => TRUE,
],
'bundle' => [
'fieldName' => 'bundle',
'publicName' => 'bundle',
'status' => [
'fieldName' => 'status',
'publicName' => 'isEnabled',
'enhancer' => ['id' => ''],
'disabled' => FALSE,
],
......@@ -365,12 +491,6 @@ class JsonExtrasApiFunctionalTest extends JsonApiFunctionalTestBase {
'enhancer' => ['id' => ''],
'disabled' => TRUE,
],
'default_langcode' => [
'fieldName' => 'default_langcode',
'publicName' => 'default_langcode',
'enhancer' => ['id' => ''],
'disabled' => TRUE,
],
'uuid' => [
'fieldName' => 'uuid',
'publicName' => 'uuid',
......