Skip to content
GitLab
Explore
Sign in
project
jsonapi_extras
Compare revisions
dbb3413a1244eb8decb9066f2543a8bc305ee107 to 8.x-1.x
Hide whitespace changes
Inline
Side-by-side
src/Plugin/Validation/Constraint/DuplicateFieldConstraint.php
0 → 100644
View file @
c7a91cc0
<?php
namespace
Drupal\jsonapi_extras\Plugin\Validation\Constraint
;
use
Symfony\Component\Validator\Constraint
;
/**
* The constraint object.
*
* @Constraint(
* id = "jsonapi_extras__duplicate_field",
* label = @Translation("Duplicate field", context = "Validation")
* )
*/
class
DuplicateFieldConstraint
extends
Constraint
{
/**
* The error message for the constraint.
*
* @var string
*/
public
$message
=
'The override must be unique.'
;
}
src/Plugin/Validation/Constraint/DuplicateFieldConstraintValidator.php
0 → 100644
View file @
c7a91cc0
<?php
namespace
Drupal\jsonapi_extras\Plugin\Validation\Constraint
;
use
Drupal\Core\Entity\EntityTypeManagerInterface
;
use
Symfony\Component\Validator\Constraint
;
use
Symfony\Component\Validator\ConstraintValidator
;
/**
* The validator.
*/
class
DuplicateFieldConstraintValidator
extends
ConstraintValidator
{
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected
$entityTypeManager
;
/**
* DuplicateFieldConstraintValidator constructor.
*/
public
function
__construct
(
EntityTypeManagerInterface
$entityTypeManager
=
NULL
)
{
$this
->
entityTypeManager
=
$entityTypeManager
?:
\Drupal
::
entityTypeManager
();
}
/**
* {@inheritdoc}
*/
public
function
validate
(
$entity_data
,
Constraint
$constraint
)
{
$resourceFields
=
$entity_data
[
'resourceFields'
];
$overrides
=
[];
// Get the field values.
foreach
(
$resourceFields
as
$field
=>
$data
)
{
// Only get the overridden fields.
if
(
$data
[
'fieldName'
]
!=
$data
[
'publicName'
])
{
// Store the publicName for comparison.
$overrides
[
$field
]
=
$data
[
'publicName'
];
}
}
// Compare the overrides and find any duplicate values.
$deduped_overrides
=
array_unique
(
$overrides
);
$dupes
=
array_diff_assoc
(
$overrides
,
$deduped_overrides
);
// Set an error if there are duplicates.
if
(
$dupes
)
{
foreach
(
$dupes
as
$field
=>
$value
)
{
$this
->
context
->
buildViolation
(
$constraint
->
message
)
->
atPath
(
"resourceFields.
$field
.publicName"
)
->
addViolation
();
}
}
// Now compare the overrides with the default names to validate no dupes
// exist.
foreach
(
$overrides
as
$field
=>
$override
)
{
if
(
array_key_exists
(
$override
,
$resourceFields
))
{
$this
->
context
->
buildViolation
(
$constraint
->
message
)
->
atPath
(
"resourceFields.
$field
.publicName"
)
->
addViolation
();
}
}
// Validate URL and resource type.
$resource_types
=
$this
->
entityTypeManager
->
getStorage
(
'jsonapi_resource_config'
)
->
loadByProperties
([
'disabled'
=>
FALSE
]);
foreach
(
$resource_types
as
$id
=>
$resource_type
)
{
if
(
$entity_data
[
'id'
]
==
$id
)
{
continue
;
}
if
(
$resource_type
->
get
(
'resourceType'
)
==
$entity_data
[
'resourceType'
])
{
$this
->
context
->
buildViolation
(
'There is already resource (@name) with this resource type.'
,
[
'@name'
=>
$resource_type
->
id
()]
)
->
atPath
(
'resourceType'
)
->
addViolation
();
}
if
(
$resource_type
->
get
(
'path'
)
==
$entity_data
[
'path'
])
{
$this
->
context
->
buildViolation
(
'There is already resource (@name) with this path.'
,
[
'@name'
=>
$resource_type
->
id
()])
->
atPath
(
'resourceType'
)
->
addViolation
();
}
}
}
}
src/Plugin/jsonapi/FieldEnhancer/DateTimeEnhancer.php
View file @
c7a91cc0
...
...
@@ -2,27 +2,18 @@
namespace
Drupal\jsonapi_extras\Plugin\jsonapi\FieldEnhancer
;
use
Drupal\jsonapi_extras
\Plugin\
ResourceField
EnhancerBase
;
use
Drupal\jsonapi_extras
\Plugin\
DateTime
EnhancerBase
;
/**
* Perform additional manipulations to
date
fields.
* Perform additional manipulations to
timestamp
fields.
*
* @ResourceFieldEnhancer(
* id = "date_time",
* label = @Translation("Date Time"),
* description = @Translation("Formats a date based the configured date format.")
* label = @Translation("Date Time
(Timestamp field)
"),
* description = @Translation("Formats a date based the configured date format
for timestamp fields
.")
* )
*/
class
DateTimeEnhancer
extends
ResourceFieldEnhancerBase
{
/**
* {@inheritdoc}
*/
public
function
defaultConfiguration
()
{
return
[
'dateTimeFormat'
=>
\DateTime
::
ISO8601
,
];
}
class
DateTimeEnhancer
extends
DateTimeEnhancerBase
{
/**
* {@inheritdoc}
...
...
@@ -44,31 +35,4 @@ class DateTimeEnhancer extends ResourceFieldEnhancerBase {
return
(
int
)
$date
->
format
(
'U'
);
}
/**
* {@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'
],
],
];
}
}
src/Plugin/jsonapi/FieldEnhancer/DateTimeFromStringEnhancer.php
0 → 100644
View file @
c7a91cc0
<?php
namespace
Drupal\jsonapi_extras\Plugin\jsonapi\FieldEnhancer
;
use
Drupal\jsonapi_extras
\Plugin\DateTimeEnhancerBase
;
/**
* Perform additional manipulations to datetime fields.
*
* @ResourceFieldEnhancer(
* id = "date_time_from_string",
* label = @Translation("Date Time (Date Time field)"),
* description = @Translation("Formats a date based the configured date format for date fields.")
* )
*/
class
DateTimeFromStringEnhancer
extends
DateTimeEnhancerBase
{
/**
* {@inheritdoc}
*/
public
function
postProcess
(
$value
)
{
$storage_timezone
=
new
\DateTimezone
(
DATETIME_STORAGE_TIMEZONE
);
$date
=
new
\DateTime
(
$value
,
$storage_timezone
);
$configuration
=
$this
->
getConfiguration
();
$output_timezone
=
new
\DateTimezone
(
drupal_get_user_timezone
());
$date
->
setTimezone
(
$output_timezone
);
return
$date
->
format
(
$configuration
[
'dateTimeFormat'
]);
}
/**
* {@inheritdoc}
*/
public
function
prepareForInput
(
$value
)
{
$date
=
new
\DateTime
(
$value
);
// Adjust the date for storage.
$storage_timezone
=
new
\DateTimezone
(
DATETIME_STORAGE_TIMEZONE
);
$date
->
setTimezone
(
$storage_timezone
);
return
$date
->
format
(
DATETIME_DATETIME_STORAGE_FORMAT
);
}
}
src/Plugin/jsonapi/FieldEnhancer/SingleNestedEnhancer.php
View file @
c7a91cc0
...
...
@@ -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'
=>
[
...
...
src/Plugin/jsonapi/FieldEnhancer/UuidLinkEnhancer.php
0 → 100644
View file @
c7a91cc0
<?php
namespace
Drupal\jsonapi_extras\Plugin\jsonapi\FieldEnhancer
;
use
Drupal\Core\Entity\EntityTypeManagerInterface
;
use
Drupal\Core\Plugin\ContainerFactoryPluginInterface
;
use
Drupal\jsonapi_extras
\Plugin\ResourceFieldEnhancerBase
;
use
Symfony\Component\DependencyInjection\ContainerInterface
;
/**
* Use UUID for internal link field value.
*
* @ResourceFieldEnhancer(
* id = "uuid_link",
* label = @Translation("UUID for link (link field only)"),
* description = @Translation("Use UUID for internal link field.")
* )
*/
class
UuidLinkEnhancer
extends
ResourceFieldEnhancerBase
implements
ContainerFactoryPluginInterface
{
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected
$entityTypeManager
;
/**
* {@inheritdoc}
*/
public
function
__construct
(
array
$configuration
,
$plugin_id
,
array
$plugin_definition
,
EntityTypeManagerInterface
$entity_type_manager
)
{
parent
::
__construct
(
$configuration
,
$plugin_id
,
$plugin_definition
);
$this
->
entityTypeManager
=
$entity_type_manager
;
}
/**
* {@inheritdoc}
*/
public
static
function
create
(
ContainerInterface
$container
,
array
$configuration
,
$plugin_id
,
$plugin_definition
)
{
return
new
static
(
$configuration
,
$plugin_id
,
$plugin_definition
,
$container
->
get
(
'entity_type.manager'
)
);
}
/**
* {@inheritdoc}
*/
public
function
postProcess
(
$value
)
{
if
(
isset
(
$value
[
'uri'
]))
{
// Check if it is a link to an entity.
preg_match
(
"/entity:(.*)\/(.*)/"
,
$value
[
'uri'
],
$parsed_uri
);
if
(
!
empty
(
$parsed_uri
))
{
$entity_type
=
$parsed_uri
[
1
];
$entity_id
=
$parsed_uri
[
2
];
$entity
=
$this
->
entityTypeManager
->
getStorage
(
$entity_type
)
->
load
(
$entity_id
);
if
(
!
is_null
(
$entity
))
{
$value
[
'uri'
]
=
'entity:'
.
$entity_type
.
'/'
.
$entity
->
bundle
()
.
'/'
.
$entity
->
uuid
();
}
// Remove the value.
else
{
$value
=
[
'uri'
=>
''
,
'title'
=>
''
,
'options'
=>
[],
];
}
}
}
return
$value
;
}
/**
* {@inheritdoc}
*/
public
function
prepareForInput
(
$value
)
{
if
(
isset
(
$value
[
'uri'
]))
{
// Check if it is a link to an entity.
preg_match
(
"/entity:(.*)\/(.*)\/(.*)/"
,
$value
[
'uri'
],
$parsed_uri
);
if
(
!
empty
(
$parsed_uri
))
{
$entity_type
=
$parsed_uri
[
1
];
$entity_uuid
=
$parsed_uri
[
3
];
$entities
=
$this
->
entityTypeManager
->
getStorage
(
$entity_type
)
->
loadByProperties
([
'uuid'
=>
$entity_uuid
]);
if
(
!
empty
(
$entities
))
{
$entity
=
array_shift
(
$entities
);
$value
[
'uri'
]
=
'entity:'
.
$entity_type
.
'/'
.
$entity
->
id
();
}
else
{
// If the entity has not been imported yet we unset the field value.
$value
=
[];
}
}
}
return
$value
;
}
/**
* {@inheritdoc}
*/
public
function
getJsonSchema
()
{
return
[
'type'
=>
'object'
,
];
}
}
src/ResourceType/ConfigurableResourceType.php
View file @
c7a91cc0
...
...
@@ -6,6 +6,7 @@ use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use
Drupal\jsonapi\ResourceType\ResourceType
;
use
Drupal\jsonapi_extras
\Entity\JsonapiResourceConfig
;
use
Drupal\jsonapi_extras
\Plugin\ResourceFieldEnhancerManager
;
use
Drupal\Core\Config\ConfigFactoryInterface
;
/**
* Defines a configurable resource type.
...
...
@@ -26,6 +27,13 @@ class ConfigurableResourceType extends ResourceType {
*/
protected
$enhancerManager
;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected
$configFactory
;
/**
* Instantiates a ResourceType object.
*
...
...
@@ -39,12 +47,20 @@ class ConfigurableResourceType extends ResourceType {
* The configuration entity.
* @param \Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager $enhancer_manager
* Plugin manager for enhancers.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public
function
__construct
(
$entity_type_id
,
$bundle
,
$deserialization_target_class
,
JsonapiResourceConfig
$resource_config
,
ResourceFieldEnhancerManager
$enhancer_manager
)
{
parent
::
__construct
(
$entity_type_id
,
$bundle
,
$deserialization_target_class
);
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
,
(
bool
)
$resource_config
->
get
(
'disabled'
)
);
$this
->
jsonapiResourceConfig
=
$resource_config
;
$this
->
enhancerManager
=
$enhancer_manager
;
$this
->
configFactory
=
$config_factory
;
if
(
$resource_config
->
get
(
'resourceType'
))
{
// Set the type name.
...
...
@@ -86,6 +102,30 @@ class ConfigurableResourceType extends ResourceType {
:
parent
::
isFieldEnabled
(
$field_name
);
}
/**
* {@inheritdoc}
*/
public
function
includeCount
()
{
return
$this
->
configFactory
->
get
(
'jsonapi_extras.settings'
)
->
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.
*
...
...
src/ResourceType/ConfigurableResourceTypeRepository.php
View file @
c7a91cc0
...
...
@@ -7,7 +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.
...
...
@@ -28,13 +29,51 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
*/
protected
$enhancerManager
;
/**
* The bundle manager.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected
$bundleManager
;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected
$configFactory
;
/**
* 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
)
{
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
;
}
/**
...
...
@@ -42,65 +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
* A
n array of resource typ
es.
* A
list of entity type ID and bundle tupl
es.
*/
p
ublic
function
get
ResourceTypes
(
$include_disabled
=
TRUE
)
{
p
rotected
function
get
EntityTypeBundleTuples
(
)
{
$entity_type_ids
=
array_keys
(
$this
->
entityTypeManager
->
getDefinitions
());
$resource_types
=
[];
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
));
$current_types
=
array_map
(
function
(
$bundle
)
use
(
$entity_type_id
,
$include_disabled
)
{
$resource_config_id
=
sprintf
(
'%s--%s'
,
$entity_type_id
,
$bundle
);
$resource_config
=
$this
->
entityRepository
->
loadEntityByConfigTarget
(
'jsonapi_resource_config'
,
$resource_config_id
);
$resource_config
=
$resource_config
?:
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
);
// 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
);
},
[]);
}
return
array_filter
(
$resource_types
);
/**
* {@inheritdoc}
*/
public
function
getPathPrefix
()
{
return
$this
->
configFactory
->
get
(
'jsonapi_extras.settings'
)
->
get
(
'path_prefix'
);
}
}
tests/phpunit.xml
0 → 100644
View file @
c7a91cc0
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit
colors=
"true"
>
<testsuites>
<testsuite
name=
"jsonapi_extras"
>
<directory>
./src/
</directory>
</testsuite>
</testsuites>
<!-- Filter for coverage reports. -->
<filter>
<blacklist>
<directory>
./vendor
</directory>
</blacklist>
<whitelist>
<directory>
../src
</directory>
</whitelist>
</filter>
</phpunit>
tests/src/Functional/JsonExtrasApiFunctionalTest.php
0 → 100644
View file @
c7a91cc0
<?php
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
;
use
Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTestBase
;
use
Drupal\user\Entity\Role
;
use
Drupal\user\Entity\User
;
use
Symfony\Component\Routing\Route
;
/**
* The test class for the main functionality.
*
* @group jsonapi_extras
*/
class
JsonExtrasApiFunctionalTest
extends
JsonApiFunctionalTestBase
{
public
static
$modules
=
[
'jsonapi_extras'
,
];
/**
* {@inheritdoc}
*/
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
);
$config
->
save
(
TRUE
);
$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'
]);
}
/**
* {@inheritdoc}
*
* Appends the 'application/vnd.api+json' if there's no Accept header.
*/
protected
function
drupalGet
(
$path
,
array
$options
=
[],
array
$headers
=
[])
{
if
(
empty
(
$headers
[
'Accept'
])
&&
empty
(
$headers
[
'accept'
]))
{
$headers
[
'Accept'
]
=
'application/vnd.api+json'
;
}
return
parent
::
drupalGet
(
$path
,
$options
,
$headers
);
}
/**
* Test the GET method.
*/
public
function
testRead
()
{
$num_articles
=
61
;
$this
->
createDefaultContent
(
$num_articles
,
5
,
TRUE
,
TRUE
,
static
::
IS_NOT_MULTILINGUAL
);
// 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 */
$route_collection
=
\Drupal
::
service
(
'router.route_provider'
)
->
getRoutesByPattern
(
'/api'
);
$this
->
assertInstanceOf
(
Route
::
class
,
$route_collection
->
get
(
'jsonapi.resource_list'
)
);
$this
->
drupalGet
(
'/jsonapi'
);
$this
->
assertSession
()
->
statusCodeEquals
(
404
);
// 2. Make sure the count is included in collections. This also tests the
// overridden paths.
$output
=
Json
::
decode
(
$this
->
drupalGet
(
'/api/articles'
));
$this
->
assertSame
(
$num_articles
,
(
int
)
$output
[
'meta'
][
'count'
]);
$this
->
assertSession
()
->
statusCodeEquals
(
200
);
// 3. Check disabled resources.
$this
->
drupalGet
(
'/api/taxonomy_vocabulary/taxonomy_vocabulary'
);
$this
->
assertSession
()
->
statusCodeEquals
(
404
);
// 4. Check renamed fields.
$output
=
Json
::
decode
(
$this
->
drupalGet
(
'/api/articles/'
.
$this
->
nodes
[
0
]
->
uuid
()));
$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
()));
$this
->
assertArrayNotHasKey
(
'uuid'
,
$output
[
'data'
][
'attributes'
]);
// 6. Test the field enhancers: DateTimeEnhancer.
$output
=
Json
::
decode
(
$this
->
drupalGet
(
'/api/articles/'
.
$this
->
nodes
[
2
]
->
uuid
()));
$timestamp
=
\DateTime
::
createFromFormat
(
'Y-m-d\TH:i:sO'
,
$output
[
'data'
][
'attributes'
][
'createdAt'
])
->
format
(
'U'
);
$this
->
assertSame
((
int
)
$timestamp
,
$this
->
nodes
[
2
]
->
getCreatedTime
());
// 7. Test the field enhancers: UuidLinkEnhancer.
$output
=
Json
::
decode
(
$this
->
drupalGet
(
'/api/articles/'
.
$this
->
nodes
[
3
]
->
uuid
()));
$expected_link
=
'entity:node/article/'
.
$this
->
nodes
[
2
]
->
uuid
();
$this
->
assertSame
(
$expected_link
,
$output
[
'data'
][
'attributes'
][
'link'
][
'uri'
]);
// 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'
]);
}
/**
* Test POST/PATCH.
*/
public
function
testWrite
()
{
$this
->
createDefaultContent
(
0
,
3
,
FALSE
,
FALSE
,
static
::
IS_NOT_MULTILINGUAL
);
// 1. Successful post.
$collection_url
=
Url
::
fromRoute
(
'jsonapi.articles.collection'
);
$body
=
[
'data'
=>
[
'type'
=>
'articles'
,
'attributes'
=>
[
'langcode'
=>
'en'
,
'title'
=>
'My custom title'
,
'default_langcode'
=>
'1'
,
'body'
=>
'Custom value'
,
'timestamp'
=>
'2017-12-23T08:45:17+0100'
,
],
'relationships'
=>
[
'contentType'
=>
[
'data'
=>
[
'type'
=>
'contentTypes'
,
'id'
=>
NodeType
::
load
(
'article'
)
->
uuid
(),
],
],
'owner'
=>
[
'data'
=>
[
'type'
=>
'user--user'
,
'id'
=>
User
::
load
(
1
)
->
uuid
()],
],
'tags'
=>
[
'data'
=>
[
[
'type'
=>
'taxonomy_term--tags'
,
'id'
=>
$this
->
tags
[
0
]
->
uuid
()],
[
'type'
=>
'taxonomy_term--tags'
,
'id'
=>
$this
->
tags
[
1
]
->
uuid
()],
],
],
],
],
];
$response
=
$this
->
request
(
'POST'
,
$collection_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
->
assertEquals
(
201
,
$response
->
getStatusCode
());
$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'
][
'timestamp'
]);
$created_node
=
Node
::
load
(
$created_response
[
'data'
][
'attributes'
][
'internalId'
]);
$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'
]);
}
/**
* Creates the JSON API Resource Config entities to override the resources.
*/
protected
static
function
overrideResources
()
{
// Disable the user resource.
JsonapiResourceConfig
::
create
([
'id'
=>
'taxonomy_vocabulary--taxonomy_vocabulary'
,
'disabled'
=>
TRUE
,
'path'
=>
'taxonomy_vocabulary/taxonomy_vocabulary'
,
'resourceType'
=>
'taxonomy_vocabulary--taxonomy_vocabulary'
,
])
->
save
();
// Override paths and fields in the articles resource.
JsonapiResourceConfig
::
create
([
'id'
=>
'node--article'
,
'disabled'
=>
FALSE
,
'path'
=>
'articles'
,
'resourceType'
=>
'articles'
,
'resourceFields'
=>
[
'nid'
=>
[
'fieldName'
=>
'nid'
,
'publicName'
=>
'internalId'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'uuid'
=>
[
'fieldName'
=>
'uuid'
,
'publicName'
=>
'uuid'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'vid'
=>
[
'fieldName'
=>
'vid'
,
'publicName'
=>
'vid'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'langcode'
=>
[
'fieldName'
=>
'langcode'
,
'publicName'
=>
'langcode'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'type'
=>
[
'fieldName'
=>
'type'
,
'publicName'
=>
'contentType'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'status'
=>
[
'fieldName'
=>
'status'
,
'publicName'
=>
'isPublished'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'title'
=>
[
'fieldName'
=>
'title'
,
'publicName'
=>
'title'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'uid'
=>
[
'fieldName'
=>
'uid'
,
'publicName'
=>
'owner'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'created'
=>
[
'fieldName'
=>
'created'
,
'publicName'
=>
'createdAt'
,
'enhancer'
=>
[
'id'
=>
'date_time'
,
'settings'
=>
[
'dateTimeFormat'
=>
'Y-m-d\TH:i:sO'
],
],
'disabled'
=>
FALSE
,
],
'changed'
=>
[
'fieldName'
=>
'changed'
,
'publicName'
=>
'updatedAt'
,
'enhancer'
=>
[
'id'
=>
'date_time'
,
'settings'
=>
[
'dateTimeFormat'
=>
'Y-m-d\TH:i:sO'
],
],
'disabled'
=>
FALSE
,
],
'promote'
=>
[
'fieldName'
=>
'promote'
,
'publicName'
=>
'isPromoted'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'sticky'
=>
[
'fieldName'
=>
'sticky'
,
'publicName'
=>
'sticky'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'revision_timestamp'
=>
[
'fieldName'
=>
'revision_timestamp'
,
'publicName'
=>
'revision_timestamp'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'revision_uid'
=>
[
'fieldName'
=>
'revision_uid'
,
'publicName'
=>
'revision_uid'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'revision_log'
=>
[
'fieldName'
=>
'revision_log'
,
'publicName'
=>
'revision_log'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'revision_translation_affected'
=>
[
'fieldName'
=>
'revision_translation_affected'
,
'publicName'
=>
'revision_translation_affected'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'default_langcode'
=>
[
'fieldName'
=>
'default_langcode'
,
'publicName'
=>
'default_langcode'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'path'
=>
[
'fieldName'
=>
'path'
,
'publicName'
=>
'path'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'body'
=>
[
'fieldName'
=>
'body'
,
'publicName'
=>
'body'
,
'enhancer'
=>
[
'id'
=>
'nested'
,
'settings'
=>
[
'path'
=>
'value'
]],
'disabled'
=>
FALSE
,
],
'field_link'
=>
[
'fieldName'
=>
'field_link'
,
'publicName'
=>
'link'
,
'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'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'field_image'
=>
[
'fieldName'
=>
'field_image'
,
'publicName'
=>
'image'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'field_recipes'
=>
[
'fieldName'
=>
'field_recipes'
,
'publicName'
=>
'recipes'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'field_tags'
=>
[
'fieldName'
=>
'field_tags'
,
'publicName'
=>
'tags'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
],
])
->
save
();
// Override the resource type in the node_type resource.
JsonapiResourceConfig
::
create
([
'id'
=>
'node_type--node_type'
,
'disabled'
=>
FALSE
,
'path'
=>
'contentTypes'
,
'resourceType'
=>
'contentTypes'
,
'resourceFields'
=>
[
'type'
=>
[
'fieldName'
=>
'type'
,
'publicName'
=>
'machineName'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'status'
=>
[
'fieldName'
=>
'status'
,
'publicName'
=>
'isEnabled'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
FALSE
,
],
'langcode'
=>
[
'fieldName'
=>
'langcode'
,
'publicName'
=>
'langcode'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
'uuid'
=>
[
'fieldName'
=>
'uuid'
,
'publicName'
=>
'uuid'
,
'enhancer'
=>
[
'id'
=>
''
],
'disabled'
=>
TRUE
,
],
],
])
->
save
();
}
}
Prev
1
2
Next