Skip to content
Commits on Source (74)
# JSON API Extras
This module provides extra functionality on top of JSON API. You should not need
this module to get an spec compliant JSON API.
This module provides extra functionality on top of JSON API. You should not
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:
......@@ -12,4 +13,5 @@ This module adds the following features:
- Lets you remove fields from the JSON API output.
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
include_count: false
jsonapi_extras.resource_config.*:
jsonapi_extras.jsonapi_resource_config.*:
type: config_entity
label: 'Resource Config config'
label: 'JSON API Resource Config'
mapping:
id:
type: string
label: 'Original Resource ID.'
disabled:
type: boolean
label: 'Disabled'
description: 'Is the resource disabled?'
path:
type: string
label: 'Path'
......@@ -22,19 +26,48 @@ jsonapi_extras.resource_config.*:
jsonapi_extras.resource_field:
type: mapping
mapping:
disabled:
type: boolean
label: 'Disabled'
description: 'Is the field disabled?'
fieldName:
type: string
label: 'Entity field name'
publicName:
type: string
label: 'Public attribute name'
postNormalizationCallbacks:
type: sequence
label: 'Post-normalization callbacks'
description: 'Set of static callbacks to be executed in sequence after normalization.'
sequence:
type: callable
enhancer:
type: mapping
label: 'Enhancer plugin'
description: 'A plugin that carries additional (de)normalization tasks.'
mapping:
id:
type: string
description: 'The enhancer plugin ID'
settings:
type: jsonapi_extras.enhancer_plugin.[%parent.id]
# TODO: Probably worth creating a data type to validate that it's a callable.
callable:
type: string
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:
type: config_object
label: 'JSON API Extras settings'
mapping:
path_prefix:
type: string
label: 'Path prefix'
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.'
span.label {
background-color: #e0e0d8;
border-radius: 5px;
color: #333;
font-weight: normal;
padding: 2px 5px;
}
span.label--overwritten {
background-color: #f39c12;
}
span.label--status {
background-color: #27ae60;
color: white;
}
span.label--status--disabled {
background-color: #c0392b;
}
/**
* @file
* JSON API Extras resources behaviors.
*/
(function ($, Drupal) {
'use strict';
/**
* Filters the resources tables by a text input search string.
*/
Drupal.behaviors.resourcesTableFilterByText = {
attach: function (context, settings) {
var $input = $('input.jsonapi-resources-filter-text', context).once('jsonapi-resources-filter-text');
var $table = $($input.attr('data-table'));
var $rows;
function filterViewList(e) {
var query = $(e.target).val().toLowerCase();
function showViewRow(index, row) {
var $row = $(row);
$row.closest('tr').toggle($row.is(":contains('" + query.toLowerCase() + "')"));
}
// Filter if the length of the query is at least 2 characters.
if (query.length >= 2) {
$rows.each(showViewRow);
}
else {
$rows.show();
}
}
if ($table.length) {
$rows = $table.find('tbody tr');
$input.on('keyup', filterViewList);
}
}
};
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
return function( elem ) {
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
}(jQuery, Drupal));
......@@ -2,5 +2,6 @@ name: JSON API Extras
type: module
description: Builds on top of JSON API to deliver extra functionality.
core: 8.x
package: Web services
dependencies:
- jsonapi
- jsonapi:jsonapi
admin:
version: VERSION
css:
theme:
css/jsonapi_extras.admin.css: {}
js:
js/jsonapi_extras.admin.js: {}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
entity.resource_config.add_form:
route_name: 'entity.resource_config.add_form'
title: 'Add Resource Config'
appears_on:
- entity.resource_config.collection
entity.resource_config.collection:
title: JSON API
description: 'Configure JSON API extras.'
entity.jsonapi_resource_config.collection:
title: JSON API Overwrites
description: 'Overwrite JSON API resources.'
parent: system.admin_config_services
route_name: entity.resource_config.collection
route_name: entity.jsonapi_resource_config.collection
jsonapi_extras.resources:
title: 'Resources'
route_name: entity.jsonapi_resource_config.collection
base_route: entity.jsonapi_resource_config.collection
jsonapi_extras.settings:
title: 'Settings'
route_name: jsonapi_extras.settings
base_route: entity.jsonapi_resource_config.collection
<?php
/**
* @file
* Module implementation file.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function jsonapi_extras_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'entity.jsonapi_resource_config.collection':
$output = '';
$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>';
return $output;
}
}
jsonapi_extras.settings:
path: '/admin/config/services/jsonapi/settings'
defaults:
_form: '\Drupal\jsonapi_extras\Form\JsonapiExtrasSettingsForm'
_title: 'Settings'
requirements:
_permission: 'administer site configuration'
services:
serializer.normalizer.field_item.jsonapi_extras:
class: Drupal\jsonapi_extras\Normalizer\FieldItemNormalizer
arguments:
- '@serializer.normalizer.field_item.jsonapi'
- '@entity_type.manager'
- '@plugin.manager.resource_field_enhancer'
tags:
- { 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: 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: jsonapi_normalizer_do_not_use_removal_imminent, priority: 22 }
plugin.manager.resource_field_enhancer:
class: Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager
parent: default_plugin_manager
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="jsonapi">
<description>Default PHP CodeSniffer configuration for JSON API Extras.</description>
<file>.</file>
<arg name="extensions" value="inc,install,module,php,profile,test,theme,yml"/>
<!--Blacklist of coding standard rules that are not yet fixed. -->
<rule ref="Drupal">
<exclude name="Drupal.Commenting.FunctionComment.IncorrectTypeHint"/>
<exclude name="Drupal.Commenting.FunctionComment.MissingReturnComment"/>
<exclude name="Drupal.NamingConventions.ValidVariableName.LowerCamelName"/>
</rule>
</ruleset>
<?php
namespace Drupal\jsonapi_extras\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Plugin annotation object for resource field enhancers.
*
* @see \Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerInterface
*
* @Annotation
*/
class ResourceFieldEnhancer extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the formatter type.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A short description of the formatter type.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description;
/**
* The name of the field formatter class.
*
* This is not provided manually, it will be added by the discovery mechanism.
*
* @var string
*/
public $class;
}
<?php
namespace Drupal\jsonapi_extras\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
/**
* Defines the JSON API Resource Config entity.
*
* @ConfigEntityType(
* id = "jsonapi_resource_config",
* label = @Translation("JSON API Resource Config"),
* handlers = {
* "list_builder" = "Drupal\jsonapi_extras\JsonapiResourceConfigListBuilder",
* "form" = {
* "add" = "Drupal\jsonapi_extras\Form\JsonapiResourceConfigForm",
* "edit" = "Drupal\jsonapi_extras\Form\JsonapiResourceConfigForm",
* "delete" = "Drupal\jsonapi_extras\Form\JsonapiResourceConfigDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\jsonapi_extras\JsonapiResourceConfigHtmlRouteProvider",
* },
* },
* config_prefix = "jsonapi_resource_config",
* admin_permission = "administer site configuration",
* static_cache = TRUE,
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/admin/config/services/jsonapi/{jsonapi_resource_config}",
* "add-form" = "/admin/config/services/jsonapi/add/{entity_type_id}/{bundle}",
* "edit-form" = "/admin/config/services/jsonapi/{jsonapi_resource_config}/edit",
* "delete-form" = "/admin/config/services/jsonapi/{jsonapi_resource_config}/delete",
* "collection" = "/admin/config/services/jsonapi"
* }
* )
*/
class JsonapiResourceConfig extends ConfigEntityBase {
/**
* The JSON API Resource Config ID.
*
* @var string
*/
protected $id;
/**
* The path for the resource.
*
* @var string
*/
protected $path;
/**
* The type for the resource.
*
* @var string
*/
protected $resourceType;
/**
* Resource fields.
*
* @var array
*/
protected $resourceFields = [];
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
\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
/**
* @file
* Contains \Drupal\jsonapi_extras\Entity\ResourceConfig.
*/
namespace Drupal\jsonapi_extras\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\Entity\ConfigEntityBase;
/**
* Defines the Resource Config entity.
*
* @ConfigEntityType(
* id = "resource_config",
* label = @Translation("Resource Config"),
* handlers = {
* "list_builder" = "Drupal\jsonapi_extras\ResourceConfigListBuilder",
* "form" = {
* "add" = "Drupal\jsonapi_extras\Form\ResourceConfigForm",
* "edit" = "Drupal\jsonapi_extras\Form\ResourceConfigForm",
* "delete" = "Drupal\jsonapi_extras\Form\ResourceConfigDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\jsonapi_extras\ResourceConfigHtmlRouteProvider",
* },
* },
* config_prefix = "resource_config",
* admin_permission = "administer site configuration",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/admin/config/services/resource_config/{resource_config}",
* "add-form" = "/admin/config/services/resource_config/add",
* "edit-form" = "/admin/config/services/resource_config/{resource_config}/edit",
* "delete-form" = "/admin/config/services/resource_config/{resource_config}/delete",
* "collection" = "/admin/config/services/resource_config"
* }
* )
*/
class ResourceConfig extends ConfigEntityBase {
/**
* The Resource Config ID.
*
* @var string
*/
protected $id;
/**
* The path for the resource.
*
* @var string
*/
protected $path;
/**
* The type for the resource.
*
* @var string
*/
protected $resourceType;
/**
* Resource fields.
*
* @var array
*/
protected $resourceFields = [];
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
parent::__construct(static::addDefaults($values), $entity_type);
}
/**
* {@inheritdoc}
*/
public static function addDefaults(array $values) {
$default_field = [
'data' => [
'column' => 'value',
],
'id' => 'field',
];
if (!empty($values['resourceFields'])) {
$values['resourceFields'] = array_map(function ($value) use ($default_field) {
if (!is_array($value)) {
// Avoid PHP blowing up.
return $value;
}
$defaults = NestedArray::mergeDeep($default_field, $value);
// If the field has a callback, that makes it a callback resource field.
if (!empty($defaults['callback'])) {
$defaults['id'] = 'callback';
unset($defaults['data']);
}
// If the process callbacks are empty, remove them.
if (isset($defaults['processCallbacks']) && !$defaults['processCallbacks']) {
unset($defaults['processCallbacks']);
}
return $defaults;
}, $values['resourceFields']);
}
return $values;
}
}
<?php
namespace Drupal\jsonapi_extras\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ProxyClass\Routing\RouteBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure JSON API settings for this site.
*/
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}
*/
protected function getEditableConfigNames() {
return ['jsonapi_extras.settings'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'jsonapi_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('jsonapi_extras.settings');
$form['path_prefix'] = [
'#title' => $this->t('Path prefix'),
'#type' => 'textfield',
'#required' => TRUE,
'#field_prefix' => '/',
'#description' => $this->t('The path prefix for JSON API.'),
'#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);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($path_prefix = $form_state->getValue('path_prefix')) {
$this->config('jsonapi_extras.settings')
->set('path_prefix', trim($path_prefix, '/'))
->save();
}
$this->config('jsonapi_extras.settings')
->set('include_count', $form_state->getValue('include_count'))
->save();
// Rebuild the router.
$this->routerBuilder->setRebuildNeeded();
parent::submitForm($form, $form_state);
}
}
<?php
/**
* @file
* Contains \Drupal\jsonapi_extras\Form\ResourceConfigDeleteForm.
*/
namespace Drupal\jsonapi_extras\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
......@@ -12,29 +7,29 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Builds the form to delete Resource Config entities.
* Builds the form to delete JSON API Resource Config entities.
*/
class ResourceConfigDeleteForm extends EntityConfirmFormBase {
class JsonapiResourceConfigDeleteForm extends EntityConfirmFormBase {
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
return $this->t('Are you sure you want to revert %id to default?', ['%id' => $this->entity->id()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.resource_config.collection');
return new Url('entity.jsonapi_resource_config.collection');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
return $this->t('Revert');
}
/**
......@@ -42,15 +37,9 @@ class ResourceConfigDeleteForm extends EntityConfirmFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->delete();
drupal_set_message(
$this->t('content @type: deleted @label.',
[
'@type' => $this->entity->bundle(),
'@label' => $this->entity->label(),
]
)
);
drupal_set_message($this->t('Resource %id has been reverted to default.', [
'%id' => $this->entity->id(),
]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
......