From d088448ec2d2d459ee5fdca1b509c5253dd13f2d Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 14:55:01 +0200 Subject: [PATCH 01/30] RDF Entity module requires PHP >=7.1. --- rdf_entity.info.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/rdf_entity.info.yml b/rdf_entity.info.yml index 17cebe2b..7c3c7849 100644 --- a/rdf_entity.info.yml +++ b/rdf_entity.info.yml @@ -2,6 +2,7 @@ name: 'Rdf entity' type: module description: 'Provides RDF entity.' package: Custom +php: 7.1 core: 8.x # These modules are required by the tests, must be available at bootstrap time. From 5a05af62365d87622889df4b25cbc85be9fe2f7a Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 14:56:41 +0200 Subject: [PATCH 02/30] Fix docs in src/RdfGraphHandler. --- src/RdfGraphHandler.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/RdfGraphHandler.php b/src/RdfGraphHandler.php index 30c2ada1..d673e395 100644 --- a/src/RdfGraphHandler.php +++ b/src/RdfGraphHandler.php @@ -62,17 +62,17 @@ class RdfGraphHandler { * @var array * * @code - * $requestGraphs = [ - * $entity_id => [ - * graph1, - * graph2 - * ] - * $entity_id2 => [ - * graph1, - * graph2, - * ] - * ] - * @code + * $requestGraphs => [ + * $entity_id => [ + * graph1, + * graph2 + * ], + * $entity_id2 => [ + * graph1, + * graph2, + * ], + * ] + * @endcode */ protected $requestGraphs; From e40420555cbb9fac001d3871edf2f376206ae74f Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 21:19:12 +0200 Subject: [PATCH 03/30] Add schema for 'rdf_entity_graph' and 'rdf_entity_mapping' entities. --- config/schema/rdf_entity.schema.yml | 76 +++++++++++++++++------------ 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/config/schema/rdf_entity.schema.yml b/config/schema/rdf_entity.schema.yml index 7b8a99ad..c8938f87 100644 --- a/config/schema/rdf_entity.schema.yml +++ b/config/schema/rdf_entity.schema.yml @@ -17,37 +17,6 @@ rdf_entity.rdfentity.*: type: text label: 'Explanation or submission guidelines' -rdf_entity.rdfentity.*.third_party.rdf_entity: - type: mapping - label: 'Third party settings' - mapping: - rdf_type: - type: string - label: 'Rdf type mapping' - graph: - type: sequence - sequence: - type: string - label: 'The mapping of a graph definition to a graph uri.' - mapping: - type: sequence - label: 'Property' - sequence: - type: sequence - label: 'Column' - sequence: - type: mapping - mapping: - predicate: - type: string - label: 'Predicate' - format: - type: string - label: 'Value format' - entity_id_plugin: - type: string - label: 'The plugin that generates the entity ID' - field.storage.*.*.third_party.rdf_entity: type: mapping mapping: @@ -86,3 +55,48 @@ field.formatter.third_party.joinup: mapping: template_suggestion: type: string + +rdf_entity.graph.*: + type: config_entity + label: 'RDF entity graph' + mapping: + id: + type: string + label: ID + name: + type: label + label: Name + description: + type: text + label: Description + +rdf_entity.mapping.*: + type: config_entity + label: 'Stores the mapping between Drupal bundle settings and RDF representation' + mapping: + rdf_type: + type: string + label: 'RDF type mapping' + graph: + type: sequence + sequence: + type: string + label: 'The mapping of a graph definition to a graph URI.' + base_fields_mapping: + type: sequence + label: 'Property' + sequence: + type: sequence + label: 'Column' + sequence: + type: mapping + mapping: + predicate: + type: string + label: 'Predicate' + format: + type: string + label: 'Value format' + entity_id_plugin: + type: string + label: 'The plugin that generates the entity ID' From 3532abeb2957b21b0896dc38f2a6ec8ca531b675 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 21:41:20 +0200 Subject: [PATCH 04/30] Move mapping config from bundle 3rd party settings to dedicated config entities. --- ...ty.mapping.taxonomy_term.taxonomy_test.yml | 53 +++++++++++++++++++ .../taxonomy.vocabulary.taxonomy_test.yml | 49 +---------------- .../rdf_entity.mapping.rdf_entity.dummy.yml | 44 +++++++++++++++ ...f_entity.mapping.rdf_entity.multifield.yml | 32 +++++++++++ ...f_entity.mapping.rdf_entity.with_owner.yml | 32 +++++++++++ .../install/rdf_entity.rdfentity.dummy.yml | 40 +------------- .../rdf_entity.rdfentity.multifield.yml | 28 +--------- .../rdf_entity.rdfentity.with_owner.yml | 28 +--------- 8 files changed, 165 insertions(+), 141 deletions(-) create mode 100644 modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml create mode 100644 tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml create mode 100644 tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml create mode 100644 tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml diff --git a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml new file mode 100644 index 00000000..72ee508f --- /dev/null +++ b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml @@ -0,0 +1,53 @@ +langcode: en +status: true +dependencies: { } +third_party_settings: { } +id: taxonomy_term.taxonomy_test +entity_type_id: taxonomy_term +bundle: taxonomy_test +rdf_type: 'http://example.com/rdf_taxonomy/test' +graph: + default: 'http://example.com/rdf_taxonomy/published' + draft: 'http://example.com/rdf_taxonomy/draft' +base_fields_mapping: + uuid: + value: + predicate: '' + format: '' + vid: + target_id: + predicate: 'http://www.w3.org/2004/02/skos/core#inScheme' + format: resource + langcode: + value: + predicate: '' + format: '' + name: + value: + predicate: 'http://www.w3.org/2004/02/skos/core#prefLabel' + format: t_literal + description: + value: + predicate: 'http://www.w3.org/2004/02/skos/core#definition' + format: t_literal + format: + value: + predicate: '' + format: '' + weight: + value: + predicate: '' + format: '' + parent: + target_id: + predicate: 'http://www.w3.org/2004/02/skos/core#broader' + format: resource + changed: + value: + predicate: '' + format: '' + default_langcode: + value: + predicate: '' + format: '' + path: { } diff --git a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/taxonomy.vocabulary.taxonomy_test.yml b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/taxonomy.vocabulary.taxonomy_test.yml index 394111ba..79890652 100644 --- a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/taxonomy.vocabulary.taxonomy_test.yml +++ b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/taxonomy.vocabulary.taxonomy_test.yml @@ -3,54 +3,7 @@ status: true dependencies: module: - rdf_entity -third_party_settings: - rdf_entity: - rdf_type: 'http://example.com/rdf_taxonomy/test' - mapping: - uuid: - value: - predicate: '' - format: '' - vid: - target_id: - predicate: 'http://www.w3.org/2004/02/skos/core#inScheme' - format: resource - langcode: - value: - predicate: '' - format: '' - name: - value: - predicate: 'http://www.w3.org/2004/02/skos/core#prefLabel' - format: t_literal - description: - value: - predicate: 'http://www.w3.org/2004/02/skos/core#definition' - format: t_literal - format: - value: - predicate: '' - format: '' - weight: - value: - predicate: '' - format: '' - parent: - target_id: - predicate: 'http://www.w3.org/2004/02/skos/core#broader' - format: resource - changed: - value: - predicate: '' - format: '' - default_langcode: - value: - predicate: '' - format: '' - path: { } - graph: - default: 'http://example.com/rdf_taxonomy/published' - draft: 'http://example.com/rdf_taxonomy/draft' +third_party_settings: { } name: 'Taxonomy test' vid: taxonomy_test description: '' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml new file mode 100644 index 00000000..246f4f09 --- /dev/null +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml @@ -0,0 +1,44 @@ +langcode: en +status: true +dependencies: { } +third_party_settings: { } +id: rdf_entity.dummy +entity_type_id: rdf_entity +bundle: dummy +rdf_type: 'http://example.com/dummy' +base_fields_mapping: + rid: + target_id: + predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' + format: 'resource' + uid: + target_id: + predicate: '' + format: '' + label: + value: + predicate: 'http://example.com/dummy_label' + format: t_literal + uuid: + value: + predicate: '' + format: '' + langcode: + value: + predicate: 'http://example.com/language' + format: t_literal + graph: + value: + predicate: '' + format: '' + created: + value: + predicate: 'http://purl.org/dc/terms/issued' + format: 'xsd:dateTime' + changed: + value: + predicate: 'http://example.com/modified' + format: 'xsd:integer' +graph: + default: 'http://example.com/dummy/published' + draft: 'http://example.com/dummy/draft' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml new file mode 100644 index 00000000..b2e3055a --- /dev/null +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml @@ -0,0 +1,32 @@ +langcode: en +status: true +dependencies: { } +third_party_settings: { } +id: rdf_entity.multifield +entity_type_id: rdf_entity +bundle: multifield +rdf_type: 'http://example.com/multifield' +base_fields_mapping: + rid: + target_id: + predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' + format: 'resource' + uid: + target_id: + predicate: 'http://example.com/multifield_with_owner/uid' + format: 'integer' + label: + value: + predicate: 'http://example.com/multifield_label' + format: literal + uuid: + value: + predicate: '' + format: '' + graph: + value: + predicate: '' + format: '' +graph: + default: 'http://example.com/multifield/published' + draft: 'http://example.com/multifield/draft' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml new file mode 100644 index 00000000..e565bfc6 --- /dev/null +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml @@ -0,0 +1,32 @@ +langcode: en +status: true +dependencies: { } +third_party_settings: { } +id: rdf_entity.with_owner +entity_type_id: rdf_entity +bundle: with_owner +rdf_type: 'http://example.com/dummy_with_owner' +base_fields_mapping: + rid: + target_id: + predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' + format: 'resource' + uid: + target_id: + predicate: 'http://example.com/dummy_with_owner/uid' + format: 'integer' + label: + value: + predicate: 'http://example.com/dummy_with_owner_label' + format: 'literal' + uuid: + value: + predicate: '' + format: '' + graph: + value: + predicate: '' + format: '' +graph: + default: 'http://example.com/with_owner/published' + draft: 'http://example.com/with_owner/draft' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.dummy.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.dummy.yml index 5192afdd..d778812a 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.dummy.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.dummy.yml @@ -1,45 +1,7 @@ langcode: en status: true dependencies: { } -third_party_settings: - rdf_entity: - rdf_type: 'http://example.com/dummy' - mapping: - rid: - target_id: - predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' - uid: - target_id: - predicate: '' - format: '' - label: - value: - predicate: 'http://example.com/dummy_label' - format: t_literal - uuid: - value: - predicate: '' - format: '' - langcode: - value: - predicate: 'http://example.com/language' - format: t_literal - graph: - value: - predicate: '' - format: '' - created: - value: - predicate: 'http://purl.org/dc/terms/issued' - format: 'xsd:dateTime' - changed: - value: - predicate: 'http://example.com/modified' - format: 'xsd:integer' - graph: - default: 'http://example.com/dummy/published' - draft: 'http://example.com/dummy/draft' +third_party_settings: { } name: Dummy rid: dummy description: '' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.multifield.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.multifield.yml index c9b3aa5f..b8769963 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.multifield.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.multifield.yml @@ -1,33 +1,7 @@ langcode: en status: true dependencies: { } -third_party_settings: - rdf_entity: - rdf_type: 'http://example.com/multifield' - mapping: - rid: - target_id: - predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' - uid: - target_id: - predicate: 'http://example.com/multifield_with_owner/uid' - format: 'integer' - label: - value: - predicate: 'http://example.com/multifield_label' - format: literal - uuid: - value: - predicate: '' - format: '' - graph: - value: - predicate: '' - format: '' - graph: - default: 'http://example.com/multifield/published' - draft: 'http://example.com/multifield/draft' +third_party_settings: { } name: Multifield rid: multifield description: '' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.with_owner.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.with_owner.yml index 47bd7aa6..76839382 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.with_owner.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.rdfentity.with_owner.yml @@ -1,33 +1,7 @@ langcode: en status: true dependencies: { } -third_party_settings: - rdf_entity: - rdf_type: 'http://example.com/dummy_with_owner' - mapping: - rid: - target_id: - predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' - uid: - target_id: - predicate: 'http://example.com/dummy_with_owner/uid' - format: 'integer' - label: - value: - predicate: 'http://example.com/dummy_with_owner_label' - format: 'literal' - uuid: - value: - predicate: '' - format: '' - graph: - value: - predicate: '' - format: '' - graph: - default: 'http://example.com/with_owner/published' - draft: 'http://example.com/with_owner/draft' +third_party_settings: { } name: 'With Owner' rid: with_owner description: '' From 05be3d9333bfd21807258adf2e5e9dbb7bce001c Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 21:50:25 +0200 Subject: [PATCH 05/30] Use the mapping stored in the dedicated config entity. --- modules/rdf_taxonomy/src/TermRdfStorage.php | 6 +- .../rdf_taxonomy_test.info.yml | 2 + rdf_entity.module | 130 +++++++++++------ src/Entity/Rdf.php | 11 +- src/Entity/RdfEntityMapping.php | 134 ++++++++++++++++++ src/RdfEntityIdPluginManager.php | 12 +- src/RdfEntityMappingInterface.php | 27 ++++ src/RdfFieldHandler.php | 7 +- src/RdfGraphHandler.php | 21 +-- 9 files changed, 273 insertions(+), 77 deletions(-) create mode 100644 src/Entity/RdfEntityMapping.php create mode 100644 src/RdfEntityMappingInterface.php diff --git a/modules/rdf_taxonomy/src/TermRdfStorage.php b/modules/rdf_taxonomy/src/TermRdfStorage.php index 2e619cf7..304e6faf 100644 --- a/modules/rdf_taxonomy/src/TermRdfStorage.php +++ b/modules/rdf_taxonomy/src/TermRdfStorage.php @@ -3,6 +3,7 @@ namespace Drupal\rdf_taxonomy; use Drupal\Core\Entity\EntityInterface; +use Drupal\rdf_entity\Entity\RdfEntityMapping; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; use Drupal\taxonomy\TermStorageInterface; use EasyRdf\Graph; @@ -238,9 +239,8 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = // We cache trees, so it's not CPU-intensive to call on a term and its // children, too. if (empty($this->treeChildren[$vid])) { - /** @var \Drupal\taxonomy\Entity\Vocabulary $voc */ - $voc = entity_load('taxonomy_vocabulary', $vid); - $concept_schema = $voc->getThirdPartySetting('rdf_entity', 'rdf_type'); + $mapping = RdfEntityMapping::loadByName('taxonomy_term', $vid); + $concept_schema = $mapping->get('rdf_type'); $this->treeChildren[$vid] = []; $this->treeParents[$vid] = []; $this->treeTerms[$vid] = []; diff --git a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/rdf_taxonomy_test.info.yml b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/rdf_taxonomy_test.info.yml index bf3a5004..ba8484ee 100644 --- a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/rdf_taxonomy_test.info.yml +++ b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/rdf_taxonomy_test.info.yml @@ -3,3 +3,5 @@ type: module description: 'Support module for rdf taxonomy tests.' core: 8.x package: Testing +config_devel: + - rdf_entity.mapping.taxonomy_term.taxonomy_test diff --git a/rdf_entity.module b/rdf_entity.module index 42745dfa..a02372c6 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -5,6 +5,8 @@ * Main functions and hook implementations of the RDF Entity module. */ +declare(strict_types = 1); + use Drupal\Core\Config\Entity\ConfigEntityBundleBase; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Database\Database; @@ -16,6 +18,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Url; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\FieldStorageConfigInterface; +use Drupal\rdf_entity\Entity\RdfEntityGraph; +use Drupal\rdf_entity\Entity\RdfEntityMapping; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; use Drupal\rdf_entity\RdfFieldHandler; use Drupal\rdf_entity\RdfInterface; @@ -109,12 +114,27 @@ function rdf_entity_form_field_storage_config_edit_form_alter(&$form, FormStateI /** * Retrieve nested third party settings from object. + * + * The object may be either a bundle entity or a field storage config entity. + * + * @todo Move this to a service (or to RDF storage?) */ function rdf_entity_get_third_party_property(ConfigEntityInterface $object, $property, $column, $default = NULL) { - $property_value = $object->getThirdPartySetting('rdf_entity', $property, FALSE); + // Mapping data requested for a configurable field. + if ($object instanceof \Drupal\field\FieldStorageConfigInterface) { + $property_value = $object->getThirdPartySetting('rdf_entity', $property, FALSE); + } + // Mapping data requested for a bundle entity. + else { + $entity_type_id = $object->getEntityType()->getBundleOf(); + $bundle = $object->id(); + $mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle); + $property_value = $mapping->get($property) ?: FALSE; + } if (!is_array($property_value) || !isset($property_value[$column])) { return $default; } + return $property_value[$column]; } @@ -174,6 +194,21 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#description' => t('RDF entity configurations.'), '#open' => TRUE, '#weight' => 99, + '#tree' => TRUE, + ]; + + if (!$mapping = RdfEntityMapping::loadByName($entity_name, $entity->id())) { + $mapping = RdfEntityMapping::create([ + 'entity_type_id' => $entity_name, + 'bundle' => $entity->id(), + ]); + } + $form_state->set('rdf_entity_mapping', $mapping); + + $form['rdf_entity']['rdf_type'] = [ + '#type' => 'textfield', + '#title' => t('RDF type mapping'), + '#default_value' => $mapping->get('rdf_type'), ]; /** @var \Drupal\rdf_entity\RdfEntityIdPluginManager $id_plugin_manager */ @@ -187,102 +222,103 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#title' => t('Entity ID generator'), '#description' => t("The generator used to create IDs for new entities."), '#options' => $plugins, - '#default_value' => $entity->getThirdPartySetting('rdf_entity', 'entity_id_plugin') ?: $id_plugin_manager->getFallbackPluginId(NULL), + '#default_value' => $mapping->get('entity_id_plugin') ?: $id_plugin_manager->getFallbackPluginId(NULL), ]; - $form['rdf_entity']['rdf_graph'] = [ + $form['rdf_entity']['graph'] = [ '#type' => 'details', '#title' => t('Graphs'), '#description' => t('Graph URI mapping'), ]; - foreach ($storage->getGraphDefinitions() as $definedGraphName => $definedGraph) { - $default_value = rdf_entity_get_third_party_property($entity, 'graph', $definedGraphName, FALSE); - $form['rdf_entity']['rdf_graph']['graph_' . $definedGraphName] = [ + + foreach ($storage->getGraphDefinitions() as $graph_id => $graph) { + $form['rdf_entity']['graph'][$graph_id] = [ '#type' => 'url', - '#title' => $definedGraph['title'], - '#description' => $definedGraph['description'], - '#default_value' => $default_value, - '#required' => TRUE, + '#title' => t('@title (@id)', ['@title' => $graph['title'], '@id' => $graph_id]), + '#description' => $graph['description'], + '#default_value' => $mapping->get('graph')[$graph_id] ?? NULL, + '#required' => $graph_id === RdfEntityGraph::DEFAULT, ]; } - $form['rdf_entity']['rdf_mapping'] = [ + $form['rdf_entity']['base_fields_mapping'] = [ '#type' => 'details', '#title' => t('Field mapping'), '#description' => t('This entity type uses a Sparql backend. Please map the bundle base fields to their corresponding RDF properties.'), ]; + $base_fields_mapping = $mapping->get('base_fields_mapping'); /** @var \Drupal\Core\Field\BaseFieldDefinition $base_field */ - foreach ($base_fields as $id => $base_field) { - $columns = $base_field->getColumns(); - // The entity id doesn't need a mapping, - // as this is the subject of the triple. - if ($id == $idKey) { + foreach ($base_fields as $field_name => $base_field) { + // The entity id doesn't need a mapping as it's the subject of the triple. + if ($field_name === $idKey) { continue; } + $columns = $base_field->getColumns(); foreach ($columns as $column_name => $column) { - $settings = rdf_entity_get_third_party_property($entity, 'mapping', $id, FALSE); $title = $base_field->getLabel(); if (count($columns) > 1) { $title .= ' (' . $column_name . ')'; } - $form['rdf_entity']['rdf_mapping'][$id] = [ + $form['rdf_entity']['base_fields_mapping'][$field_name] = [ '#type' => 'details', '#title' => $title, '#description' => $base_field->getDescription(), ]; - $form['rdf_entity']['rdf_mapping'][$id]['predicate_' . $id . '_' . $column_name] = [ + $form['rdf_entity']['base_fields_mapping'][$field_name][$column_name]['predicate'] = [ '#type' => 'url', '#title' => t('Mapping'), '#description' => t('The rdf predicate.'), '#weight' => 150, - '#default_value' => isset($settings[$column_name]['predicate']) ? $settings[$column_name]['predicate'] : NULL, + '#default_value' => $base_fields_mapping[$field_name][$column_name]['predicate'] ?? NULL, ]; - $form['rdf_entity']['rdf_mapping'][$id]['format_' . $id . '_' . $column_name] = [ + $form['rdf_entity']['base_fields_mapping'][$field_name][$column_name]['format'] = [ '#type' => 'select', '#title' => t('Value format'), '#description' => t('The rdf format. Required if format is filled.'), '#options' => RdfFieldHandler::getSupportedDatatypes(), '#empty_value' => '', '#weight' => 151, - '#default_value' => isset($settings[$column_name]['format']) ? $settings[$column_name]['format'] : NULL, + '#default_value' => $base_fields_mapping[$field_name][$column_name]['format'] ?? NULL, ]; } } - $form['#entity_builders'][] = 'rdf_entity_bundle_config'; + $form['#entity_builders'][] = 'rdf_entity_save_mapping'; } /** - * Store the mapping of fields and rdf properties. + * Stores the mapping of base fields and RDF properties. + * + * @param string $entity_type_id + * The entity type ID. + * @param ConfigEntityInterface $bundle_entity + * The bundle entity being built. + * @param array $form + * The form API form render array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + * + * @see rdf_entity_form_alter() */ -function rdf_entity_bundle_config($entity_type, ConfigEntityInterface $entity, array &$form, FormStateInterface $form_state) { - $entity_name = $entity->getEntityType()->getBundleOf(); - $base_fields = \Drupal::entityManager()->getBaseFieldDefinitions($entity_name); - $storage = \Drupal::entityManager()->getStorage($entity_name); - $idKey = $storage->getEntityType()->getKey('id'); - $entity->setThirdPartySetting('rdf_entity', 'entity_id_plugin', $form_state->getValue('entity_id_plugin')); - $third_party_settings = []; - /** @var \Drupal\Core\Field\BaseFieldDefinition $base_field */ - foreach ($base_fields as $id => $base_field) { - if ($id == $idKey) { - continue; - } - $columns = $base_field->getColumns(); - foreach ($columns as $column_name => $column) { - $third_party_settings[$id][$column_name]['predicate'] = $form_state->getValue('predicate_' . $id . '_' . $column_name); - $third_party_settings[$id][$column_name]['format'] = $form_state->getValue('format_' . $id . '_' . $column_name); - } +function rdf_entity_save_mapping(string $entity_type_id, ConfigEntityInterface $bundle_entity, array $form, FormStateInterface $form_state): void { + /** @var \Drupal\rdf_entity\RdfEntityMappingInterface $mapping */ + $mapping = $form_state->get('rdf_entity_mapping'); + $values = $form_state->getValue('rdf_entity'); + + try { + $mapping + ->set('rdf_type', $values['rdf_type']) + ->set('entity_id_plugin', $values['entity_id_plugin']) + ->set('graph', $values['graph']) + ->set('base_fields_mapping', $values['base_fields_mapping']) + ->save(); } - $entity->setThirdPartySetting('rdf_entity', 'mapping', $third_party_settings); - $third_party_settings = []; - foreach ($storage->getGraphDefinitions() as $definedGraphName => $definedGraph) { - $graph_uri = $form_state->getValue('graph_' . $definedGraphName); - $third_party_settings[$definedGraphName] = $graph_uri; + catch (\Exception $exception) { + \Drupal::logger('rdf_entity')->critical(t('Could not save RDF entity mapping'), ['exception' => $exception]); } - $entity->setThirdPartySetting('rdf_entity', 'graph', $third_party_settings); } /** diff --git a/src/Entity/Rdf.php b/src/Entity/Rdf.php index 2c237380..27914a84 100755 --- a/src/Entity/Rdf.php +++ b/src/Entity/Rdf.php @@ -378,18 +378,17 @@ public function hasGraph($graph) { /** * Returns whether the entity bundle has mapping for a certain field column. * - * @param string $field + * @param string $field_name * The field name. * @param string $column * The field column. Defaults to 'value'. * * @return bool - * True if the mapping is set, false otherwise. + * TRUE if the mapping is set, FALSE otherwise. */ - protected function hasFieldMapping($field, $column = 'value') { - $bundle = $this->get($this->getEntityType()->getKey('bundle'))->entity; - $mapping = rdf_entity_get_third_party_property($bundle, 'mapping', $field); - return !empty($mapping[$column]['predicate']); + protected function hasFieldMapping($field_name, $column = 'value') { + $mapping = RdfEntityMapping::loadByName($this->getEntityTypeId(), $this->bundle()); + return !empty($mapping->get('base_fields_mapping')[$field_name][$column]['predicate']); } } diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php new file mode 100644 index 00000000..e378e37b --- /dev/null +++ b/src/Entity/RdfEntityMapping.php @@ -0,0 +1,134 @@ + NULL, + ]; + + /** + * The base fields mapping. + * + * @var array + */ + protected $base_fields_mapping; + + /** + * The plugin that generates the entity ID. + * + * @var string + */ + protected $entity_id_plugin; + + /** + * {@inheritdoc} + */ + public function __construct(array $values, $entity_type) { + if (empty($values['entity_type_id'])) { + throw new \InvalidArgumentException('Missing required property: entity_type_id.'); + } + + // Valid entity type? + if (!$storage = $this->entityTypeManager()->getStorage($values['entity_type_id'])) { + throw new \InvalidArgumentException("Invalid entity type: {$values['entity_type_id']}."); + } + + if ($storage->getEntityType()->hasKey('bundle')) { + // If this entity type requires a bundle, the bundle should be passed. + if (empty($values['bundle'])) { + throw new \InvalidArgumentException('Missing required property: bundle.'); + } + } + else { + $values['bundle'] = $values['entity_type_id']; + } + + // Only entities with RDF storage are eligible. + if (!$storage instanceof RdfEntitySparqlStorage) { + throw new \InvalidArgumentException("Cannot handle non-RDF entity type: {$values['entity_type_id']}."); + } + + parent::__construct($values, $entity_type); + } + + /** + * {@inheritdoc} + */ + public function id() { + return "{$this->entity_type_id}.{$this->bundle}"; + } + + /** + * {@inheritdoc} + */ + public static function loadByName(string $entity_type_id, string $bundle): ?RdfEntityMappingInterface { + return static::load("$entity_type_id.$bundle"); + } + +} diff --git a/src/RdfEntityIdPluginManager.php b/src/RdfEntityIdPluginManager.php index 573e4e4f..8b6ad24f 100644 --- a/src/RdfEntityIdPluginManager.php +++ b/src/RdfEntityIdPluginManager.php @@ -9,6 +9,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\rdf_entity\Annotation\RdfEntityId; +use Drupal\rdf_entity\Entity\RdfEntityMapping; /** * Plugin manager for entity ID generator plugins. @@ -71,14 +72,9 @@ public function getPlugin(ContentEntityInterface $entity) { if (!isset($this->instances[$entity_type_id][$bundle_id])) { $options = ['plugin_id' => NULL]; - if ($bundle_entity_type_id = $entity->getEntityType()->getBundleEntityType()) { - if ($bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id)) { - /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */ - if ($bundle = $bundle_storage->load($bundle_id)) { - if ($plugin_id = $bundle->getThirdPartySetting('rdf_entity', 'entity_id_plugin')) { - $options['plugin_id'] = $plugin_id; - } - } + if ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id)) { + if ($plugin_id = $mapping->get('entity_id_plugin')) { + $options['plugin_id'] = $plugin_id; } } $this->instances[$entity_type_id][$bundle_id] = $this->getInstance($options); diff --git a/src/RdfEntityMappingInterface.php b/src/RdfEntityMappingInterface.php new file mode 100644 index 00000000..5aae816b --- /dev/null +++ b/src/RdfEntityMappingInterface.php @@ -0,0 +1,27 @@ +entityFieldManager->getFieldDefinitions($entity_type_id, $rdf_bundle_entity->id()); $bundle_definitions_ids = array_keys($bundle_definitions); - $bundle_mapping = $rdf_bundle_entity->getThirdPartySetting('rdf_entity', 'rdf_type'); - if (empty($bundle_mapping)) { + $mapping = RdfEntityMapping::loadByName($entity_type_id, $rdf_bundle_entity->id()); + if (!$bundle_mapping = $mapping->get('rdf_type')) { throw new \Exception("The {$rdf_bundle_entity->label()} rdf entity does not have an rdf_type set."); } $this->outboundMap[$entity_type_id]['bundles'][$rdf_bundle_entity->id()] = $bundle_mapping; @@ -112,7 +113,7 @@ protected function buildEntityTypeProperties($entity_type_id) { } if ($storage_definition instanceof BaseFieldDefinition) { - $field_data = rdf_entity_get_third_party_property($rdf_bundle_entity, 'mapping', $id, FALSE); + $field_data = $mapping->get('base_fields_mapping')[$id]; $main_property = $storage_definition->getFieldStorageDefinition()->getMainPropertyName(); } else { diff --git a/src/RdfGraphHandler.php b/src/RdfGraphHandler.php index d673e395..1c38c6b1 100644 --- a/src/RdfGraphHandler.php +++ b/src/RdfGraphHandler.php @@ -7,6 +7,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\rdf_entity\Entity\Query\Sparql\SparqlArg; +use Drupal\rdf_entity\Entity\RdfEntityMapping; /** * Contains helper methods for managing the Rdf graphs. @@ -462,7 +463,7 @@ public function clearBundleGraphUrisCache() { /** * Retrieves the uri of a bundle's graph from the settings. * - * @param string $bundle_type_key + * @param string $bundle_entity_type_id * The bundle type key. E.g. 'node_type'. * @param string $bundle_id * The bundle machine name. @@ -475,22 +476,22 @@ public function clearBundleGraphUrisCache() { * @throws \Exception * Thrown if the graph is not found. */ - protected function getBundleGraphUriFromSettings($bundle_type_key, $bundle_id, $graph_name) { - if (!isset($this->bundleGraphUris[$bundle_type_key][$bundle_id][$graph_name])) { - /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */ - $bundle = $this->entityTypeManager->getStorage($bundle_type_key)->load($bundle_id); - $graph_uri = rdf_entity_get_third_party_property($bundle, 'graph', $graph_name, FALSE); + protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle_id, $graph_name) { + if (!isset($this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name])) { + $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id); + $entity_type_id = $bundle_entity_type->getBundleOf(); + $mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id); + $graph_uri = $mapping->get('graph')[$graph_name] ?? NULL; if (!$graph_uri) { throw new \Exception(format_string('Unable to determine graph %graph for bundle %bundle', [ '%graph' => $graph_name, - '%bundle' => $bundle->id(), + '%bundle' => $bundle_id, ])); } - - $this->bundleGraphUris[$bundle_type_key][$bundle_id][$graph_name] = $graph_uri; + $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name] = $graph_uri; } - return $this->bundleGraphUris[$bundle_type_key][$bundle_id][$graph_name]; + return $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name]; } } From a9462206148edc5fbbb116b8404468c3dde32cf4 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 21:51:18 +0200 Subject: [PATCH 06/30] Add the rdf_entity_graph config entity. --- config/install/rdf_entity.graph.default.yml | 5 ++ config/schema/rdf_entity.schema.yml | 11 +++- src/Entity/RdfEntityGraph.php | 59 +++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 config/install/rdf_entity.graph.default.yml create mode 100644 src/Entity/RdfEntityGraph.php diff --git a/config/install/rdf_entity.graph.default.yml b/config/install/rdf_entity.graph.default.yml new file mode 100644 index 00000000..11ede93f --- /dev/null +++ b/config/install/rdf_entity.graph.default.yml @@ -0,0 +1,5 @@ +langcode: en +status: true +id: default +name: Default +description: 'Default graph. This is available for all entity types and bundles.' diff --git a/config/schema/rdf_entity.schema.yml b/config/schema/rdf_entity.schema.yml index c8938f87..82da0c17 100644 --- a/config/schema/rdf_entity.schema.yml +++ b/config/schema/rdf_entity.schema.yml @@ -74,6 +74,15 @@ rdf_entity.mapping.*: type: config_entity label: 'Stores the mapping between Drupal bundle settings and RDF representation' mapping: + id: + type: string + label: ID + entity_type_id: + type: string + label: 'Referred entity type' + bundle: + type: string + label: 'Referred bundle' rdf_type: type: string label: 'RDF type mapping' @@ -84,7 +93,7 @@ rdf_entity.mapping.*: label: 'The mapping of a graph definition to a graph URI.' base_fields_mapping: type: sequence - label: 'Property' + label: 'The base fields mapping' sequence: type: sequence label: 'Column' diff --git a/src/Entity/RdfEntityGraph.php b/src/Entity/RdfEntityGraph.php new file mode 100644 index 00000000..44e0159e --- /dev/null +++ b/src/Entity/RdfEntityGraph.php @@ -0,0 +1,59 @@ + Date: Tue, 9 Jan 2018 23:32:45 +0200 Subject: [PATCH 07/30] Additional checks for field existence in base fields mapping. --- src/Entity/Rdf.php | 2 +- src/RdfFieldHandler.php | 3 ++- src/RdfGraphHandler.php | 16 +++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Entity/Rdf.php b/src/Entity/Rdf.php index 27914a84..16a063b7 100755 --- a/src/Entity/Rdf.php +++ b/src/Entity/Rdf.php @@ -388,7 +388,7 @@ public function hasGraph($graph) { */ protected function hasFieldMapping($field_name, $column = 'value') { $mapping = RdfEntityMapping::loadByName($this->getEntityTypeId(), $this->bundle()); - return !empty($mapping->get('base_fields_mapping')[$field_name][$column]['predicate']); + return $mapping && !empty($mapping->get('base_fields_mapping')[$field_name][$column]['predicate']); } } diff --git a/src/RdfFieldHandler.php b/src/RdfFieldHandler.php index 5c6366ca..319ad688 100644 --- a/src/RdfFieldHandler.php +++ b/src/RdfFieldHandler.php @@ -107,13 +107,14 @@ protected function buildEntityTypeProperties($entity_type_id) { $this->outboundMap[$entity_type_id]['bundles'][$rdf_bundle_entity->id()] = $bundle_mapping; // More than one drupal bundle can share the same mapped uri. $this->inboundMap[$entity_type_id]['bundles'][$bundle_mapping][] = $rdf_bundle_entity->id(); + $base_fields_mapping = $mapping->get('base_fields_mapping'); foreach ($storage_definitions as $id => $storage_definition) { if (!in_array($id, $bundle_definitions_ids)) { continue; } if ($storage_definition instanceof BaseFieldDefinition) { - $field_data = $mapping->get('base_fields_mapping')[$id]; + $field_data = $base_fields_mapping[$id] ?? FALSE; $main_property = $storage_definition->getFieldStorageDefinition()->getMainPropertyName(); } else { diff --git a/src/RdfGraphHandler.php b/src/RdfGraphHandler.php index 1c38c6b1..9d247808 100644 --- a/src/RdfGraphHandler.php +++ b/src/RdfGraphHandler.php @@ -480,13 +480,15 @@ protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle if (!isset($this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name])) { $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id); $entity_type_id = $bundle_entity_type->getBundleOf(); - $mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id); - $graph_uri = $mapping->get('graph')[$graph_name] ?? NULL; - if (!$graph_uri) { - throw new \Exception(format_string('Unable to determine graph %graph for bundle %bundle', [ - '%graph' => $graph_name, - '%bundle' => $bundle_id, - ])); + $graph_uri = NULL; + if ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id)) { + $graph_uri = $mapping->get('graph')[$graph_name] ?? NULL; + if (!$graph_uri) { + throw new \Exception(format_string('Unable to determine graph %graph for bundle %bundle', [ + '%graph' => $graph_name, + '%bundle' => $bundle_id, + ])); + } } $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name] = $graph_uri; } From edfdde794a5a832fa69640c0faec289cbc5f8933 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 9 Jan 2018 23:34:05 +0200 Subject: [PATCH 08/30] Update path: move mapping configs in their dedicated storage. --- rdf_entity.install | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/rdf_entity.install b/rdf_entity.install index 2819dddd..0f314650 100644 --- a/rdf_entity.install +++ b/rdf_entity.install @@ -5,6 +5,9 @@ * Includes installation functions for the rdf_entity module. */ +use Drupal\rdf_entity\Entity\RdfEntityMapping; +use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; + /** * Implements hook_requirements(). */ @@ -26,3 +29,44 @@ function rdf_entity_requirements($phase) { $requirements += rdf_entity_virtuoso_permission_requirements(); return $requirements; } + +/** + * Move RDF entity mapping data from bundle entities into dedicated entities. + */ +function rdf_entity_update_8100() { + $entity_type_manager = \Drupal::entityTypeManager(); + + // Iterate over all entities that are bundles of content entities with + // RdfEntitySparqlStorage and move their 3rd party settings belonging to + // rdf_entity module into their dedicated rdf_entity_mapping config entities. + foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) { + if ($storage = $entity_type_manager->getStorage($entity_type_id)) { + if ($storage instanceof RdfEntitySparqlStorage) { + if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { + if ($bundle_storage = $entity_type_manager->getStorage($bundle_entity_type_id)) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityBase $bundle_entity */ + foreach ($bundle_storage->loadMultiple() as $bundle => $bundle_entity) { + $third_party_settings = $bundle_entity->getThirdPartySettings('rdf_entity'); + $values = [ + 'entity_type_id' => $entity_type_id, + 'bundle' => $bundle, + ] + $third_party_settings; + // Rename key 'mapping' to 'base_fields_mapping'. + $values['base_fields_mapping'] = $values['mapping'] ?? []; + unset($values['mapping']); + + // Create the new 'rdf_entity_mapping' entity. + RdfEntityMapping::create($values)->save(); + + // Cleanup 3rd party settings from the bundle entity. + foreach ($third_party_settings as $key => $value) { + $bundle_entity->unsetThirdPartySetting('rdf_entity', $key); + } + $bundle_entity->save(); + } + } + } + } + } + } +} From d5dc9b1a025c564842ee14ad2ced7010639896e3 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 10:33:01 +0200 Subject: [PATCH 09/30] Add an interface to RdfEntityGraph class. --- rdf_entity.module | 5 ++--- src/Entity/RdfEntityGraph.php | 10 ++-------- src/Entity/RdfEntityMapping.php | 4 +++- src/RdfEntityGraphInterface.php | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 src/RdfEntityGraphInterface.php diff --git a/rdf_entity.module b/rdf_entity.module index a02372c6..15261c49 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -18,10 +18,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Url; use Drupal\field\Entity\FieldStorageConfig; -use Drupal\field\FieldStorageConfigInterface; -use Drupal\rdf_entity\Entity\RdfEntityGraph; use Drupal\rdf_entity\Entity\RdfEntityMapping; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; +use Drupal\rdf_entity\RdfEntityGraphInterface; use Drupal\rdf_entity\RdfFieldHandler; use Drupal\rdf_entity\RdfInterface; use EasyRdf\Http; @@ -237,7 +236,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#title' => t('@title (@id)', ['@title' => $graph['title'], '@id' => $graph_id]), '#description' => $graph['description'], '#default_value' => $mapping->get('graph')[$graph_id] ?? NULL, - '#required' => $graph_id === RdfEntityGraph::DEFAULT, + '#required' => $graph_id === RdfEntityGraphInterface::DEFAULT, ]; } diff --git a/src/Entity/RdfEntityGraph.php b/src/Entity/RdfEntityGraph.php index 44e0159e..ef7a572f 100644 --- a/src/Entity/RdfEntityGraph.php +++ b/src/Entity/RdfEntityGraph.php @@ -4,6 +4,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\ConfigEntityType; +use Drupal\rdf_entity\RdfEntityGraphInterface; /** * Defines the RDF entity graph config entity. @@ -26,14 +27,7 @@ * }, * ) */ -class RdfEntityGraph extends ConfigEntityBase { - - /** - * Default graph. - * - * @var string - */ - const DEFAULT = 'default'; +class RdfEntityGraph extends ConfigEntityBase implements RdfEntityGraphInterface { /** * The unique ID of this RDF entity graph. diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php index e378e37b..5d804658 100644 --- a/src/Entity/RdfEntityMapping.php +++ b/src/Entity/RdfEntityMapping.php @@ -4,6 +4,7 @@ namespace Drupal\rdf_entity\Entity; +use Drupal\rdf_entity\RdfEntityGraphInterface; use Drupal\rdf_entity\RdfEntityMappingInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\ConfigEntityType; @@ -69,7 +70,7 @@ class RdfEntityMapping extends ConfigEntityBase implements RdfEntityMappingInter * @var array */ protected $graph = [ - 'default' => NULL, + RdfEntityGraphInterface::DEFAULT => NULL, ]; /** @@ -106,6 +107,7 @@ public function __construct(array $values, $entity_type) { } } else { + // The bundle is the entiy type ID, regardless of the passed value. $values['bundle'] = $values['entity_type_id']; } diff --git a/src/RdfEntityGraphInterface.php b/src/RdfEntityGraphInterface.php new file mode 100644 index 00000000..a72aef9b --- /dev/null +++ b/src/RdfEntityGraphInterface.php @@ -0,0 +1,21 @@ + Date: Wed, 10 Jan 2018 11:40:54 +0200 Subject: [PATCH 10/30] Expand the RdfEntityMapping with setters and getters. --- modules/rdf_taxonomy/src/TermRdfStorage.php | 2 +- rdf_entity.module | 20 +- src/Entity/Rdf.php | 2 +- src/Entity/RdfEntityMapping.php | 135 ++++++++++++- src/RdfEntityIdPluginManager.php | 2 +- src/RdfEntityMappingInterface.php | 201 ++++++++++++++++++++ src/RdfFieldHandler.php | 4 +- src/RdfGraphHandler.php | 8 +- 8 files changed, 351 insertions(+), 23 deletions(-) diff --git a/modules/rdf_taxonomy/src/TermRdfStorage.php b/modules/rdf_taxonomy/src/TermRdfStorage.php index 304e6faf..d7a92c02 100644 --- a/modules/rdf_taxonomy/src/TermRdfStorage.php +++ b/modules/rdf_taxonomy/src/TermRdfStorage.php @@ -240,7 +240,7 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = // children, too. if (empty($this->treeChildren[$vid])) { $mapping = RdfEntityMapping::loadByName('taxonomy_term', $vid); - $concept_schema = $mapping->get('rdf_type'); + $concept_schema = $mapping->getRdfType(); $this->treeChildren[$vid] = []; $this->treeParents[$vid] = []; $this->treeTerms[$vid] = []; diff --git a/rdf_entity.module b/rdf_entity.module index 15261c49..41985922 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -207,7 +207,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { $form['rdf_entity']['rdf_type'] = [ '#type' => 'textfield', '#title' => t('RDF type mapping'), - '#default_value' => $mapping->get('rdf_type'), + '#default_value' => $mapping->getRdfType(), ]; /** @var \Drupal\rdf_entity\RdfEntityIdPluginManager $id_plugin_manager */ @@ -221,7 +221,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#title' => t('Entity ID generator'), '#description' => t("The generator used to create IDs for new entities."), '#options' => $plugins, - '#default_value' => $mapping->get('entity_id_plugin') ?: $id_plugin_manager->getFallbackPluginId(NULL), + '#default_value' => $mapping->getEntityIdPlugin() ?: $id_plugin_manager->getFallbackPluginId(NULL), ]; $form['rdf_entity']['graph'] = [ @@ -235,7 +235,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#type' => 'url', '#title' => t('@title (@id)', ['@title' => $graph['title'], '@id' => $graph_id]), '#description' => $graph['description'], - '#default_value' => $mapping->get('graph')[$graph_id] ?? NULL, + '#default_value' => $mapping->getGraph($graph_id), '#required' => $graph_id === RdfEntityGraphInterface::DEFAULT, ]; } @@ -246,7 +246,6 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#description' => t('This entity type uses a Sparql backend. Please map the bundle base fields to their corresponding RDF properties.'), ]; - $base_fields_mapping = $mapping->get('base_fields_mapping'); /** @var \Drupal\Core\Field\BaseFieldDefinition $base_field */ foreach ($base_fields as $field_name => $base_field) { // The entity id doesn't need a mapping as it's the subject of the triple. @@ -271,7 +270,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#title' => t('Mapping'), '#description' => t('The rdf predicate.'), '#weight' => 150, - '#default_value' => $base_fields_mapping[$field_name][$column_name]['predicate'] ?? NULL, + '#default_value' => $mapping->getMapping($field_name, $column_name)['predicate'] ?? NULL, ]; $form['rdf_entity']['base_fields_mapping'][$field_name][$column_name]['format'] = [ @@ -281,7 +280,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#options' => RdfFieldHandler::getSupportedDatatypes(), '#empty_value' => '', '#weight' => 151, - '#default_value' => $base_fields_mapping[$field_name][$column_name]['format'] ?? NULL, + '#default_value' => $mapping->getMapping($field_name, $column_name)['format'] ?? NULL, ]; } } @@ -309,10 +308,11 @@ function rdf_entity_save_mapping(string $entity_type_id, ConfigEntityInterface $ try { $mapping - ->set('rdf_type', $values['rdf_type']) - ->set('entity_id_plugin', $values['entity_id_plugin']) - ->set('graph', $values['graph']) - ->set('base_fields_mapping', $values['base_fields_mapping']) + ->setRdfType($values['rdf_type']) + ->setEntityIdPlugin($values['entity_id_plugin']) + // Add only non-empty values. + ->setGraphs(array_filter($values['graph'])) + ->setMappings($values['base_fields_mapping']) ->save(); } catch (\Exception $exception) { diff --git a/src/Entity/Rdf.php b/src/Entity/Rdf.php index 16a063b7..987591ca 100755 --- a/src/Entity/Rdf.php +++ b/src/Entity/Rdf.php @@ -388,7 +388,7 @@ public function hasGraph($graph) { */ protected function hasFieldMapping($field_name, $column = 'value') { $mapping = RdfEntityMapping::loadByName($this->getEntityTypeId(), $this->bundle()); - return $mapping && !empty($mapping->get('base_fields_mapping')[$field_name][$column]['predicate']); + return $mapping && !empty($mapping->getMapping($field_name, $column)['predicate']); } } diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php index 5d804658..e26bdb9c 100644 --- a/src/Entity/RdfEntityMapping.php +++ b/src/Entity/RdfEntityMapping.php @@ -4,10 +4,11 @@ namespace Drupal\rdf_entity\Entity; -use Drupal\rdf_entity\RdfEntityGraphInterface; -use Drupal\rdf_entity\RdfEntityMappingInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\ConfigEntityType; +use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\rdf_entity\RdfEntityGraphInterface; +use Drupal\rdf_entity\RdfEntityMappingInterface; /** * Defines the RDF entity mapping config entity. @@ -126,6 +127,136 @@ public function id() { return "{$this->entity_type_id}.{$this->bundle}"; } + /** + * {@inheritdoc} + */ + public function getTargetEntityTypeId(): string { + return $this->entity_type_id; + } + + /** + * {@inheritdoc} + */ + public function getTargetEntityType(): ?ContentEntityTypeInterface { + return $this->entityTypeManager()->getDefinition($this->entity_type_id); + } + + /** + * {@inheritdoc} + */ + public function getTargetBundle(): string { + return $this->bundle; + } + + /** + * {@inheritdoc} + */ + public function setRdfType(string $rdf_type): RdfEntityMappingInterface { + $this->rdf_type = $rdf_type; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRdfType(): ?string { + return $this->rdf_type; + } + + /** + * {@inheritdoc} + */ + public function setEntityIdPlugin(string $entity_id_plugin): RdfEntityMappingInterface { + $this->entity_id_plugin = $entity_id_plugin; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getEntityIdPlugin(): ?string { + return $this->entity_id_plugin; + } + + /** + * {@inheritdoc} + */ + public function addGraphs(array $graphs): RdfEntityMappingInterface { + $this->graph = $graphs + $this->graph; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setGraphs(array $graphs): RdfEntityMappingInterface { + if (!isset($graphs[RdfEntityGraphInterface::DEFAULT])) { + throw new \InvalidArgumentException("Passed graphs should include the '" . RdfEntityGraphInterface::DEFAULT . "' graph."); + } + $this->graph = $graphs; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getGraphs(): array { + return $this->graph; + } + + /** + * {@inheritdoc} + */ + public function getGraphUri(string $graph): ?string { + return $this->graph[$graph] ?? NULL; + } + + /** + * {@inheritdoc} + */ + public function unsetGraphs(array $graphs): RdfEntityMappingInterface { + $this->graph = array_diff_key($this->graph, array_flip($graphs)); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addMappings(array $mappings): RdfEntityMappingInterface { + $this->base_fields_mapping = $mappings + $this->base_fields_mapping; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setMappings(array $mappings): RdfEntityMappingInterface { + $this->base_fields_mapping = $mappings; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMappings(): array { + return $this->base_fields_mapping; + } + + /** + * {@inheritdoc} + */ + public function getMapping(string $field_name, string $column_name = 'value'): array { + return $this->base_fields_mapping[$field_name][$column_name] ?? NULL; + } + + /** + * {@inheritdoc} + */ + public function unsetMappings(array $field_names): RdfEntityMappingInterface { + $this->base_fields_mapping = array_diff_key($this->base_fields_mapping, array_flip($field_names)); + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/RdfEntityIdPluginManager.php b/src/RdfEntityIdPluginManager.php index 8b6ad24f..5405003e 100644 --- a/src/RdfEntityIdPluginManager.php +++ b/src/RdfEntityIdPluginManager.php @@ -73,7 +73,7 @@ public function getPlugin(ContentEntityInterface $entity) { if (!isset($this->instances[$entity_type_id][$bundle_id])) { $options = ['plugin_id' => NULL]; if ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id)) { - if ($plugin_id = $mapping->get('entity_id_plugin')) { + if ($plugin_id = $mapping->getEntityIdPlugin()) { $options['plugin_id'] = $plugin_id; } } diff --git a/src/RdfEntityMappingInterface.php b/src/RdfEntityMappingInterface.php index 5aae816b..b09f0499 100644 --- a/src/RdfEntityMappingInterface.php +++ b/src/RdfEntityMappingInterface.php @@ -5,12 +5,213 @@ namespace Drupal\rdf_entity; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Entity\ContentEntityTypeInterface; /** * Provides an interface for 'rdf_entity_mapping' entities. */ interface RdfEntityMappingInterface extends ConfigEntityInterface { + /** + * Gets the referred entity type ID. + * + * @return string + */ + public function getTargetEntityTypeId(): string; + + /** + * Gets the referred entity type. + * + * @return \Drupal\Core\Entity\ContentEntityTypeInterface|null + * The target entity type. + */ + public function getTargetEntityType(): ?ContentEntityTypeInterface; + + /** + * Gets the referred bundle ID. + * + * @return string + */ + public function getTargetBundle(): string; + + /** + * Sets the RDF type mapping value. + * + * @param string $rdf_type + * The RDF type mapping. + * + * @return $this + */ + public function setRdfType(string $rdf_type): self; + + /** + * Gets the RDF type mapping value. + * + * @return string|null + * The RDF type mapping. + */ + public function getRdfType(): ?string; + + /** + * Sets the RDF entity ID generator plugin. + * + * @param string $entity_id_plugin + * The RDF entity ID generator plugin. + * + * @return $this + */ + public function setEntityIdPlugin(string $entity_id_plugin): self; + + /** + * Gets the RDF entity ID generator plugin. + * + * @return string|null + * The RDF entity ID generator plugin. + */ + public function getEntityIdPlugin(): ?string; + + /** + * Adds a list of graphs. + * + * Graphs are added to the existing list. If a graph with the same name + * already exists will be overridden with the new passed value. + * + * @param string[] $graphs + * Associative array keyed by graph name, having the graph URIs as values. + * + * @return $this + */ + public function addGraphs(array $graphs): self; + + /** + * Sets the list of graphs. + * + * Unlike ::addGraphs(), this method replaces the whole list of graphs. It's + * mandatory to pass also the 'default'. + * + * @param string[] $graphs + * Associative array keyed by graph name, having the graph URIs as values. + * + * @return $this + * + * @throws \InvalidArgumentException + * If the passed list of graphs doesn't contain the 'default' graph. + * + * @see self::addGraphs() + */ + public function setGraphs(array $graphs): self; + + /** + * Gets all graphs. + * + * @return string[ + * Associative array keyed by graph name, having the graph URIs as values. + */ + public function getGraphs(): array; + + /** + * Gets the URI value given a specific graph ID. + * + * @param string $graph + * The graph ID. + * + * @return string|null + * The graph URI or NULL if doesn't exist. + */ + public function getGraphUri(string $graph): ?string; + + /** + * Un-sets a list of graphs. + * + * @param string[] $graphs + * A list if graph IDs. + * + * @return $this + */ + public function unsetGraphs(array $graphs): self; + + /** + * Adds a list of base fields mappings. + * + * Mappings are added to the existing list. If a mapping with the same name + * already exists will be overridden with the new passed value. + * + * @param array $mappings + * A structured associative array having the next structure: + * @code + * [ + * 'field1' => [ + * 'column1' => [ + * 'predicate' => 'http://example.com', + * 'format' => 't_literal', + * ], + * 'column2' => [ + * 'predicate' => 'http://example.com/other', + * 'format' => 'xsd:dateTime', + * ], + * ], + * 'field2' => [ + * ... + * ], + * ] + * @endcode + * - The first level are field names, + * - The second level are field columns, such as 'value', 'target_id'. + * - The values are arrays with two keys: 'predicate', 'format'. + * + * @return $this + */ + public function addMappings(array $mappings): self; + + /** + * Sets the list of base fields mappings. + * + * Unlike ::addMappings(), this method replaces the whole list of existing + * mappings. + * + * @param array $mappings + * A structured associative array with the same structure as the parameter + * from ::addMappings(). + * + * @return $this + * + * @throws \InvalidArgumentException + * If the passed list of graphs doesn't contain the 'default' graph. + * + * @see self::addMappings() + */ + public function setMappings(array $mappings): self; + + /** + * Returns all base fields mappings. + * + * @return array + */ + public function getMappings(): array; + + /** + * Returns the mapping for a specific base field and column. + * + * @param string $field_name + * The field name. + * @param string $column_name + * (optional) The column name. Defaults to 'value'. + * + * @return array|null + * Associative array with two keys: 'predicate' and 'format'. + */ + public function getMapping(string $field_name, string $column_name = 'value'): ?array; + + /** + * Un-sets the mappings for a given list of fields. + * + * @param string[] $field_names + * A list of field names. + * + * @return $this + */ + public function unsetMappings(array $field_names): self; + /** * Loads a rdf_entity_mapping entity given the entity type ID and the bundle. * diff --git a/src/RdfFieldHandler.php b/src/RdfFieldHandler.php index 319ad688..4098aaf9 100644 --- a/src/RdfFieldHandler.php +++ b/src/RdfFieldHandler.php @@ -101,13 +101,13 @@ protected function buildEntityTypeProperties($entity_type_id) { $bundle_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $rdf_bundle_entity->id()); $bundle_definitions_ids = array_keys($bundle_definitions); $mapping = RdfEntityMapping::loadByName($entity_type_id, $rdf_bundle_entity->id()); - if (!$bundle_mapping = $mapping->get('rdf_type')) { + if (!$bundle_mapping = $mapping->getRdfType()) { throw new \Exception("The {$rdf_bundle_entity->label()} rdf entity does not have an rdf_type set."); } $this->outboundMap[$entity_type_id]['bundles'][$rdf_bundle_entity->id()] = $bundle_mapping; // More than one drupal bundle can share the same mapped uri. $this->inboundMap[$entity_type_id]['bundles'][$bundle_mapping][] = $rdf_bundle_entity->id(); - $base_fields_mapping = $mapping->get('base_fields_mapping'); + $base_fields_mapping = $mapping->getMappings(); foreach ($storage_definitions as $id => $storage_definition) { if (!in_array($id, $bundle_definitions_ids)) { continue; diff --git a/src/RdfGraphHandler.php b/src/RdfGraphHandler.php index 9d247808..117700fd 100644 --- a/src/RdfGraphHandler.php +++ b/src/RdfGraphHandler.php @@ -482,12 +482,8 @@ protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle $entity_type_id = $bundle_entity_type->getBundleOf(); $graph_uri = NULL; if ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id)) { - $graph_uri = $mapping->get('graph')[$graph_name] ?? NULL; - if (!$graph_uri) { - throw new \Exception(format_string('Unable to determine graph %graph for bundle %bundle', [ - '%graph' => $graph_name, - '%bundle' => $bundle_id, - ])); + if (!$graph_uri = $mapping->getGraphUri($graph_name)) { + throw new \Exception("Unable to determine graph $graph_name for bundle $bundle_id"); } } $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name] = $graph_uri; From 28ec21fb7b43f27915c1e2bbd5f1b8b1ca6d4590 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:31:45 +0200 Subject: [PATCH 11/30] Fix the return type. --- src/Entity/RdfEntityMapping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php index e26bdb9c..c4e619a0 100644 --- a/src/Entity/RdfEntityMapping.php +++ b/src/Entity/RdfEntityMapping.php @@ -245,7 +245,7 @@ public function getMappings(): array { /** * {@inheritdoc} */ - public function getMapping(string $field_name, string $column_name = 'value'): array { + public function getMapping(string $field_name, string $column_name = 'value'): ?array { return $this->base_fields_mapping[$field_name][$column_name] ?? NULL; } From d30b89d33bbbf7472ff10f3c88f6d29d40df3359 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:32:32 +0200 Subject: [PATCH 12/30] YAML standards on rdf_draft.info.yml. --- modules/rdf_draft/rdf_draft.info.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rdf_draft/rdf_draft.info.yml b/modules/rdf_draft/rdf_draft.info.yml index 0dc4ff1e..605110ca 100644 --- a/modules/rdf_draft/rdf_draft.info.yml +++ b/modules/rdf_draft/rdf_draft.info.yml @@ -1,6 +1,6 @@ -name: RDF draft +name: 'RDF draft' type: module -description: Brings the notion of a draft graph to RDF entities. +description: 'Brings the notion of a draft graph to RDF entities.' core: 8.x package: Joinup dependencies: From 885d3e4e84f0936a8d66fb1da5d855eae0e02bd8 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:33:40 +0200 Subject: [PATCH 13/30] ::getGraph() was renamed to ::getGraphUri(). --- rdf_entity.module | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rdf_entity.module b/rdf_entity.module index 41985922..92c98929 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -169,22 +169,22 @@ function rdf_entity_entity_create(EntityInterface $entity) { * Configurations for the RDF entity bundle. */ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { - $form_obj = $form_state->getFormObject(); - if (!$form_obj instanceof BundleEntityFormBase) { + $form_object = $form_state->getFormObject(); + if (!$form_object instanceof BundleEntityFormBase) { return; } - /** @var \Drupal\Core\Config\Entity\ConfigEntityBundleBase $entity */ - $entity = $form_obj->getEntity(); - if (!$entity instanceof ConfigEntityBundleBase) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityBundleBase $bundle_entity */ + $bundle_entity = $form_object->getEntity(); + if (!$bundle_entity instanceof ConfigEntityBundleBase) { return; } - $entity_name = $entity->getEntityType()->getBundleOf(); + $entity_type_id = $bundle_entity->getEntityType()->getBundleOf(); /** @var \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage $storage */ - $storage = \Drupal::entityManager()->getStorage($entity_name); + $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id); if (!$storage instanceof RdfEntitySparqlStorage) { return; } - $base_fields = \Drupal::entityManager()->getBaseFieldDefinitions($entity_name); + $base_fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type_id); $idKey = $storage->getEntityType()->getKey('id'); $form['rdf_entity'] = [ @@ -196,10 +196,10 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#tree' => TRUE, ]; - if (!$mapping = RdfEntityMapping::loadByName($entity_name, $entity->id())) { + if (!$mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_entity->id())) { $mapping = RdfEntityMapping::create([ - 'entity_type_id' => $entity_name, - 'bundle' => $entity->id(), + 'entity_type_id' => $entity_type_id, + 'bundle' => $bundle_entity->id(), ]); } $form_state->set('rdf_entity_mapping', $mapping); @@ -235,7 +235,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { '#type' => 'url', '#title' => t('@title (@id)', ['@title' => $graph['title'], '@id' => $graph_id]), '#description' => $graph['description'], - '#default_value' => $mapping->getGraph($graph_id), + '#default_value' => $mapping->getGraphUri($graph_id), '#required' => $graph_id === RdfEntityGraphInterface::DEFAULT, ]; } From ae40916388e3a56589a622e7440fe6b041cdbbb1 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:35:29 +0200 Subject: [PATCH 14/30] Add setters/getters and a new property 'entity_types' to RdfEntityGraph. --- config/install/rdf_entity.graph.default.yml | 1 + config/schema/rdf_entity.schema.yml | 7 ++ src/Entity/RdfEntityGraph.php | 88 +++++++++++++++++++++ src/RdfEntityGraphInterface.php | 55 +++++++++++++ 4 files changed, 151 insertions(+) diff --git a/config/install/rdf_entity.graph.default.yml b/config/install/rdf_entity.graph.default.yml index 11ede93f..6208191a 100644 --- a/config/install/rdf_entity.graph.default.yml +++ b/config/install/rdf_entity.graph.default.yml @@ -3,3 +3,4 @@ status: true id: default name: Default description: 'Default graph. This is available for all entity types and bundles.' +entity_types: null diff --git a/config/schema/rdf_entity.schema.yml b/config/schema/rdf_entity.schema.yml index 82da0c17..8f782e92 100644 --- a/config/schema/rdf_entity.schema.yml +++ b/config/schema/rdf_entity.schema.yml @@ -69,6 +69,13 @@ rdf_entity.graph.*: description: type: text label: Description + entity_types: + type: sequence + nullable: true + label: 'Entity types' + sequence: + type: string + label: 'Entity type' rdf_entity.mapping.*: type: config_entity diff --git a/src/Entity/RdfEntityGraph.php b/src/Entity/RdfEntityGraph.php index ef7a572f..936d4bff 100644 --- a/src/Entity/RdfEntityGraph.php +++ b/src/Entity/RdfEntityGraph.php @@ -4,6 +4,9 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\ConfigEntityType; +use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\Core\Entity\EntityPublishedInterface; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\rdf_entity\RdfEntityGraphInterface; /** @@ -24,6 +27,7 @@ * "id", * "name", * "description", + * "entity_types", * }, * ) */ @@ -50,4 +54,88 @@ class RdfEntityGraph extends ConfigEntityBase implements RdfEntityGraphInterface */ protected $description; + /** + * Entity type IDs where this graph applies. + * + * NULL means it applies to all entity types. + * + * @var string[]|null + */ + protected $entity_types = NULL; + + /** + * {@inheritdoc} + */ + public function setName(string $name): RdfEntityGraphInterface { + $this->name = $name; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDescription(string $description): RdfEntityGraphInterface { + $this->description = $description; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getDescription(): string { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function getEntityTypeIds(): ?array { + return $this->entity_types ?: NULL; + } + + /** + * {@inheritdoc} + */ + public function setEntityTypeIds(?array $entity_type_ids): RdfEntityGraphInterface { + if (empty($entity_type_ids)) { + $this->entity_types = NULL; + } + else { + foreach ($entity_type_ids as $entity_type_id) { + if (!$this->entityTypeManager()->getDefinition($entity_type_id, FALSE)) { + throw new \InvalidArgumentException("Invalid entity type: '$entity_type_id'."); + } + $storage = $this->entityTypeManager()->getStorage($entity_type_id); + if (!$storage instanceof RdfEntitySparqlStorage) { + throw new \InvalidArgumentException("Entity type '$entity_type_id' not a RDF entity."); + } + } + $this->entity_types = $entity_type_ids; + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function delete() { + if ($this->id() === static::DEFAULT) { + throw new \RuntimeException("The '" . static::DEFAULT . "' graph cannot be deleted."); + } + parent::delete(); + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + if ($this->id() === static::DEFAULT) { + if (!$this->status()) { + throw new \RuntimeException("The '" . static::DEFAULT . "' graph cannot be disabled."); + } + } + } + } diff --git a/src/RdfEntityGraphInterface.php b/src/RdfEntityGraphInterface.php index a72aef9b..7bcdb744 100644 --- a/src/RdfEntityGraphInterface.php +++ b/src/RdfEntityGraphInterface.php @@ -18,4 +18,59 @@ interface RdfEntityGraphInterface extends ConfigEntityInterface { */ const DEFAULT = 'default'; + /** + * Set the graph name. + * + * @param string $name + * The graph name. + * + * @return $this + */ + public function setName(string $name): self; + + /** + * Set the graph description. + * + * @param string $description + * The graph description. + * + * @return $this + */ + public function setDescription(string $description): self; + + /** + * Gets the graph description. + * + * @return string + */ + public function getDescription(): string; + + /** + * Gets the entity types supporting this graph. + * + * @return string[]|null + * A list of entity type IDs or NULL if this graph is available to all + * entity types. + */ + public function getEntityTypeIds(): ?array; + + /** + * Sets the entity type IDs to whom this graph is made available. + * + * @param string[]|null $entity_type_ids + * A list of entity type IDs or NULL to expose this graph to all types. + * + * @return $this + * + * @throws \InvalidArgumentException + * If there are non-eligible entity types in the list. Eligible entity type + * IDs are those each fulfilling all the following conditions: + * - An entity type exists for that ID, + * - The entity type is a content entity type, + * - The entity type storage is an instance of RdfEntitySparqlStorage. + * + * @see \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage + */ + public function setEntityTypeIds(?array $entity_type_ids): self; + } From 5b62742eb99aa34167e6d30fcd3807b66ae71151 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:36:43 +0200 Subject: [PATCH 15/30] Refactor \Drupal\rdf_entity\RdfGraphHandler::getGraphDefinitions(). --- src/RdfGraphHandler.php | 47 ++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/RdfGraphHandler.php b/src/RdfGraphHandler.php index 117700fd..8e160921 100644 --- a/src/RdfGraphHandler.php +++ b/src/RdfGraphHandler.php @@ -89,6 +89,13 @@ class RdfGraphHandler { */ protected $targetGraph = NULL; + /** + * The RDF entity graph config entity storage. + * + * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface + */ + protected $rdfEntityGraphStorage; + /** * Constructs a RDF graph handler object. * @@ -139,18 +146,25 @@ public function resetRequestGraphs(array $entity_ids = []) { * * @return array * A structured array of graph definitions containing a title and a - * description. The array keys are the machine names of the graphs. + * description. The array keys are the machine names of the graphs. */ public function getGraphDefinitions($entity_type_id) { - $graphs_definition = []; - $graphs_definition['default'] = [ - 'title' => $this->t('Default'), - 'description' => $this->t('The default graph used to store entities of this type.'), - ]; - // @todo Consider turning this into an event. Advantages? - - $this->moduleHandler->alter('rdf_graph_definition', $entity_type_id, $graphs_definition); - return $graphs_definition; + $query = $this->getRdfEntityGraphStorage()->getQuery(); + $ids = $query->condition($query->orConditionGroup() + // An empty value, such as NULL or [], means "all entity types". + ->notExists('entity_types') + ->condition('entity_types', []) + ->condition('entity_types.*', $entity_type_id) + )->condition('status', TRUE) + ->execute(); + $graphs = $this->getRdfEntityGraphStorage()->loadMultiple($ids); + + return array_map(function (RdfEntityGraphInterface $graph): array { + return [ + 'title' => $graph->label(), + 'description' => $graph->getDescription(), + ]; + }, $graphs); } /** @@ -492,4 +506,17 @@ protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle return $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name]; } + /** + * Returns the RDF entity graph config entity storage service. + * + * @return \Drupal\Core\Config\Entity\ConfigEntityStorageInterface + * The RDF entity graph config entity storage service. + */ + protected function getRdfEntityGraphStorage() { + if (!isset($this->rdfEntityGraphStorage)) { + $this->rdfEntityGraphStorage = $this->entityTypeManager->getStorage('rdf_entity_graph'); + } + return $this->rdfEntityGraphStorage; + } + } From 7fa3c20934026257f2d9c0e91c7dc9771144eb8b Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:37:55 +0200 Subject: [PATCH 16/30] Add the 'draft' graph. --- modules/rdf_draft/config/install/rdf_entity.graph.draft.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 modules/rdf_draft/config/install/rdf_entity.graph.draft.yml diff --git a/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml b/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml new file mode 100644 index 00000000..7deb13ff --- /dev/null +++ b/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml @@ -0,0 +1,6 @@ +langcode: en +status: true +id: draft +name: Draft +description: 'Draft graph.' +entity_types: null From d19927b566f80406370acbcee332286fa411505e Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 16:38:45 +0200 Subject: [PATCH 17/30] Change the label of 'default' graph to 'Published' when rdf_draft module gets enabled. --- modules/rdf_draft/rdf_draft.install | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 modules/rdf_draft/rdf_draft.install diff --git a/modules/rdf_draft/rdf_draft.install b/modules/rdf_draft/rdf_draft.install new file mode 100644 index 00000000..5b26b0eb --- /dev/null +++ b/modules/rdf_draft/rdf_draft.install @@ -0,0 +1,19 @@ +setName('Published')->save(); + } +} From 2f7a793f46caf7daa82ab1527e4c18bdf8d3bf2f Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 10 Jan 2018 22:12:50 +0200 Subject: [PATCH 18/30] Completely modernize RdfGraphHandler & RdfEntitySparqlStorage --- API.md | 98 +++ config/install/rdf_entity.graph.default.yml | 1 + config/schema/rdf_entity.schema.yml | 3 + .../config/install/rdf_entity.graph.draft.yml | 1 + .../config/schema/rdf_draft.schema.yml | 16 - modules/rdf_draft/rdf_draft.info.yml | 2 +- modules/rdf_draft/rdf_draft.install | 13 + modules/rdf_draft/rdf_draft.links.menu.yml | 5 - modules/rdf_draft/rdf_draft.module | 72 +-- modules/rdf_draft/rdf_draft.routing.yml | 7 - modules/rdf_draft/rdf_draft.services.yml | 1 + .../src/Controller/RdfController.php | 4 +- .../EventSubscriber/ActiveGraphSubscriber.php | 81 ++- modules/rdf_draft/src/Form/SettingsForm.php | 89 --- modules/rdf_draft/src/RdfGraphAccessCheck.php | 11 +- .../rdf_entity.mapping.rdf_entity.fruit.yml | 25 + .../install/rdf_entity.rdfentity.fruit.yml | 7 + .../rdf_draft_test/rdf_draft_test.info.yml | 7 + .../tests/src/Kernel/RdfDraftGraphTest.php | 123 ++++ modules/rdf_taxonomy/src/TermRdfStorage.php | 2 +- ...ty.mapping.taxonomy_term.taxonomy_test.yml | 6 +- rdf_entity.install | 11 +- rdf_entity.links.action.yml | 6 + rdf_entity.links.menu.yml | 6 + rdf_entity.module | 46 +- rdf_entity.routing.yml | 52 +- rdf_entity.services.yml | 2 +- src/ActiveGraphEvent.php | 137 ++++- src/Controller/RdfEntityGraphToggle.php | 73 +++ src/Entity/Controller/RdfListBuilder.php | 3 +- src/Entity/Query/Sparql/Query.php | 29 +- src/Entity/Query/Sparql/SparqlCondition.php | 2 +- src/Entity/Rdf.php | 9 +- src/Entity/RdfEntityGraph.php | 75 ++- src/Entity/RdfEntityMapping.php | 69 ++- src/Entity/RdfEntitySparqlStorage.php | 561 +++++++++--------- src/Form/RdfEntityGraphForm.php | 103 ++++ src/ParamConverter/RdfEntityConverter.php | 33 +- .../pathauto/AliasType/RdfEntityAliasType.php | 4 +- src/RdfEntityGraphAccessControlHandler.php | 35 ++ src/RdfEntityGraphInterface.php | 25 +- src/RdfEntityGraphListBuilder.php | 85 +++ src/RdfEntityMappingInterface.php | 16 +- src/RdfEntitySparqlStorageInterface.php | 135 +++++ src/RdfGraphHandler.php | 513 +++------------- src/RdfGraphHandlerInterface.php | 160 +++++ .../rdf_entity.mapping.rdf_entity.dummy.yml | 8 +- ...f_entity.mapping.rdf_entity.multifield.yml | 10 +- ...f_entity.mapping.rdf_entity.with_owner.yml | 12 +- tests/src/Kernel/SparqlEntityInsertTest.php | 2 +- tests/src/Kernel/SparqlEntityQueryTest.php | 2 +- 51 files changed, 1762 insertions(+), 1036 deletions(-) create mode 100644 API.md delete mode 100644 modules/rdf_draft/config/schema/rdf_draft.schema.yml delete mode 100644 modules/rdf_draft/rdf_draft.links.menu.yml delete mode 100644 modules/rdf_draft/rdf_draft.routing.yml delete mode 100644 modules/rdf_draft/src/Form/SettingsForm.php create mode 100644 modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.mapping.rdf_entity.fruit.yml create mode 100644 modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.rdfentity.fruit.yml create mode 100644 modules/rdf_draft/tests/modules/rdf_draft_test/rdf_draft_test.info.yml create mode 100644 modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php create mode 100644 src/Controller/RdfEntityGraphToggle.php create mode 100644 src/Form/RdfEntityGraphForm.php create mode 100644 src/RdfEntityGraphAccessControlHandler.php create mode 100644 src/RdfEntityGraphListBuilder.php create mode 100644 src/RdfEntitySparqlStorageInterface.php create mode 100644 src/RdfGraphHandlerInterface.php diff --git a/API.md b/API.md new file mode 100644 index 00000000..f3ce3429 --- /dev/null +++ b/API.md @@ -0,0 +1,98 @@ +# RDF Entity API + +@todo This document is under development. + +## RDF Graphs + +Entities using the SPARQL storage can be stored in different graphs, which are +actually versions of the same entity, You can use graphs, for example, to store +a draft version of the entity. + +### RDF graphs storage + +Graphs are handled by the `RdfGraphHandler` service which is injected in the +`Query` and the `RdfEntitySparqlStorage` classes. There are a number of methods +offered to handle the graphs. + +### Graphs CRUD + +Graphs are config entities of type `rdf_entity_graph` and are supporting the +whole Drupal API regarding config entities. You can add, edit, delete graphs +also using the UI, at `/admin/config/rdf_entity/graph`. The `default` graph, +shipped with `rdf_entity` module cannot be deleted or restricted to specific +entity types. However, you can still edit its name and description. Only enabled +graphs are taken into account by the SPARQL backend. + +The order of graph entities is important. You can configure a priority by +settings the `weight` property. Also this could be done in the UI. + +### Handling entities and graphs + +#### Entity creation + +Set a specific graph to a new entity: + +```php +$storage = \Drupal::entityTypeManager()->getStorage('food'); +$entity = $storage->create([ + 'id' => 'http://example.com', + 'type' => 'fruit', + 'graph' => 'draft', +]); +$entity->save(); +``` + +If no `'graph'` is set, the entity will be saved in the topmost graph. The +topmost graph is the graph witch has the lowest `weight` property. + +#### Reading the graph of an entity + +```php +$graph_id = $entity->get('graph')->value; +// or... +$graph_id = $entity->graph->value; + +``` + +#### Loading an entity from a specific graph + +```php +$storage = \Drupal::entityTypeManager()->getStorage('food'); + +// Load from the default graph (the tompost graph in the list). +$entity = $storage->load($id); + +// Load from the 'draft' graph. +$entity = $storage->load($id, ['draft']); + +// Load from the first graph where the entity exists. First, the storage will +// attempt to load the entity from the 'draft' graph. If this entity doesn't +// exist in the 'draft' graph, will fallback to the next one which is 'sync' and +// so on. If fails with all, the normal behaviour is in place: will return NULL. +$entity = $storage->load($id, ['draft', 'sync', 'obsolete', ...]); + +// Load multiple entities using a graph candidate list. +$entities = $storage->loadMultiple($ids, ['draft', 'sync', 'obsolete', ...]); +``` + +**Note**: When the list of graph candidates is not specified (first example), +the candidates are all enabled graph entities, ordered by the weight property. + +#### Saving in a different graph + +```php +$storage = \Drupal::entityTypeManager()->getStorage('food'); +$entity = $storage->load($id, ['draft']); +$entity->set('graph', 'default')->save(); +``` + +#### Using graphs with entity query + +```php +$storage = \Drupal::entityTypeManager()->getStorage('food'); +$query = $storage->getQuery; +$ids = $query + ->condition('type', 'fruit') + ->setGraphType(['default', 'draft']) + ->execute(); +``` diff --git a/config/install/rdf_entity.graph.default.yml b/config/install/rdf_entity.graph.default.yml index 6208191a..6772c5ce 100644 --- a/config/install/rdf_entity.graph.default.yml +++ b/config/install/rdf_entity.graph.default.yml @@ -1,6 +1,7 @@ langcode: en status: true id: default +weight: 0 name: Default description: 'Default graph. This is available for all entity types and bundles.' entity_types: null diff --git a/config/schema/rdf_entity.schema.yml b/config/schema/rdf_entity.schema.yml index 8f782e92..8516a3fe 100644 --- a/config/schema/rdf_entity.schema.yml +++ b/config/schema/rdf_entity.schema.yml @@ -63,6 +63,9 @@ rdf_entity.graph.*: id: type: string label: ID + weight: + type: integer + label: Weight name: type: label label: Name diff --git a/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml b/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml index 7deb13ff..f078e3c8 100644 --- a/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml +++ b/modules/rdf_draft/config/install/rdf_entity.graph.draft.yml @@ -1,6 +1,7 @@ langcode: en status: true id: draft +weight: 10 name: Draft description: 'Draft graph.' entity_types: null diff --git a/modules/rdf_draft/config/schema/rdf_draft.schema.yml b/modules/rdf_draft/config/schema/rdf_draft.schema.yml deleted file mode 100644 index 87583d87..00000000 --- a/modules/rdf_draft/config/schema/rdf_draft.schema.yml +++ /dev/null @@ -1,16 +0,0 @@ -rdf_draft.settings: - type: config_object - label: 'Email settings' - mapping: - revision_bundle_rdf_entity: - type: sequence - sequence: - type: string - default_save_graph_rdf_entity: - type: string - revision_bundle_taxonomy_term: - type: sequence - sequence: - type: string - default_save_graph_taxonomy_term: - type: string diff --git a/modules/rdf_draft/rdf_draft.info.yml b/modules/rdf_draft/rdf_draft.info.yml index 605110ca..c4d20df5 100644 --- a/modules/rdf_draft/rdf_draft.info.yml +++ b/modules/rdf_draft/rdf_draft.info.yml @@ -2,6 +2,6 @@ name: 'RDF draft' type: module description: 'Brings the notion of a draft graph to RDF entities.' core: 8.x -package: Joinup +package: Custom dependencies: - rdf_entity diff --git a/modules/rdf_draft/rdf_draft.install b/modules/rdf_draft/rdf_draft.install index 5b26b0eb..c3353914 100644 --- a/modules/rdf_draft/rdf_draft.install +++ b/modules/rdf_draft/rdf_draft.install @@ -5,6 +5,7 @@ * Includes installation functions for the rdf_draft module. */ +use Drupal\Core\Serialization\Yaml; use Drupal\rdf_entity\Entity\RdfEntityGraph; use Drupal\rdf_entity\RdfEntityGraphInterface; @@ -17,3 +18,15 @@ function rdf_draft_install() { $default->setName('Published')->save(); } } + +/** + * Install the 'draft' config entity. + */ +function rdf_draft_update_8001() { + // Update or post-update scripts might need this config entity available when + // they run. We don't wait on configuration synchronization, because that runs + // usually after the database update, so we make this entity available in an + // early stage of updates. + $values = Yaml::decode(file_get_contents(__DIR__ . '/config/install/rdf_entity.graph.draft.yml')); + RdfEntityGraph::create($values)->save(); +} diff --git a/modules/rdf_draft/rdf_draft.links.menu.yml b/modules/rdf_draft/rdf_draft.links.menu.yml deleted file mode 100644 index 552f3c08..00000000 --- a/modules/rdf_draft/rdf_draft.links.menu.yml +++ /dev/null @@ -1,5 +0,0 @@ -rdf_draft.settings: - title: 'RDF draft settings' - parent: rdf_entity.admin_index - description: 'Administer RDF draft settings.' - route_name: rdf_draft.settings diff --git a/modules/rdf_draft/rdf_draft.module b/modules/rdf_draft/rdf_draft.module index 79882533..f78296f2 100644 --- a/modules/rdf_draft/rdf_draft.module +++ b/modules/rdf_draft/rdf_draft.module @@ -7,23 +7,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; - -/** - * Implements hook_rdf_graph_definition_alter(). - */ -function rdf_draft_rdf_graph_definition_alter($entity_type_id, &$definitions) { - // @todo Replace this hard-coded setting with a settings form - // where you can enable entity types. - if (!(\Drupal::entityManager()->getStorage($entity_type_id) instanceof RdfEntitySparqlStorage)) { - return; - } - - $definitions['default']['title'] = t('Published'); - $definitions['draft'] = [ - 'title' => t('Draft'), - 'description' => t('The draft graph used to store entities of this type.'), - ]; -} +use Drupal\rdf_entity\RdfEntityGraphInterface; /** * Implements hook_entity_type_alter(). @@ -35,29 +19,20 @@ function rdf_draft_entity_type_alter(array &$entity_types) { return; } - $config = \Drupal::config('rdf_draft.settings'); - foreach ($entity_types as $entity_type_id => $entity_type) { - if ( - // This entity type is not eligible for RDF Draft. - !($bundles = $config->get("revision_bundle_{$entity_type_id}")) - // Or doesn't have any 'draft' enabled bundle. - || !array_filter($bundles) - // Or doesn't have a view builder class. - || !$entity_type->hasViewBuilderClass() - // Or is missing a canonical link template. - || !$entity_type->hasLinkTemplate('canonical') - ) { - continue; - } + /** @var \Drupal\rdf_entity\RdfGraphHandler $graph_handler */ + $graph_handler = \Drupal::service('sparql.graph_handler'); - $recurse = TRUE; - $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id); - $recurse = FALSE; - $definitions = $storage->getGraphDefinitions(); - unset($definitions['default']); - // Set the link templates for each graph type. - foreach ($definitions as $name => $definition) { - $entity_type->setLinkTemplate('rdf-draft-' . $name, "/$entity_type_id/{{$entity_type_id}}/graph/$name"); + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + foreach ($entity_types as $entity_type_id => $entity_type) { + if ($entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical')) { + $recurse = TRUE; + $graphs = $graph_handler->getGraphDefinitions($entity_type_id); + $recurse = FALSE; + foreach ($graphs as $graph_id => $graph) { + if ($graph_id !== RdfEntityGraphInterface::DEFAULT) { + $entity_type->setLinkTemplate("rdf-draft-$graph_id", "/$entity_type_id/{{$entity_type_id}}/graph/$graph_id"); + } + } } } } @@ -67,25 +42,18 @@ function rdf_draft_entity_type_alter(array &$entity_types) { */ function rdf_draft_entity_operation(EntityInterface $entity) { $operations = []; - $storage = \Drupal::service('entity.manager')->getStorage($entity->getEntityTypeId()); - if ($storage instanceof RdfEntitySparqlStorage) { - $definitions = $storage->getGraphDefinitions(); - foreach ($definitions as $name => $definition) { - if ($entity->hasLinkTemplate('rdf-draft-' . $name) && $entity->hasGraph($name)) { + $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); + if ($storage instanceof RdfEntitySparqlStorage) { + foreach ($storage->getGraphDefinitions() as $name => $definition) { + $link_template = "rdf-draft-$name"; + if ($entity->hasLinkTemplate($link_template) && $entity->hasGraph($name)) { $operations[$name] = [ 'title' => t('View @graph', ['@graph' => $name]), 'weight' => 100, - 'url' => $entity->toUrl('rdf-draft-' . $name), + 'url' => $entity->toUrl($link_template), ]; } } } return $operations; } - -/** - * Implements hook_rdf_default_active_graph_alter(). - */ -function rdf_draft_rdf_default_active_graph_alter($entity_type, &$graph) { - $graph[] = 'draft'; -} diff --git a/modules/rdf_draft/rdf_draft.routing.yml b/modules/rdf_draft/rdf_draft.routing.yml deleted file mode 100644 index 36e48676..00000000 --- a/modules/rdf_draft/rdf_draft.routing.yml +++ /dev/null @@ -1,7 +0,0 @@ -rdf_draft.settings: - path: '/admin/config/rdf_entity/draft-settings' - defaults: - _title: 'RDF draft settings' - _form: '\Drupal\rdf_draft\Form\SettingsForm' - requirements: - _permission: 'administer rdf entity' diff --git a/modules/rdf_draft/rdf_draft.services.yml b/modules/rdf_draft/rdf_draft.services.yml index 5bef71e9..bad5d395 100644 --- a/modules/rdf_draft/rdf_draft.services.yml +++ b/modules/rdf_draft/rdf_draft.services.yml @@ -6,6 +6,7 @@ services: - { name: event_subscriber } rdf_draft.subscriber: class: Drupal\rdf_draft\EventSubscriber\ActiveGraphSubscriber + arguments: ['@sparql.graph_handler'] tags: - { name: event_subscriber } diff --git a/modules/rdf_draft/src/Controller/RdfController.php b/modules/rdf_draft/src/Controller/RdfController.php index 8074d68b..1938cba9 100644 --- a/modules/rdf_draft/src/Controller/RdfController.php +++ b/modules/rdf_draft/src/Controller/RdfController.php @@ -66,10 +66,10 @@ public function view(RouteMatchInterface $route_match) { $parameter_name = $route_match->getRouteObject()->getOption('entity_type_id'); /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $route_match->getParameter($parameter_name); + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ $storage = $this->entityManager->getStorage($entity->getEntityTypeId()); $graph_name = $route_match->getRouteObject()->getOption('graph_name'); - $storage->setRequestGraphs($entity->id(), [$graph_name]); - $draft_entity = $storage->load($entity->id()); + $draft_entity = $storage->load($entity->id(), [$graph_name]); if (!$draft_entity) { // Should not occur: RdfGraphAccessCheck validates that the entity exists. throw new \Exception('Entity not loaded from graph'); diff --git a/modules/rdf_draft/src/EventSubscriber/ActiveGraphSubscriber.php b/modules/rdf_draft/src/EventSubscriber/ActiveGraphSubscriber.php index 8290ba04..9dd27a27 100644 --- a/modules/rdf_draft/src/EventSubscriber/ActiveGraphSubscriber.php +++ b/modules/rdf_draft/src/EventSubscriber/ActiveGraphSubscriber.php @@ -3,6 +3,8 @@ namespace Drupal\rdf_draft\EventSubscriber; use Drupal\rdf_entity\ActiveGraphEvent; +use Drupal\rdf_entity\Event\RdfEntityEvents; +use Drupal\rdf_entity\RdfGraphHandlerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -10,6 +12,23 @@ */ class ActiveGraphSubscriber implements EventSubscriberInterface { + /** + * The RDF graph handler service. + * + * @var \Drupal\rdf_entity\RdfGraphHandlerInterface + */ + protected $rdfGraphHandler; + + /** + * Constructs a new event subscriber object. + * + * @param \Drupal\rdf_entity\RdfGraphHandlerInterface $rdf_graph_handler + * The RDF graph handler service. + */ + public function __construct(RdfGraphHandlerInterface $rdf_graph_handler) { + $this->rdfGraphHandler = $rdf_graph_handler; + } + /** * Set the appropriate graph as an active graph for the entity. * @@ -33,16 +52,15 @@ class ActiveGraphSubscriber implements EventSubscriberInterface { * Thrown when the access is denied and redirects to user login page. */ public function graphForEntityConvert(ActiveGraphEvent $event) { - $defaults = $event->getDefaults(); + $defaults = $event->getRouteDefaults(); if ($defaults['_route']) { + $entity_type_id = $event->getEntityTypeId(); + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id); $route_parts = explode('.', $defaults['_route']); // On the edit form, load from draft graph if possible. if (array_search('edit_form', $route_parts)) { - $entity_type_id = substr($event->getDefinition()['type'], strlen('entity:')); - /** @var RdfEntitySparqlStorage $storage */ - $storage = \Drupal::entityManager()->getStorage($entity_type_id); - $storage->setRequestGraphs($event->getValue(), ['draft', 'default']); - $entity = $storage->load($event->getValue()); + $entity = $storage->load($event->getEntityId(), ['draft', 'default']); // If the entity is empty, it means the user tried to access the edit // route of a non existing entity. In that case, simply return and let // the rdf entity try to load the entity from all graphs. @@ -51,26 +69,22 @@ public function graphForEntityConvert(ActiveGraphEvent $event) { } // When drafting is enabled for this entity type, try to load the draft // version on the edit form. - if ($this->draftEnabled($entity_type_id, $entity->bundle())) { - $storage->setRequestGraphs($event->getValue(), ['draft', 'default']); + if ($this->rdfGraphHandler->bundleHasGraph($entity_type_id, $entity->bundle(), 'draft')) { + $event->setGraphs(['draft', 'default']); } else { - $storage->setRequestGraphs($event->getValue(), ['default']); + $event->setGraphs(['default']); } - $storage->setRequestGraphs($event->getValue(), ['draft', 'default']); } // Viewing the entity on a graph specific tab. elseif (isset($route_parts[2]) && (strpos($route_parts[2], 'rdf_draft_') === 0)) { // Retrieve the graph name from the route. - $graph_name = str_replace('rdf_draft_', '', $route_parts[2]); - $event->setGraph($graph_name); + $graph_id = str_replace('rdf_draft_', '', $route_parts[2]); + $event->setGraphs([$graph_id]); } // On the canonical route, the default entity is preferred. elseif (isset($route_parts[2]) && $route_parts[2] === 'canonical') { - $entity_type_id = substr($event->getDefinition()['type'], strlen('entity:')); - /** @var RdfEntitySparqlStorage $storage */ - $storage = \Drupal::entityManager()->getStorage($entity_type_id); - $storage->setRequestGraphs($event->getValue(), ['default', 'draft']); + $event->setGraphs(['default', 'draft']); } } } @@ -79,38 +93,9 @@ public function graphForEntityConvert(ActiveGraphEvent $event) { * {@inheritdoc} */ public static function getSubscribedEvents() { - $events['rdf_graph.entity_convert'][] = ['graphForEntityConvert']; - return $events; - } - - /** - * Check if user enabled draft for this bundle. - * - * @param string $entity_type_id - * Entity type name. - * @param string $bundle - * Bundle name. - * - * @return bool - * Enabled? - */ - protected function draftEnabled($entity_type_id, $bundle) { - $enabled_bundles = \Drupal::config('rdf_draft.settings')->get('revision_bundle_' . $entity_type_id); - return !empty($enabled_bundles[$bundle]); - } - - /** - * Get the graph to use when storing a entity through the create form. - * - * @param string $entity_type_id - * The entity type id. - * - * @return string - * The graph to use as default when creating entities. - */ - protected function defaultSaveGraph($entity_type_id) { - $default_save_graph = \Drupal::config('rdf_draft.settings')->get('default_save_graph_' . $entity_type_id); - return !empty($default_save_graph) ? $default_save_graph : 'default'; + return [ + RdfEntityEvents::GRAPH_ENTITY_CONVERT => ['graphForEntityConvert'], + ]; } } diff --git a/modules/rdf_draft/src/Form/SettingsForm.php b/modules/rdf_draft/src/Form/SettingsForm.php deleted file mode 100644 index 7445c096..00000000 --- a/modules/rdf_draft/src/Form/SettingsForm.php +++ /dev/null @@ -1,89 +0,0 @@ -getDefinitions(); - foreach ($definitions as $name => $definition) { - $storage = \Drupal::entityManager()->getStorage($name); - if ($storage instanceof RdfEntitySparqlStorage) { - $enabled_bundles = $this->config('rdf_draft.settings')->get('revision_bundle_' . $name); - $bundle_type = $definition->getBundleEntityType(); - $bundles = \Drupal::entityTypeManager()->getStorage($bundle_type)->loadMultiple(); - $options = []; - foreach ($bundles as $bundle_name => $bundle) { - $options[$bundle_name] = $bundle->label(); - } - $form['revision_bundles:' . $name] = [ - '#type' => 'checkboxes', - '#title' => $this->t('Enable save as draft for %entity_type bundles', ['%entity_type' => $name]), - '#options' => $options, - ]; - if ($enabled_bundles) { - $form['revision_bundles:' . $name]['#default_value'] = $enabled_bundles; - } - $graphs = []; - /** @var ContentEntityType $def */ - foreach ($storage->getGraphDefinitions() as $key => $def) { - $graphs[$key] = $def['title']; - } - $default_save_graph = $this->config('rdf_draft.settings')->get('default_save_graph_' . $name); - $form['default_save_graph:' . $name] = [ - '#title' => $this->t('Default graph to save to'), - '#type' => 'select', - '#options' => $graphs, - ]; - if ($default_save_graph) { - $form['default_save_graph:' . $name]['#default_value'] = $default_save_graph; - } - } - } - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - parent::submitForm($form, $form_state); - $definitions = \Drupal::entityTypeManager()->getDefinitions(); - foreach ($definitions as $name => $definition) { - $storage = \Drupal::entityManager()->getStorage($name); - if ($storage instanceof RdfEntitySparqlStorage) { - $config = $this->config('rdf_draft.settings') - ->set('revision_bundle_' . $name, $form_state->getValue('revision_bundles:' . $name)) - ->set('default_save_graph_' . $name, $form_state->getValue('default_save_graph:' . $name)); - $config->save(); - } - } - } - -} diff --git a/modules/rdf_draft/src/RdfGraphAccessCheck.php b/modules/rdf_draft/src/RdfGraphAccessCheck.php index 71af5b55..d093e877 100644 --- a/modules/rdf_draft/src/RdfGraphAccessCheck.php +++ b/modules/rdf_draft/src/RdfGraphAccessCheck.php @@ -8,6 +8,7 @@ use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\Session\AccountInterface; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; +use Drupal\rdf_entity\RdfEntityGraphInterface; use Drupal\rdf_entity\RdfInterface; use Symfony\Component\Routing\Route; @@ -55,18 +56,14 @@ public function access(Route $route, AccountInterface $account, RdfInterface $rd // The active graph is the published graph. It is handled by the default // operation handler. // @todo: getActiveGraph is not the default. We should load from settings. - $default_graph = $storage->getBundleGraphUri($rdf_entity->bundle(), 'default'); - $requested_graph = $storage->getBundleGraphUri($rdf_entity->bundle(), $graph); + $default_graph = $storage->getGraphHandler()->getBundleGraphUri($rdf_entity->getEntityTypeId(), $rdf_entity->bundle(), RdfEntityGraphInterface::DEFAULT); + $requested_graph = $storage->getGraphHandler()->getBundleGraphUri($rdf_entity->getEntityTypeId(), $rdf_entity->bundle(), $graph); if ($requested_graph == $default_graph) { return AccessResult::neutral(); } - $active_graph_type = $storage->getRequestGraphs($rdf_entity->id()); // Check if there is an entity saved in the passed graph. - $storage->setRequestGraphs($rdf_entity->id(), [$graph]); - $entity = $storage->load($rdf_entity->id()); - // Restore active graph. - $storage->setRequestGraphs($rdf_entity->id(), $active_graph_type); + $entity = $storage->load($rdf_entity->id(), [$graph]); // @todo: When the requested graph is the only one and it is not the // default, it is loaded in the default view, so maybe there is no need diff --git a/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.mapping.rdf_entity.fruit.yml b/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.mapping.rdf_entity.fruit.yml new file mode 100644 index 00000000..3f6444fb --- /dev/null +++ b/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.mapping.rdf_entity.fruit.yml @@ -0,0 +1,25 @@ +langcode: en +status: true +dependencies: + config: + - rdf_entity.graph.default + - rdf_entity.graph.draft + - rdf_entity.rdfentity.fruit +third_party_settings: { } +id: rdf_entity.fruit +entity_type_id: rdf_entity +bundle: fruit +rdf_type: 'http://example.com/fruit/type' +base_fields_mapping: + rid: + target_id: + predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' + format: resource + label: + value: + predicate: 'http://example.com/fruit/name' + format: t_literal +graph: + default: 'http://example.com/fruit/graph/default' + draft: 'http://example.com/fruit/graph/draft' +entity_id_plugin: default diff --git a/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.rdfentity.fruit.yml b/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.rdfentity.fruit.yml new file mode 100644 index 00000000..4a3c567d --- /dev/null +++ b/modules/rdf_draft/tests/modules/rdf_draft_test/config/install/rdf_entity.rdfentity.fruit.yml @@ -0,0 +1,7 @@ +langcode: en +status: true +dependencies: { } +third_party_settings: { } +name: Fruit +rid: fruit +description: Fruits diff --git a/modules/rdf_draft/tests/modules/rdf_draft_test/rdf_draft_test.info.yml b/modules/rdf_draft/tests/modules/rdf_draft_test/rdf_draft_test.info.yml new file mode 100644 index 00000000..267b4116 --- /dev/null +++ b/modules/rdf_draft/tests/modules/rdf_draft_test/rdf_draft_test.info.yml @@ -0,0 +1,7 @@ +type: module +core: 8.x +name: 'RDF Draft Test' +description: 'Testing module for RDF Draft.' +package: Testing +dependencies: + - rdf_draft diff --git a/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php b/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php new file mode 100644 index 00000000..3e241f2d --- /dev/null +++ b/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php @@ -0,0 +1,123 @@ +setUpSparql(); + $this->installConfig(['rdf_entity', 'rdf_draft', 'rdf_draft_test']); + } + + /** + * Tests graphs. + */ + public function test() { + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $manager */ + $manager = $this->container->get('entity_type.manager'); + /** @var \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage $storage */ + $storage = $manager->getStorage('rdf_entity'); + + /** @var \Drupal\rdf_entity\RdfInterface $apple */ + $apple = $storage->create([ + 'rid' => 'fruit', + 'label' => 'Draft of Apple', + 'graph' => 'draft', + ]); + $apple->save(); + + $id = $apple->id(); + + // Check that, by default, only the draft exists. + $apple = $storage->load($id); + $this->assertEquals('draft', $apple->graph->value); + $this->assertFalse($storage->hasGraph($apple, 'default')); + + // Check cascading over the graph candidate list. + $apple = $storage->load($id, ['default', 'draft']); + $this->assertEquals('draft', $apple->graph->value); + + // Add the 'default' graph. + $apple + ->set('graph', 'default') + ->setName('Apple') + ->save(); + + // Check that, by default, the 'default' graph is loaded. + $apple = $storage->load($id); + $this->assertEquals('default', $apple->graph->value); + $this->assertTrue($storage->hasGraph($apple, 'default')); + $this->assertTrue($storage->hasGraph($apple, 'draft')); + + // Try to request the entity from a non-existing graph. + $apple = $storage->load($id, ['invalid graph']); + $this->assertNull($apple); + + // Check cascading over the graph candidate list. + $apple = $storage->load($id, ['invalid graph', 'default', 'draft']); + $this->assertEquals('default', $apple->graph->value); + $this->assertEquals('Apple', $apple->label()); + + $apple = $storage->load($id, ['invalid graph', 'draft', 'default']); + $this->assertEquals('draft', $apple->graph->value); + $this->assertEquals('Draft of Apple', $apple->label()); + + // Create a new graph and add it to the mapping. + RdfEntityGraph::create([ + 'id' => 'arbitrary', + 'label' => 'Some graph', + ])->save(); + RdfEntityMapping::loadByName('rdf_entity', 'fruit') + ->addGraphs(['arbitrary' => 'http://example.com/fruit/graph/arbitrary']) + ->save(); + + $apple + ->set('graph', 'arbitrary') + ->setName('Apple in arbitrary graph') + ->save(); + + $apple = $storage->load($id, ['arbitrary']); + $this->assertEquals('arbitrary', $apple->graph->value); + $this->assertEquals('Apple in arbitrary graph', $apple->label()); + + // Delete the draft version. + $storage->deleteFromGraph($apple->id(), 'draft'); + $this->assertNull($storage->load($id, ['draft'])); + $this->assertNotNull($storage->load($id, ['default'])); + $this->assertNotNull($storage->load($id, ['arbitrary'])); + + // Delete the entity. + $apple->delete(); + // All versions are gone. + $this->assertNull($storage->load($id, ['default'])); + $this->assertNull($storage->load($id, ['arbitrary'])); + } + +} diff --git a/modules/rdf_taxonomy/src/TermRdfStorage.php b/modules/rdf_taxonomy/src/TermRdfStorage.php index d7a92c02..abee5496 100644 --- a/modules/rdf_taxonomy/src/TermRdfStorage.php +++ b/modules/rdf_taxonomy/src/TermRdfStorage.php @@ -97,7 +97,7 @@ protected function doPreSave(EntityInterface $entity) { /** * {@inheritdoc} */ - protected function alterGraph(Graph &$graph, EntityInterface $entity) { + protected function alterGraph(Graph &$graph, EntityInterface $entity): void { parent::alterGraph($graph, $entity); // @todo Document this. I have no idea what this is for, I only know that // taxonomy terms require this. diff --git a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml index 72ee508f..d3c80486 100644 --- a/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml +++ b/modules/rdf_taxonomy/tests/modules/rdf_taxonomy_test/config/install/rdf_entity.mapping.taxonomy_term.taxonomy_test.yml @@ -1,6 +1,9 @@ langcode: en status: true -dependencies: { } +dependencies: + config: + - rdf_entity.graph.default + - taxonomy.vocabulary.taxonomy_test third_party_settings: { } id: taxonomy_term.taxonomy_test entity_type_id: taxonomy_term @@ -8,7 +11,6 @@ bundle: taxonomy_test rdf_type: 'http://example.com/rdf_taxonomy/test' graph: default: 'http://example.com/rdf_taxonomy/published' - draft: 'http://example.com/rdf_taxonomy/draft' base_fields_mapping: uuid: value: diff --git a/rdf_entity.install b/rdf_entity.install index 0f314650..1fea387c 100644 --- a/rdf_entity.install +++ b/rdf_entity.install @@ -5,6 +5,8 @@ * Includes installation functions for the rdf_entity module. */ +use Drupal\Core\Serialization\Yaml; +use Drupal\rdf_entity\Entity\RdfEntityGraph; use Drupal\rdf_entity\Entity\RdfEntityMapping; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; @@ -33,9 +35,16 @@ function rdf_entity_requirements($phase) { /** * Move RDF entity mapping data from bundle entities into dedicated entities. */ -function rdf_entity_update_8100() { +function rdf_entity_update_8001() { $entity_type_manager = \Drupal::entityTypeManager(); + // Update or post-update scripts might need this config entity available when + // they run. We don't wait on configuration synchronization, because that runs + // usually after the database update, so we make this entity available in an + // early stage of updates. + $values = Yaml::decode(file_get_contents(__DIR__ . '/config/install/rdf_entity.graph.default.yml')); + RdfEntityGraph::create($values)->save(); + // Iterate over all entities that are bundles of content entities with // RdfEntitySparqlStorage and move their 3rd party settings belonging to // rdf_entity module into their dedicated rdf_entity_mapping config entities. diff --git a/rdf_entity.links.action.yml b/rdf_entity.links.action.yml index 3de67989..0f0fd435 100755 --- a/rdf_entity.links.action.yml +++ b/rdf_entity.links.action.yml @@ -15,3 +15,9 @@ entity.rdf_type.add_form: title: 'Add RDF bundle' appears_on: - entity.rdf_type.collection + +rdf_entity_graph.add: + route_name: rdf_entity_graph.add + title: 'Add graph' + appears_on: + - entity.rdf_entity_graph.collection diff --git a/rdf_entity.links.menu.yml b/rdf_entity.links.menu.yml index b287d308..be8e5b17 100755 --- a/rdf_entity.links.menu.yml +++ b/rdf_entity.links.menu.yml @@ -18,3 +18,9 @@ rdf_entity.admin_index: description: 'Administer the suite of RDF modules.' position: right weight: -5 + +entity.rdf_entity_graph.collection: + title: 'RDF graphs' + parent: rdf_entity.admin_index + description: 'Create and manage RDF entity graphs.' + route_name: entity.rdf_entity_graph.collection diff --git a/rdf_entity.module b/rdf_entity.module index 92c98929..60c3885f 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -11,6 +11,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBundleBase; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Database\Database; use Drupal\Core\Entity\BundleEntityFormBase; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -18,9 +19,11 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Url; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\FieldStorageConfigInterface; use Drupal\rdf_entity\Entity\RdfEntityMapping; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; use Drupal\rdf_entity\RdfEntityGraphInterface; +use Drupal\rdf_entity\RdfEntitySparqlStorageInterface; use Drupal\rdf_entity\RdfFieldHandler; use Drupal\rdf_entity\RdfInterface; use EasyRdf\Http; @@ -33,6 +36,8 @@ function rdf_entity_entity_base_field_info_alter(&$fields, EntityTypeInterface $ return; } + // @todo Now that graphs are entities, transform this field into an entity + // reference field. $fields['graph'] = BaseFieldDefinition::create('uri') ->setLabel(t('The graph where the entity is stored.')) ->setTargetEntityTypeId('rdf_entity') @@ -120,7 +125,7 @@ function rdf_entity_form_field_storage_config_edit_form_alter(&$form, FormStateI */ function rdf_entity_get_third_party_property(ConfigEntityInterface $object, $property, $column, $default = NULL) { // Mapping data requested for a configurable field. - if ($object instanceof \Drupal\field\FieldStorageConfigInterface) { + if ($object instanceof FieldStorageConfigInterface) { $property_value = $object->getThirdPartySetting('rdf_entity', $property, FALSE); } // Mapping data requested for a bundle entity. @@ -155,11 +160,13 @@ function rdf_entity_form_alter_builder($entity_type, FieldStorageConfig $entity, * Implements hook_entity_create(). */ function rdf_entity_entity_create(EntityInterface $entity) { - $entity_type = $entity->getEntityTypeId(); - $storage = \Drupal::entityManager()->getStorage($entity_type); + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); if ($storage instanceof RdfEntitySparqlStorage) { - // The target graph should be the graph of the entity when it's created. - $entity->set('graph', $storage->getGraphHandler()->getTargetGraphFromEntity($entity)); + if ($entity->get('graph')->isEmpty()) { + // If no graph has been explicitly set, use the top entry. + $entity->set('graph', $storage->getGraphHandler()->getDefaultGraphId($entity->getEntityTypeId())); + } } } @@ -292,7 +299,7 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { * * @param string $entity_type_id * The entity type ID. - * @param ConfigEntityInterface $bundle_entity + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle_entity * The bundle entity being built. * @param array $form * The form API form render array. @@ -537,3 +544,30 @@ function rdf_entity_pathauto_alias_types_alter(array &$definitions) { // @see \Drupal\rdf_entity\Plugin\pathauto\AliasType\RdfEntityAliasType unset($definitions['canonical_entities:rdf_entity']); } + +/** + * Implements hook_cache_flush(). + */ +function rdf_entity_cache_flush() { + \Drupal::service('sparql.graph_handler')->clearCache(); +} + +/** + * Implements hook_entity_storage_load(). + */ +function rdf_entity_entity_storage_load(array $entities, $entity_type_id) { + $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id); + if ($storage instanceof RdfEntitySparqlStorageInterface) { + // Store the graph ID of the loaded entity to be, eventually, used when this + // entity gets saved. During the saving process, this value is passed to + // RdfEntitySparqlStorage::loadUnchanged() to correctly determine the + // original entity graph. This value persists in entity over an entity form + // submit, as the entity is stored in the form state, so that the entity + // save can rely on it. + // @see \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage::doPreSave() + // @see \Drupal\Core\Entity\EntityForm + array_walk($entities, function (ContentEntityInterface $entity): void { + $entity->rdfEntityOriginalGraph = $entity->get('graph')->value; + }); + } +} diff --git a/rdf_entity.routing.yml b/rdf_entity.routing.yml index d7380fe9..748b1fba 100755 --- a/rdf_entity.routing.yml +++ b/rdf_entity.routing.yml @@ -65,7 +65,7 @@ entity.rdf_type.collection: _entity_list: 'rdf_type' _title: 'Rdf type' requirements: - _permission: 'administer rdf_type' + _permission: 'administer rdf entity' entity.rdf_type.add_form: path: '/admin/structure/rdf_type/add' @@ -98,3 +98,53 @@ rdf_entity.admin_index: _title: 'Rdf entity' requirements: _permission: 'administer rdf entity' + +entity.rdf_entity_graph.collection: + path: '/admin/config/rdf_entity/graph' + defaults: + _entity_list: 'rdf_entity_graph' + _title: 'RDF Graphs' + requirements: + _permission: 'administer rdf entity' + +rdf_entity_graph.add: + path: '/admin/config/rdf_entity/graph/add' + defaults: + _entity_form: 'rdf_entity_graph.add' + _title: 'Add graph' + requirements: + _entity_create_access: rdf_entity_graph + +entity.rdf_entity_graph.edit_form: + path: '/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}' + defaults: + _entity_form: 'rdf_entity_graph.edit' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + requirements: + _entity_access: rdf_entity_graph.update + +entity.rdf_entity_graph.delete_form: + path: '/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/delete' + defaults: + _entity_form: 'rdf_entity_graph.delete' + _title: 'Delete' + requirements: + _entity_access: rdf_entity_graph.delete + +entity.rdf_entity_graph.enable: + path: '/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/enable' + defaults: + _controller: 'Drupal\rdf_entity\Controller\RdfEntityGraphToggle::toggle' + _title: Enable + toggle_operation: enable + requirements: + _custom_access: 'Drupal\rdf_entity\Controller\RdfEntityGraphToggle::access' + +entity.rdf_entity_graph.disable: + path: '/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/disable' + defaults: + _controller: 'Drupal\rdf_entity\Controller\RdfEntityGraphToggle::toggle' + _title: Disable + toggle_operation: disable + requirements: + _custom_access: 'Drupal\rdf_entity\Controller\RdfEntityGraphToggle::access' diff --git a/rdf_entity.services.yml b/rdf_entity.services.yml index d08e11dd..b2baa797 100644 --- a/rdf_entity.services.yml +++ b/rdf_entity.services.yml @@ -26,7 +26,7 @@ services: - { name: route_processor_outbound, priority: 200 } sparql.graph_handler: class: \Drupal\rdf_entity\RdfGraphHandler - arguments: ['@entity_type.manager', '@module_handler'] + arguments: ['@entity_type.manager'] sparql.field_handler: class: \Drupal\rdf_entity\RdfFieldHandler arguments: ['@entity_type.manager', '@entity_field.manager', '@event_dispatcher'] diff --git a/src/ActiveGraphEvent.php b/src/ActiveGraphEvent.php index 8cfd6a45..97cb2679 100644 --- a/src/ActiveGraphEvent.php +++ b/src/ActiveGraphEvent.php @@ -1,5 +1,7 @@ value = $value; - $this->definition = $definition; - $this->converterName = $name; - $this->defaults = $defaults; + public function __construct(string $parameter_name, string $entity_id, string $entity_type_id, $definition, array $route_defaults) { + $this->parameterName = $parameter_name; + $this->entityId = $entity_id; + $this->entityTypeId = $entity_type_id; + $this->parameterDefinition = $definition; + $this->routeDefaults = $route_defaults; + } + + /** + * The RDF entity value. + * + * @return string + * The RDF entity value. + */ + public function getEntityId(): string { + return $this->entityId; } /** - * Getter: The raw value. + * Returns the entity type ID. + * + * @return string + * The entity type ID. */ - public function getValue() { - return $this->value; + public function getEntityTypeId(): string { + return $this->entityTypeId; } /** - * Getter: The parameter definition provided in the route options. + * The parameter definition provided in the route options. + * + * @return mixed + * The parameter definition provided in the route options. */ - public function getDefinition() { - return $this->definition; + public function getParameterDefinition() { + return $this->parameterDefinition; } /** - * Getter: The name of the parameter. + * The name of the route parameter. + * + * @return string + * The parameter name. */ - public function getConverterName() { - return $this->converterName; + public function getParameterName(): string { + return $this->parameterName; } /** - * Getter: The route defaults array. + * The route defaults array. + * + * @return array + * The route defaults. */ - public function getDefaults() { - return $this->defaults; + public function getRouteDefaults(): array { + return $this->routeDefaults; } /** - * Getter: The active graph. + * Gets the list of graphs. + * + * @return string[]|null + * A list of graph IDs or NULL if none. */ - public function getGraph() { - return $this->graph; + public function getGraphs(): ?array { + return $this->graphs; } /** - * Setter: The graph used to load the entity. + * The graphs used to load the entity. + * + * @param string[] $graphs + * The graphs used to load the entity. + * + * @return $this */ - public function setGraph($graph) { - $this->graph = $graph; + public function setGraphs(array $graphs): self { + $this->graphs = $graphs; + return $this; } } diff --git a/src/Controller/RdfEntityGraphToggle.php b/src/Controller/RdfEntityGraphToggle.php new file mode 100644 index 00000000..a181adcb --- /dev/null +++ b/src/Controller/RdfEntityGraphToggle.php @@ -0,0 +1,73 @@ +status()) || + // The operation is 'disable' and the entity is already disabled. + ($toggle_operation === 'disable' && !$rdf_entity_graph->status()) || + // This is the 'default' RDF entity graph. + ($rdf_entity_graph->id() === RdfEntityGraphInterface::DEFAULT); + + return $forbidden ? AccessResult::forbidden() : AccessResult::allowed(); + } + + /** + * Toggles the RDF entity graph status. + * + * @param \Drupal\rdf_entity\RdfEntityGraphInterface $rdf_entity_graph + * The RDF graph entity. + * @param string $toggle_operation + * The operation: 'enable', 'disable'. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirect response. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * In case of failures on entity save. + */ + public function toggle(RdfEntityGraphInterface $rdf_entity_graph, string $toggle_operation) { + $arguments = [ + '%name' => $rdf_entity_graph->label(), + '%id' => $rdf_entity_graph->id(), + ]; + + if ($toggle_operation === 'enable') { + $rdf_entity_graph->enable()->save(); + $message = $this->t("The %name (%id) graph has been enabled.", $arguments); + } + else { + $rdf_entity_graph->disable()->save(); + $message = $this->t("The %name (%id) graph has been disabled.", $arguments); + } + drupal_set_message($message); + + return $this->redirect('entity.rdf_entity_graph.collection'); + } + +} diff --git a/src/Entity/Controller/RdfListBuilder.php b/src/Entity/Controller/RdfListBuilder.php index 9731f3dd..40e32559 100755 --- a/src/Entity/Controller/RdfListBuilder.php +++ b/src/Entity/Controller/RdfListBuilder.php @@ -60,6 +60,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI */ protected function getEntityIds() { $request = \Drupal::request(); + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $rdf_storage */ $rdf_storage = $this->getStorage(); /** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info */ $bundle_info = \Drupal::service('entity_type.bundle.info'); @@ -76,7 +77,7 @@ protected function getEntityIds() { } } else { - $query->setGraphType($rdf_storage->getGraphHandler()->getEntityTypeEnabledGraphs()); + $query->setGraphType($rdf_storage->getGraphHandler()->getEntityTypeGraphIds($rdf_storage->getEntityTypeId())); } if ($rid = $request->get('rid') ?: NULL) { diff --git a/src/Entity/Query/Sparql/Query.php b/src/Entity/Query/Sparql/Query.php index 352f3306..4b0b5daf 100644 --- a/src/Entity/Query/Sparql/Query.php +++ b/src/Entity/Query/Sparql/Query.php @@ -57,11 +57,11 @@ class Query extends QueryBase implements QueryInterface { protected $results = NULL; /** - * Entity storage. + * The SPARQL entity storage. * - * @var \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage + * @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface */ - protected $entityStorage = NULL; + protected $entityStorage; /** * The entity type manager service. @@ -196,28 +196,23 @@ public function execute() { } /** - * Set the graph types for the query. + * Sets the graph types for the query. * - * This allows the filtering of graphs on the query level. There are two ways - * to filter the results: - * - Set the graph types in this method. - * - Set the request graphs in the storage level. - * The query graph filter that is set below is filtering the graphs - * that the query will run on, so this makes this filter a runtime filter. - * After the results are retrieved, the storage will further filter the - * results based on the request graphs set for the entities. + * This allows the filtering of graphs on the query level. Set the graph IDs + * to restrict the entities to the list of graphs. If the parameter is not + * passed, the default, topmost graph is used. * - * @param array $graph_types - * An array of graphs ids to be passed into the query. + * @param string[]|null $graph_ids + * (optional) An array of graphs ids to be passed into the query. * * @todo: When a condition is set on the bundle, this graphs should be * filtered accordingly. * - * @see \Drupal\rdf_entity\RdfGraphHandler::setRequestGraphs() * @see \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage::processGraphResults() */ - public function setGraphType(array $graph_types = ['default']) { - $this->graphs = $this->entityStorage->getGraphHandler()->getEntityTypeGraphUrisList($this->entityType->getBundleEntityType(), $graph_types); + public function setGraphType(array $graph_ids = NULL) { + $graph_ids = $graph_ids ?: $this->getGraphHandler()->getDefaultGraphId($this->getEntityTypeId()); + $this->graphs = $this->entityStorage->getGraphHandler()->getEntityTypeGraphUrisFlatList($this->getEntityTypeId(), $graph_ids); } /** diff --git a/src/Entity/Query/Sparql/SparqlCondition.php b/src/Entity/Query/Sparql/SparqlCondition.php index 136d4a11..93c5c7b5 100644 --- a/src/Entity/Query/Sparql/SparqlCondition.php +++ b/src/Entity/Query/Sparql/SparqlCondition.php @@ -193,7 +193,7 @@ public function __construct($conjunction, QueryInterface $query, array $namespac parent::__construct($conjunction, $query, $namespaces); $this->graphHandler = $rdf_graph_handler; $this->fieldHandler = $rdf_field_handler; - $this->typePredicate = $query->getEntityStorage()->bundlePredicate(); + $this->typePredicate = $query->getEntityStorage()->getBundlePredicates(); $this->bundleKey = $query->getEntityType()->getKey('bundle'); $this->idKey = $query->getEntityType()->getKey('id'); $this->labelKey = $query->getEntityType()->getKey('label'); diff --git a/src/Entity/Rdf.php b/src/Entity/Rdf.php index 987591ca..5dcd09d4 100755 --- a/src/Entity/Rdf.php +++ b/src/Entity/Rdf.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\rdf_entity\RdfEntityGraphInterface; use Drupal\rdf_entity\RdfInterface; use Drupal\Core\Entity\EntityChangedTrait; use Drupal\user\UserInterface; @@ -250,7 +251,7 @@ public function getName() { * {@inheritdoc} */ public function setName($name) { - $this->set('name', $name); + $this->set('label', $name); return $this; } @@ -268,14 +269,14 @@ public function getWeight() { * {@inheritdoc} */ public function isPublished() { - /** @var \Drupal\rdf_entity\Entity\RdfEntitySparqlStorage $storage */ + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId()); - $published_graph = $storage->getBundleGraphUri($this->bundle(), 'default'); + $published_graph = $storage->getGraphHandler()->getBundleGraphUri($this->getEntityTypeId(), $this->bundle(), RdfEntityGraphInterface::DEFAULT); $entity_graph_name = $this->get('graph')->first()->getValue()['value']; if (empty($entity_graph_name)) { return FALSE; } - $entity_graph = $storage->getBundleGraphUri($this->bundle(), $entity_graph_name); + $entity_graph = $storage->getGraphHandler()->getBundleGraphUri($this->getEntityTypeId(), $this->bundle(), $entity_graph_name); return ($entity_graph === $published_graph); } diff --git a/src/Entity/RdfEntityGraph.php b/src/Entity/RdfEntityGraph.php index 936d4bff..b141702b 100644 --- a/src/Entity/RdfEntityGraph.php +++ b/src/Entity/RdfEntityGraph.php @@ -3,9 +3,6 @@ namespace Drupal\rdf_entity\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; -use Drupal\Core\Entity\Annotation\ConfigEntityType; -use Drupal\Core\Entity\ContentEntityTypeInterface; -use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\rdf_entity\RdfEntityGraphInterface; @@ -21,14 +18,33 @@ * entity_keys = { * "id" = "id", * "label" = "name", - * "status" = "status" + * "status" = "status", + * "weight" = "weight", * }, * config_export = { * "id", + * "weight", * "name", * "description", * "entity_types", * }, + * handlers = { + * "access" = "Drupal\rdf_entity\RdfEntityGraphAccessControlHandler", + * "form" = { + * "add" = "Drupal\rdf_entity\Form\RdfEntityGraphForm", + * "edit" = "Drupal\rdf_entity\Form\RdfEntityGraphForm", + * "delete" = "Drupal\Core\Entity\EntityDeleteForm" + * }, + * "list_builder" = "Drupal\rdf_entity\RdfEntityGraphListBuilder", + * }, + * links = { + * "edit-form" = "/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}", + * "delete-form" = "/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/delete", + * "collection" = "/admin/config/rdf_entity/graph", + * "enable" = "/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/enable", + * "disable" = "/admin/config/rdf_entity/graph/manage/{rdf_entity_graph}/disable", + * }, + * admin_permission = "administer rdf entity", * ) */ class RdfEntityGraph extends ConfigEntityBase implements RdfEntityGraphInterface { @@ -40,6 +56,13 @@ class RdfEntityGraph extends ConfigEntityBase implements RdfEntityGraphInterface */ protected $id; + /** + * The weight value is used to define the order in the list of graphs. + * + * @var int + */ + protected $weight = 0; + /** * The label of the RDF entity graph. * @@ -71,6 +94,21 @@ public function setName(string $name): RdfEntityGraphInterface { return $this; } + /** + * {@inheritdoc} + */ + public function setWeight(int $weight): RdfEntityGraphInterface { + $this->weight = $weight; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWeight(): int { + return $this->weight; + } + /** * {@inheritdoc} */ @@ -82,7 +120,7 @@ public function setDescription(string $description): RdfEntityGraphInterface { /** * {@inheritdoc} */ - public function getDescription(): string { + public function getDescription(): ?string { return $this->description; } @@ -130,12 +168,37 @@ public function delete() { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { - parent::preSave($storage); + if ($this->entity_types === []) { + // Normalize 'entity_types' empty array to NULL. + $this->entity_types = NULL; + } + if ($this->id() === static::DEFAULT) { if (!$this->status()) { throw new \RuntimeException("The '" . static::DEFAULT . "' graph cannot be disabled."); } + if ($this->getEntityTypeIds()) { + throw new \RuntimeException("The '" . static::DEFAULT . "' graph cannot be limited to certain entity types."); + } } + parent::preSave($storage); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + // Wipe out the static cache of the RDF entity graph handler. + \Drupal::service('sparql.graph_handler')->clearCache(); + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + \Drupal::service('sparql.graph_handler')->clearCache(); } } diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php index c4e619a0..5bcd7639 100644 --- a/src/Entity/RdfEntityMapping.php +++ b/src/Entity/RdfEntityMapping.php @@ -5,8 +5,8 @@ namespace Drupal\rdf_entity\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; -use Drupal\Core\Entity\Annotation\ConfigEntityType; use Drupal\Core\Entity\ContentEntityTypeInterface; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\rdf_entity\RdfEntityGraphInterface; use Drupal\rdf_entity\RdfEntityMappingInterface; @@ -264,4 +264,71 @@ public static function loadByName(string $entity_type_id, string $bundle): ?RdfE return static::load("$entity_type_id.$bundle"); } + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + parent::calculateDependencies(); + + /** @var \Drupal\rdf_entity\RdfEntityGraphInterface $graph */ + foreach (RdfEntityGraph::loadMultiple(array_keys($this->get('graph'))) as $graph) { + // Add dependency to graph. + $this->addDependency($graph->getConfigDependencyKey(), $graph->getConfigDependencyName()); + } + + // Add dependency to the paired bundle entity. + if ($entity_type = $this->getTargetEntityType()) { + if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { + if ($bundle_storage = $this->entityTypeManager()->getStorage($bundle_entity_type_id)) { + if ($bundle_entity = $bundle_storage->load($this->getTargetBundle())) { + $this->addDependency($bundle_entity->getConfigDependencyKey(), $bundle_entity->getConfigDependencyName()); + } + } + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + + /** @var \Drupal\rdf_entity\RdfEntityGraphInterface $graph */ + foreach ($dependencies['config'] as $graph) { + if ($graph->getEntityTypeId() === 'rdf_entity_graph') { + // Normally we shouldn't be notified about 'default' graph deletion + // because this could never occur. However, we take this additional + // precaution to cover any accidental removal. + if ($graph->id() !== RdfEntityGraphInterface::DEFAULT) { + // Remove the reference to the deleted graph and flag this mapping + // entity to be re-saved. + unset($this->graph[$graph->id()]); + $changed = TRUE; + } + } + // Don't react on paired bundle entity deletion (AKA remove this entity). + } + + return $changed; + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + \Drupal::service('sparql.graph_handler')->clearCache(); + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + \Drupal::service('sparql.graph_handler')->clearCache(); + } + } diff --git a/src/Entity/RdfEntitySparqlStorage.php b/src/Entity/RdfEntitySparqlStorage.php index 6e1361f7..8cba91e6 100644 --- a/src/Entity/RdfEntitySparqlStorage.php +++ b/src/Entity/RdfEntitySparqlStorage.php @@ -1,5 +1,7 @@ bundlePredicate; } /** - * Returns the graph handler object. + * {@inheritdoc} */ - public function getGraphHandler() { + public function getGraphHandler(): RdfGraphHandlerInterface { return $this->graphHandler; } /** - * Get the defined graph types for this entity type. - * - * This is here for convenience. - * - * @see \Drupal\rdf_entity\RdfGraphHandler::getGraphDefinitions - * - * @return array - * A structured array of graph definitions containing a title and a - * description. The array keys are the machine names of the graphs. + * {@inheritdoc} */ - public function getGraphDefinitions() { + public function getGraphDefinitions(): array { return $this->getGraphHandler()->getGraphDefinitions($this->entityTypeId); } - /** - * Set the graph type to use when interacting with entities. - * - * @param string $entity_id - * The entity id associated with the requested graphs. - * @param array $graph_types - * An array of graph machine names. - * - * @see \Drupal\rdf_entity\RdfGraphHandler::setRequestGraphs - */ - public function setRequestGraphs($entity_id, array $graph_types) { - $this->getGraphHandler()->setRequestGraphs($entity_id, $this->entityTypeId, $graph_types); - } - - /** - * Set the graph type to use for multiple ids. - * - * @param array $data - * An associative array with Ids for indexes and graph types in an array - * for values. - * - * @see \Drupal\rdf_entity\RdfGraphHandler::setRequestGraphs - */ - public function setRequestGraphsMultiple(array $data) { - foreach ($data as $entity_id => $graph_types) { - $this->getGraphHandler()->setRequestGraphs($entity_id, $this->entityTypeId, $graph_types); - } - } - - /** - * Returns the active graphs. - * - * @param string $entity_id - * The entity id associated with the requested graphs. - * - * @return array - * An array of graph ids related to the passed entity id. - * - * @see \Drupal\rdf_entity\RdfGraphHandler::getRequestGraphs - */ - public function getRequestGraphs($entity_id) { - return $this->getGraphHandler()->getRequestGraphs($entity_id); - } - - /** - * Get the (active) graph URI for a given bundle. - */ - public function getBundleGraphUri($bundle, $graph_type) { - return $this->getGraphHandler()->getBundleGraphUri($this->entityType->getBundleEntityType(), $bundle, $graph_type); - } - - /** - * Set the save graph. - * - * @param string $graph - * The graph to use. - * - * @deprecated - * This will be replaced with an event listener before the 1.0 release. - * - * @see https://www.drupal.org/node/2901490 - */ - public function setSaveGraph($graph) { - trigger_error(__METHOD__ . ' will be removed before the 1.0 release.', E_USER_DEPRECATED); - $this->getGraphHandler()->setTargetGraph($graph); - } - - /** - * Get the graph URIs for each bundle. - * - * @param array $graph_types - * Optionally filter the retrieved graphs. If empty, all available graphs - * will be loaded. - * - * @return array - * An array with the graph uris as keys and the corresponding bundles as - * values. - * - * @see \Drupal\rdf_entity\GraphHandler::getEntityTypeGraphUris - */ - public function getEntityTypeGraphUris(array $graph_types = NULL) { - return $this->getGraphHandler()->getEntityTypeGraphUris($this->entityType->getBundleEntityType(), $graph_types); - } - /** * {@inheritdoc} */ - public function doLoadMultiple(array $ids = NULL) { + public function doLoadMultiple(array $ids = NULL, array $graph_ids = []) { // Attempt to load entities from the persistent cache. This will remove IDs // that were loaded from $ids. - $entities_from_cache = $this->getFromPersistentCache($ids); + $entities_from_cache = $this->getFromPersistentCache($ids, $graph_ids); // Load any remaining entities from the database. - $entities_from_storage = $this->getFromStorage($ids); + $entities_from_storage = $this->getFromStorage($ids, $graph_ids); return $entities_from_cache + $entities_from_storage; } @@ -288,11 +192,18 @@ public function doLoadMultiple(array $ids = NULL) { * @param array|null $ids * If not empty, return entities that match these IDs. Return all entities * when NULL. + * @param array $graph_ids + * A list of graph IDs. * * @return \Drupal\Core\Entity\ContentEntityInterface[] * Array of entities from the storage. + * + * @throws \Drupal\rdf_entity\Exception\SparqlQueryException + * If the SPARQL query fails. + * @throws \Exception + * The query fails with no specific reason. */ - protected function getFromStorage(array $ids = NULL) { + protected function getFromStorage(array $ids = NULL, array $graph_ids = []): array { if (empty($ids)) { return []; } @@ -303,7 +214,7 @@ protected function getFromStorage(array $ids = NULL) { foreach ($operation_ids as $k => $v) { unset($remaining_ids[$k]); } - $entities_values = $this->loadFromStorage($operation_ids); + $entities_values = $this->loadFromStorage($operation_ids, $graph_ids); if ($entities_values) { foreach ($entities_values as $id => $entity_values) { $bundle = $this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : FALSE; @@ -327,9 +238,23 @@ protected function getFromStorage(array $ids = NULL) { } /** - * Retrieve the entity data from the Sparql endpoint. + * Retrieves the entity data from the SPARQL endpoint. + * + * @param string[] $ids + * A list of entity IDs. + * @param string[]|null $graph_ids + * An ordered list of candidate graph IDs. + * + * @return array|null + * The entity values indexed by the field mapping ID or NULL in there are no + * results. + * + * @throws \Drupal\rdf_entity\Exception\SparqlQueryException + * If the SPARQL query fails. + * @throws \Exception + * The query fails with no specific reason. */ - protected function loadFromStorage($ids) { + protected function loadFromStorage(array $ids, array $graph_ids): ?array { if (empty($ids)) { return []; } @@ -337,7 +262,7 @@ protected function loadFromStorage($ids) { // @todo: We should filter per entity per graph and not load the whole // database only to filter later on. $ids_string = SparqlArg::serializeUris($ids, ' '); - $graphs = $this->getGraphHandler()->getEntityTypeGraphUrisList($this->getEntityType()->getBundleEntityType()); + $graphs = $this->getGraphHandler()->getEntityTypeGraphUrisFlatList($this->getEntityTypeId()); $named_graph = ''; foreach ($graphs as $graph) { $named_graph .= 'FROM NAMED ' . SparqlArg::uri($graph) . "\n"; @@ -358,7 +283,7 @@ protected function loadFromStorage($ids) { QUERY; $entity_values = $this->sparql->query($query); - return $this->processGraphResults($entity_values); + return $this->processGraphResults($entity_values, $graph_ids); } /** @@ -366,25 +291,27 @@ protected function loadFromStorage($ids) { * * @todo Reduce the cyclomatic complexity of this function. * - * When an entity is loaded, the values might derive from multiple graph. - * This function will process the results and attempt to load a published - * version of the entity. - * If there is no published version available, then it will fallback to the - * rest of the graphs. + * When an entity is loaded, the values might derive from multiple graph. This + * function will process the results and attempt to load a published version + * of the entity. If there is no published version available, then it will + * fallback to the rest of the graphs. * * If the graph parameter can be used to restrict the available graphs to load * from. * * @param \EasyRdf\Sparql\Result|\EasyRdf\Graph $results * A set of query results indexed per graph and entity id. + * @param string[] $graph_ids + * Graph IDs. * - * @return array - * The entity values indexed by the field mapping id. + * @return array|null + * The entity values indexed by the field mapping ID or NULL in there are no + * results. * * @throws \Exception * Thrown when the entity graph is empty. */ - protected function processGraphResults($results) { + protected function processGraphResults($results, array $graph_ids): ?array { $values_per_entity = $this->deserializeGraphResults($results); if (empty($values_per_entity)) { return NULL; @@ -394,26 +321,29 @@ protected function processGraphResults($results) { $inbound_map = $this->fieldHandler->getInboundMap($this->entityTypeId); $return = []; foreach ($values_per_entity as $entity_id => $values_per_graph) { - $request_graphs = $this->getGraphHandler()->getRequestGraphs($entity_id); - $entity_graph_uris = $this->getGraphHandler()->getEntityTypeGraphUris($this->getEntityType()->getBundleEntityType()); - foreach ($request_graphs as $priority_graph) { + $graph_uris = $this->getGraphHandler()->getEntityTypeGraphUris($this->getEntityTypeId()); + foreach ($graph_ids as $priority_graph_id) { foreach ($values_per_graph as $graph_uri => $entity_values) { - if (isset($return[$entity_id]) || array_search($graph_uri, array_column($entity_graph_uris, $priority_graph)) === FALSE) { + // If the entity has been processed or the backend didn't returned + // anything for this graph, jump to the next graph retrieved from the + // SPARQL backend. + if (isset($return[$entity_id]) || array_search($graph_uri, array_column($graph_uris, $priority_graph_id)) === FALSE) { continue; } - /** @var \Drupal\rdf_entity\Entity\RdfEntityType $bundle */ $bundle = $this->getActiveBundle($entity_values); if (!$bundle) { continue; } - // Check if the graph checked is in the request graphs. - // If there are multiple graphs set, probably the default is requested - // with the rest as fallback or it is a neutral call. - // If the default is requested, it is going to be first in line so in - // any case, use the first one. - $graph_id = $this->getGraphHandler()->getBundleGraphId($this->entityType->getBundleEntityType(), $bundle, $graph_uri); + // Check if the graph checked is in the request graphs. If there are + // multiple graphs set, probably the default is requested with the + // rest as fallback or it is a neutral call. If the default is + // requested, it is going to be first in line so in any case, use the + // first one. + if (!$graph_id = $this->getGraphHandler()->getBundleGraphId($this->getEntityTypeId(), $bundle, $graph_uri)) { + continue; + } // Map bundle and entity id. $return[$entity_id][$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] = $bundle; @@ -449,7 +379,7 @@ protected function processGraphResults($results) { } /** - * Desirializes a list of graph results to an array. + * Deserializes a list of graph results to an array. * * The results array is an array of loaded entity values from different * graphs. @@ -470,7 +400,7 @@ protected function processGraphResults($results) { * @return array * The entity values indexed by the field mapping id. */ - protected function deserializeGraphResults(Result $results) { + protected function deserializeGraphResults(Result $results): array { $values_per_entity = []; foreach ($results as $result) { $entity_id = (string) $result->entity_id; @@ -490,9 +420,18 @@ protected function deserializeGraphResults(Result $results) { } /** - * Derive the bundle from the rdf:type. + * Derives the bundle from the rdf:type. + * + * @param array $entity_values + * Entity in a raw formatted array. + * + * @return string + * The bundle ID string. + * + * @throws \Exception + * Thrown when the bundle is not found. */ - protected function getActiveBundle($entity_values) { + protected function getActiveBundle(array $entity_values): ?string { $bundle_predicates = $this->bundlePredicate; $bundles = []; foreach ($bundle_predicates as $bundle_predicate) { @@ -502,7 +441,7 @@ protected function getActiveBundle($entity_values) { } } if (empty($bundles)) { - return; + return NULL; } // Since it is possible to map more than one bundles to the same uri, allow @@ -517,16 +456,54 @@ protected function getActiveBundle($entity_values) { /** * {@inheritdoc} */ - public function load($id) { - $entities = $this->loadMultiple([$id]); + public function load($id, array $graph_ids = NULL): ?ContentEntityInterface { + $entities = $this->loadMultiple([$id], $graph_ids); return array_shift($entities); } /** * {@inheritdoc} */ - public function loadMultiple(array $ids = NULL) { - $entities = parent::loadMultiple($ids); + public function loadMultiple(array $ids = NULL, array $graph_ids = NULL): array { + $entity_type_graph_ids = $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); + $graph_ids = $graph_ids ? array_values(array_intersect($graph_ids, $entity_type_graph_ids)) : $entity_type_graph_ids; + + // If there aro no valid graph candidates, there are no entities. + if (!$graph_ids) { + return []; + } + + // We copy this part from parent::loadMultiple(), otherwise we cannot pass + // the $graph_ids to self::getFromStaticCache() and self::doLoadMultiple(). + // START parent::loadMultiple() fork. + $entities = []; + $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; + if ($this->entityType->isStaticallyCacheable() && $ids) { + $entities += $this->getFromStaticCache($ids, $graph_ids); + if ($passed_ids) { + $ids = array_keys(array_diff_key($passed_ids, $entities)); + } + } + if ($ids === NULL || $ids) { + $queried_entities = $this->doLoadMultiple($ids, $graph_ids); + } + if (!empty($queried_entities)) { + $this->postLoad($queried_entities); + $entities += $queried_entities; + } + if ($this->entityType->isStaticallyCacheable()) { + if (!empty($queried_entities)) { + $this->setStaticCache($queried_entities); + } + } + if ($passed_ids) { + $passed_ids = array_intersect_key($passed_ids, $entities); + foreach ($entities as $entity) { + $passed_ids[$entity->id()] = $entity; + } + $entities = $passed_ids; + } + // END parent::loadMultiple() fork. if (empty($entities)) { return []; } @@ -536,9 +513,87 @@ public function loadMultiple(array $ids = NULL) { // the backend schema has no UUID, ID is reused as UUID. $rdf_entity->set($uuid_key, $rdf_entity->id()); }); + return $entities; } + /** + * {@inheritdoc} + */ + protected function doPreSave(EntityInterface $entity) { + // The code bellow is forked from EntityStorageBase::doPreSave() and + // ContentEntityStorageBase::doPreSave(). We are not using the original + // methods in order to be able to pass an additional list of graphs + // parameter to ::loadUnchanged() method. + // START forking from ContentEntityStorageBase::doPreSave(). + /** @var \Drupal\Core\Entity\ContentEntityBase $entity */ + $entity->updateOriginalValues(); + if ($entity->getEntityType()->isRevisionable() && !$entity->isNew() && empty($entity->getLoadedRevisionId())) { + $entity->updateLoadedRevisionId(); + } + + // START forking from EntityStorageBase::doPreSave(). + $id = $entity->id(); + if ($entity->getOriginalId() !== NULL) { + $id = $entity->getOriginalId(); + } + $id_exists = $this->has($id, $entity); + if ($id_exists && $entity->isNew()) { + throw new EntityStorageException("'{$this->entityTypeId}' entity with ID '$id' already exists."); + } + if ($id_exists && !isset($entity->original)) { + // In the case when the entity graph has been changed before saving, we + // need the original graph, so that we load the original/unchanged entity + // from the backend. This property was set in during entity load, in + // rdf_entity_entity_storage_load(). We can rely on this property also + // when the entity us saved via UI, as this value persists in entity over + // an entity form submit, because the entity is stored in the form state. + // @see rdf_entity_entity_storage_load() + $entity->original = $this->loadUnchanged($id, [$entity->rdfEntityOriginalGraph]); + } + $entity->preSave($this); + $this->invokeHook('presave', $entity); + // END forking from EntityStorageBase::doPreSave(). + if (!$entity->isNew()) { + if (empty($entity->original) || $entity->id() != $entity->original->id()) { + throw new EntityStorageException("Update existing '{$this->entityTypeId}' entity while changing the ID is not supported."); + } + if (!$entity->isNewRevision() && $entity->getRevisionId() != $entity->getLoadedRevisionId()) { + throw new EntityStorageException("Update existing '{$this->entityTypeId}' entity revision while changing the revision ID is not supported."); + } + } + // END forking from ContentEntityStorageBase::doPreSave(). + // Finally reset the entity original graph property so that that its updated + // value it's available for the rest of this request. + $entity->rdfEntityOriginalGraph = $entity->graph->value; + + return $id; + } + + /** + * {@inheritdoc} + */ + public function loadUnchanged($id, array $graph_ids = NULL): ?ContentEntityInterface { + // Code forked from parent::loadUnchanged() and adapted to accept graph + // candidates. + $graph_ids = $graph_ids ?: $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); + + $ids = [$id]; + parent::resetCache($ids); + $entities = $this->getFromPersistentCache($ids, $graph_ids); + if (!$entities) { + $entities[$id] = $this->load($id, $graph_ids); + } + else { + $this->postLoad($entities); + if ($this->entityType->isStaticallyCacheable()) { + $this->setStaticCache($entities); + } + } + + return $entities[$id]; + } + /** * {@inheritdoc} */ @@ -557,31 +612,19 @@ public function deleteRevision($revision_id) { /** * {@inheritdoc} */ - public function deleteFromGraph($entity_id, $graph) { - $this->getGraphHandler()->setRequestGraphs($entity_id, $this->entityTypeId, [$graph]); - $entity = $this->load($entity_id); + public function deleteFromGraph(string $entity_id, string $graph_id): void { + $entity = $this->load($entity_id, [$graph_id]); if (!empty($entity)) { $this->doDelete([$entity_id => $entity]); $this->resetCache([$entity_id]); } - - // Reset the request graphs for the deleted entities. - $this->getGraphHandler()->resetRequestGraphs([$entity_id]); } /** - * Checks if a RDF entity has a specific graph. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object. - * @param string $graph - * The graph to be checked ('draft', etc). - * - * @return bool - * TRUE if this entity has the specified graph. + * {@inheritdoc} */ - public function hasGraph(EntityInterface $entity, $graph) { - $graph_uri = $this->graphHandler->getBundleGraphUri($entity->getEntityType()->getBundleEntityType(), $entity->bundle(), $graph); + public function hasGraph(EntityInterface $entity, string $graph_id): bool { + $graph_uri = $this->getGraphHandler()->getBundleGraphUri($entity->getEntityTypeId(), $entity->bundle(), $graph_id); return $this->idExists($entity->id(), $graph_uri); } @@ -624,7 +667,7 @@ public function delete(array $entities) { /** @var \Drupal\Core\Entity\EntityInterface $keyed_entity */ foreach ($keyed_entities as $keyed_entity) { // Determine all possible graphs for the entity. - $graphs = $this->graphHandler->getEntityTypeGraphUris($this->entityType->getBundleEntityType()); + $graphs = $this->getGraphHandler()->getEntityTypeGraphUris($this->getEntityTypeId()); foreach ($graphs[$keyed_entity->bundle()] as $graph_name => $graph_uri) { $entities_by_graph[$graph_uri][$keyed_entity->id()] = $keyed_entity; } @@ -650,27 +693,32 @@ protected function doDelete($entities) { /** @var string $id */ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ foreach ($entities as $id => $entity) { - $graph_uri = $this->getGraphHandler()->getGraphUriFromEntity($entity); + $graph_uri = $this->getGraphHandler()->getBundleGraphUri($entity->getEntityTypeId(), $entity->bundle(), $entity->graph->value); $entities_by_graph[$graph_uri][$id] = $entity; } - foreach ($entities_by_graph as $graph => $entities_to_delete) { - $this->doDeleteFromGraph($entities, $graph); + foreach ($entities_by_graph as $graph_uri => $entities_to_delete) { + $this->doDeleteFromGraph($entities, $graph_uri); } } /** - * Construct and execute the delete query. + * Constructs and execute the delete query. * * @param array $entities * An array of entity objects to delete. - * @param string $graph - * The graph uri to delete from. + * @param string $graph_uri + * The graph URI to delete from. + * + * @throws \Drupal\rdf_entity\Exception\SparqlQueryException + * If the SPARQL query fails. + * @throws \Exception + * The query fails with no specific reason. */ - protected function doDeleteFromGraph(array $entities, $graph) { + protected function doDeleteFromGraph(array $entities, string $graph_uri): void { $entity_list = SparqlArg::serializeUris(array_keys($entities)); $query = << +DELETE FROM <$graph_uri> { ?entity ?field ?value } @@ -698,21 +746,19 @@ protected function getQueryServiceName() { public function getQuery($conjunction = 'AND') { // Access the service directly rather than entity.query factory so the // storage's current entity type is used. + /** @var \Drupal\rdf_entity\Entity\Query\Sparql\Query $query */ $query = \Drupal::service($this->getQueryServiceName())->get($this->entityType, $conjunction, $this->graphHandler, $this->fieldHandler); /* - * Hold on tight this ain't easy... - * @todo: Get - * - * When the storage class supports the notion of a 'published state' - * by implementing the published interface, we then have to determine - * if drafting has been enabled for this entity type (rdf_draft module). - * If so, the 'draft' graph will hold the unpublished versions, 'default' - * graph contains the published entities. + * When the storage class supports the notion of a 'published state' by + * implementing the published interface, we then have to determine if + * drafting has been enabled for this entity type (rdf_draft module). If so, + * the 'draft' graph will hold the unpublished versions, 'default' graph + * contains the published entities. */ - if (in_array('Drupal\Core\Entity\EntityPublishedInterface', class_implements($this->entityClass))) { + if (in_array(EntityPublishedInterface::class, class_implements($this->entityClass))) { if ($this->moduleHandler->moduleExists('rdf_draft')) { - $query->setGraphType(['draft', 'default']); + $query->setGraphType($this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId())); } } @@ -773,10 +819,8 @@ protected function doSave($id, EntityInterface $entity) { throw new DuplicatedIdException("Attempting to create a new entity with the ID '$id' already taken."); } - // If the target graph is set, it has priority over the one the entity is - // loaded from. If no target graph is set, use the previous one. - $target_graph = $this->getGraphHandler()->getTargetGraphFromEntity($entity); - $graph_uri = $this->getBundleGraphUri($bundle, $target_graph); + $graph_id = !$entity->get('graph')->isEmpty() ? $entity->graph->value : $this->getGraphHandler()->getDefaultGraphId($this->getEntityTypeId()); + $graph_uri = $this->getGraphHandler()->getBundleGraphUri($entity->getEntityTypeId(), $entity->bundle(), $graph_id); $graph = self::getGraph($graph_uri); $lang_array = $this->toLangArray($entity); foreach ($lang_array as $field_name => $langcode_data) { @@ -797,8 +841,9 @@ protected function doSave($id, EntityInterface $entity) { } } - // Give implementations a chance to alter the graph before is saved. + // Give implementations a chance to alter the graph right before is saved. $this->alterGraph($graph, $entity); + if (!$entity->isNew()) { $this->deleteBeforeInsert($id, $graph_uri); } @@ -823,10 +868,10 @@ protected function doSave($id, EntityInterface $entity) { * * So, the process is: * - If the current language is the default one, add all fields to the - * x-default index. + * x-default index. * - If the current language is not the default language, then the default * - language will only provide the translatable fields as default and the - * non translatable will be filled by the current language. + * non-translatable will be filled by the current language. * - All the other languages, will only provide the translatable fields. * * Only t_literal fields should be translatable. @@ -837,7 +882,7 @@ protected function doSave($id, EntityInterface $entity) { * @return array * The array of values including the translations. */ - protected function toLangArray(ContentEntityInterface $entity) { + protected function toLangArray(ContentEntityInterface $entity): array { $values = []; $languages = array_keys(array_filter($entity->getTranslationLanguages(), function (LanguageInterface $language) { return !$language->isLocked(); @@ -908,8 +953,11 @@ protected function toLangArray(ContentEntityInterface $entity) { * * @return string|null * A language code or NULL, if the field has no language. + * + * @throws \Exception + * Thrown when a non existing field is requested. */ - protected function resolveFieldLangcode($entity_type_id, $field_name, $langcode = NULL) { + protected function resolveFieldLangcode($entity_type_id, $field_name, $langcode = NULL): ?string { $format = $this->fieldHandler->getFieldFormat($entity_type_id, $field_name); $non_languages = [ LanguageInterface::LANGCODE_NOT_SPECIFIED, @@ -930,29 +978,10 @@ protected function resolveFieldLangcode($entity_type_id, $field_name, $langcode return $langcode; } - /** - * Resolves the language based on entity and current site language. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * @param mixed $field_item - * The field item or the field item list for which to resolve the language. - * - * @return string|null - * A language code or NULL, if the field has no language. - */ - protected function resolveFieldItemLangcode(EntityInterface $entity, $field_item) { - if (!$langcode = $field_item->getLangcode()) { - return NULL; - } - - return $this->resolveFieldLangcode($entity->getEntityTypeId(), $field_item->getName(), $langcode); - } - /** * Alters the graph before saving the entity. * - * Implementation are able to change, delete or add items to the graph before + * Implementations are able to change, delete or add items to the graph before * this is saved to SPARQL backend. * * @param \EasyRdf\Graph $graph @@ -960,38 +989,27 @@ protected function resolveFieldItemLangcode(EntityInterface $entity, $field_item * @param \Drupal\Core\Entity\EntityInterface $entity * The entity being saved. */ - protected function alterGraph(Graph &$graph, EntityInterface $entity) {} - - /** - * Get the schema definition for a given field column. - * - * @param \Drupal\Core\Field\FieldItemInterface $item - * The field. - * @param string $column - * The column name. - * - * @return mixed - * The field column schema. - */ - protected function getColumnSchema(FieldItemInterface $item, $column) { - $schema = $item->getFieldDefinition()->getFieldStorageDefinition()->getSchema(); - return $schema['columns'][$column]; - } + protected function alterGraph(Graph &$graph, EntityInterface $entity): void {} /** * Insert a graph of triples. * * @param \EasyRdf\Graph $graph * The graph to insert. - * @param string $graphUri + * @param string $graph_uri * Graph to save to. * - * @return \EasyRdf\Graph|\EasyRdf\Sparql\Result + * @return \EasyRdf\Sparql\Result * Response. + * + * @throws \Drupal\rdf_entity\Exception\SparqlQueryException + * If the SPARQL query fails. + * @throws \Exception + * The query fails with no specific reason. */ - private function insert(Graph $graph, $graphUri) { - $graphUri = SparqlArg::uri($graphUri); - $query = "INSERT DATA INTO $graphUri {\n"; + protected function insert(Graph $graph, string $graph_uri): Result { + $graph_uri = SparqlArg::uri($graph_uri); + $query = "INSERT DATA INTO $graph_uri {\n"; $query .= $graph->serialise('ntriples') . "\n"; $query .= '}'; return $this->sparql->update($query); @@ -1029,7 +1047,7 @@ public function hasData() { * @todo: To be removed when columns will be supported. No need to manually * set this. */ - private function applyFieldDefaults($type, array &$values) { + protected function applyFieldDefaults($type, array &$values): void { if (empty($values)) { return; } @@ -1045,7 +1063,8 @@ private function applyFieldDefaults($type, array &$values) { // Strip timezone part in dates. // @todo Move in InboundOutboundValueSubscriber::massageInboundValue() case 'datetime': - $time_stamp = strtotime($value['value']); + $time_stamp = (int) $value['value']; + // $time_stamp = strtotime($value['value']);. $date = date('o-m-d', $time_stamp) . "T" . date('H:i:s', $time_stamp); $value['value'] = $date; break; @@ -1056,19 +1075,17 @@ private function applyFieldDefaults($type, array &$values) { /** * {@inheritdoc} - * - * If there are more than one graphs in the request, return the first - * available with an entity in it. */ - protected function getFromStaticCache(array $ids) { + protected function getFromStaticCache(array $ids, array $graph_ids = []) { $entities = []; foreach ($ids as $id) { - $request_graphs = $this->getRequestGraphs($id); - foreach ($request_graphs as $request_graph) { - if (isset($this->entities[$id][$request_graph])) { - if (!isset($entities[$id])) { - $entities[$id] = $this->entities[$id][$request_graph]; - } + // If there are more than one graphs in the request, return only the first + // one, if exists. If the first candidate doesn't exist in the static + // cache, we don't pickup the following because the first might be + // available later in the persistent cache or in the storage. + if (isset($this->entities[$id][$graph_ids[0]])) { + if (!isset($entities[$id])) { + $entities[$id] = $this->entities[$id][$graph_ids[0]]; } } } @@ -1079,12 +1096,9 @@ protected function getFromStaticCache(array $ids) { * {@inheritdoc} */ protected function setStaticCache(array $entities) { - foreach ($entities as $id => $entity) { - // The target graph should be empty since it's a load so the one from the - // entity should be loaded here. - $graph = $this->getGraphHandler()->getTargetGraphFromEntity($entity); - if ($this->entityType->isStaticallyCacheable()) { - $this->entities[$id][$graph] = $entity; + if ($this->entityType->isStaticallyCacheable()) { + foreach ($entities as $id => $entity) { + $this->entities[$id][$entity->graph->value] = $entity; } } } @@ -1092,7 +1106,7 @@ protected function setStaticCache(array $entities) { /** * {@inheritdoc} */ - protected function getFromPersistentCache(array &$ids = NULL) { + protected function getFromPersistentCache(array &$ids = NULL, array $graph_ids = []) { if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) { return []; } @@ -1100,9 +1114,8 @@ protected function getFromPersistentCache(array &$ids = NULL) { // Build the list of cache entries to retrieve. $cid_map = []; foreach ($ids as $id) { - $request_graphs = $this->getGraphHandler()->getRequestGraphs($id); - $graph = reset($request_graphs); - $cid_map[$id] = "{$this->buildCacheId($id)}:{$graph}"; + $graph_id = reset($graph_ids); + $cid_map[$id] = "{$this->buildCacheId($id)}:{$graph_id}"; } $cids = array_values($cid_map); if ($cache = $this->cacheBackend->getMultiple($cids)) { @@ -1131,20 +1144,18 @@ protected function setPersistentCache($entities) { 'entity_field_info', ]; foreach ($entities as $id => $entity) { - $graph = $this->getGraphHandler()->getTargetGraphFromEntity($entity); - $cid = "{$this->buildCacheId($id)}:{$graph}"; + $cid = "{$this->buildCacheId($id)}:{$entity->graph->value}"; $this->cacheBackend->set($cid, $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags); } } /** * {@inheritdoc} - * - * Since the notion of graphs exist in the sparql storage, the cache reset - * should remove entities from all graphs. */ public function resetCache(array $ids = NULL) { - $graphs = $this->getGraphHandler()->getEntityTypeEnabledGraphs(); + // Since the notion of graphs exist in the SPARQL storage, the cache reset + // should remove entities from all graphs. + $graphs = $this->getGraphHandler()->getEntityTypeGraphIds($this->entityTypeId); if ($ids) { $cids = []; foreach ($ids as $id) { @@ -1184,8 +1195,13 @@ protected function buildCacheId($id) { * The entity uri. * @param string $graph_uri * The graph uri. + * + * @throws \Drupal\rdf_entity\Exception\SparqlQueryException + * If the SPARQL query fails. + * @throws \Exception + * The query fails with no specific reason. */ - protected function deleteBeforeInsert($id, $graph_uri) { + protected function deleteBeforeInsert(string $id, string $graph_uri): void { $property_list = $this->fieldHandler->getPropertyListToArray($this->getEntityTypeId()); $serialized = SparqlArg::serializeUris($property_list); $id = SparqlArg::uri($id); @@ -1207,18 +1223,9 @@ protected function deleteBeforeInsert($id, $graph_uri) { } /** - * Checks if a specific entity ID already exists in the backend. - * - * @param string $id - * The ID to be checked. - * @param string $graph - * The bundle resource uri. If passed, the id will be checked only against - * this graph. - * - * @return bool - * TRUE if this entity ID already exists, FALSE otherwise. + * {@inheritdoc} */ - public function idExists($id, $graph = NULL) { + public function idExists(string $id, string $graph = NULL): bool { $id = SparqlArg::uri($id); $predicates = SparqlArg::serializeUris($this->bundlePredicate, ' '); if ($graph) { diff --git a/src/Form/RdfEntityGraphForm.php b/src/Form/RdfEntityGraphForm.php new file mode 100644 index 00000000..26c5d78a --- /dev/null +++ b/src/Form/RdfEntityGraphForm.php @@ -0,0 +1,103 @@ +getEntity(); + + $form['name'] = [ + '#title' => t('Name'), + '#type' => 'textfield', + '#default_value' => $graph->label(), + '#description' => $this->t('The human-readable name of this graph.'), + '#required' => TRUE, + '#size' => 30, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $graph->id(), + '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH, + '#disabled' => !$graph->isNew(), + '#machine_name' => [ + 'exists' => [RdfEntityGraph::class, 'load'], + 'source' => ['name'], + ], + '#description' => $this->t('A unique machine-readable name for this graph. It must only contain lowercase letters, numbers, and underscores.'), + ]; + + $form['weight'] = [ + '#type' => 'value', + '#value' => $graph->getWeight(), + ]; + + $form['description'] = [ + '#title' => $this->t('Description'), + '#type' => 'textarea', + '#default_value' => $graph->getDescription(), + '#description' => $this->t('The description of this graph.'), + ]; + + $entity_types = []; + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + if ($storage = $this->entityTypeManager->getStorage($entity_type_id)) { + if ($storage instanceof RdfEntitySparqlStorage) { + $entity_types[$entity_type_id] = $entity_type->getLabel(); + } + } + } + + $form['entity_types'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Entity types using this graph'), + '#description' => $this->t('If none is selected, this graph is made available to all entity types that are using SPARQL storage.'), + '#options' => $entity_types, + '#disabled' => $graph->id() === RdfEntityGraphInterface::DEFAULT, + '#access' => (bool) $entity_types, + '#default_value' => (array) $graph->getEntityTypeIds(), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function buildEntity(array $form, FormStateInterface $form_state) { + // Normalize the entity types array. + $entity_types = $form_state->getValue('entity_types'); + $form_state->setValue('entity_types', array_values(array_filter($entity_types))); + return parent::buildEntity($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $form_state->setRedirect('entity.rdf_entity_graph.collection'); + drupal_set_message($this->t("Graph %name (%id) has been saved.", [ + '%name' => $this->getEntity()->label(), + '%id' => $this->getEntity()->id(), + ])); + return parent::save($form, $form_state); + } + +} diff --git a/src/ParamConverter/RdfEntityConverter.php b/src/ParamConverter/RdfEntityConverter.php index 2fac4432..4800cea3 100644 --- a/src/ParamConverter/RdfEntityConverter.php +++ b/src/ParamConverter/RdfEntityConverter.php @@ -43,30 +43,25 @@ public function applies($definition, $name, Route $route) { * {@inheritdoc} */ public function convert($value, $definition, $name, array $defaults) { - // Here the escaped uri is transformed into a valid uri. - // @see \Drupal\rdf_entity\Entity\Rdf::urlRouteParameters + // Here the escaped uri is transformed into a valid URI. if (!SparqlArg::isValidResource($value)) { $value = UriEncoder::decodeUrl($value); } $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults); - if ($storage = $this->entityManager->getStorage($entity_type_id)) { - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ - $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); - $event = new ActiveGraphEvent($value, $definition, $name, $defaults); - // Determine the graph by emitting an event. - $graph = $dispatcher->dispatch(RdfEntityEvents::GRAPH_ENTITY_CONVERT, $event); - if ($graph_type = $graph->getGraph()) { - $storage->setRequestGraphs($value, [$graph_type]); - } - - $entity = $storage->load($value); - // If the entity type is translatable, ensure we return the proper - // translation object for the current context. - if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) { - $entity = $this->entityManager->getTranslationFromContext($entity, NULL, ['operation' => 'entity_upcast']); - } - return $entity; + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ + $storage = $this->entityManager->getStorage($entity_type_id); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $event = new ActiveGraphEvent($name, $value, $entity_type_id, $definition, $defaults); + // Determine the graph by dispatching an event. + $event = $dispatcher->dispatch(RdfEntityEvents::GRAPH_ENTITY_CONVERT, $event); + $entity = $storage->load($value, $event->getGraphs()); + // If the entity type is translatable, ensure we return the proper + // translation object for the current context. + if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) { + $entity = $this->entityManager->getTranslationFromContext($entity, NULL, ['operation' => 'entity_upcast']); } + return $entity; } } diff --git a/src/Plugin/pathauto/AliasType/RdfEntityAliasType.php b/src/Plugin/pathauto/AliasType/RdfEntityAliasType.php index dbb6d45e..887fc7e7 100644 --- a/src/Plugin/pathauto/AliasType/RdfEntityAliasType.php +++ b/src/Plugin/pathauto/AliasType/RdfEntityAliasType.php @@ -93,7 +93,7 @@ public function batchUpdate($action, &$context) { $updates = $this->bulkUpdate($ids); $context['sandbox']['count'] += count($ids); $context['results']['updates'] += $updates; - $context['message'] = $this->t('Updated alias for Rdf entity @id.', array('@id' => end($ids))); + $context['message'] = $this->t('Updated alias for Rdf entity @id.', ['@id' => end($ids)]); if ($context['sandbox']['count'] != $context['sandbox']['total']) { $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; @@ -130,7 +130,7 @@ protected function getRdfEntityQuery() : Query { $storage = $this->entityTypeManager->getStorage('rdf_entity'); /** @var \Drupal\rdf_entity\Entity\Query\Sparql\Query $query */ $query = $storage->getQuery(); - $query->setGraphType($storage->getGraphHandler()->getEntityTypeEnabledGraphs()); + $query->setGraphType($storage->getGraphHandler()->getEntityTypeGraphIds('rdf_entity')); return $query; } diff --git a/src/RdfEntityGraphAccessControlHandler.php b/src/RdfEntityGraphAccessControlHandler.php new file mode 100644 index 00000000..645ffc44 --- /dev/null +++ b/src/RdfEntityGraphAccessControlHandler.php @@ -0,0 +1,35 @@ +id() === RdfEntityGraphInterface::DEFAULT) { + return AccessResult::forbidden()->addCacheableDependency($rdf_entity_graph); + } + return parent::checkAccess($rdf_entity_graph, $operation, $account)->addCacheableDependency($rdf_entity_graph); + + default: + return parent::checkAccess($rdf_entity_graph, $operation, $account); + + } + } + +} diff --git a/src/RdfEntityGraphInterface.php b/src/RdfEntityGraphInterface.php index 7bcdb744..5b22031a 100644 --- a/src/RdfEntityGraphInterface.php +++ b/src/RdfEntityGraphInterface.php @@ -18,6 +18,28 @@ interface RdfEntityGraphInterface extends ConfigEntityInterface { */ const DEFAULT = 'default'; + /** + * Sets the RDF entity graph weight. + * + * The weight value is used to define the order in the list of graphs. + * + * @param int $weight + * The weight as integer. + * + * @return $this + */ + public function setWeight(int $weight): self; + + /** + * Gets the weight of this RDF entity graph. + * + * The weight value is used to define the order in the list of graphs. + * + * @return int + * The weight of this RDF entity graph. + */ + public function getWeight(): int; + /** * Set the graph name. * @@ -42,8 +64,9 @@ public function setDescription(string $description): self; * Gets the graph description. * * @return string + * The graph description. */ - public function getDescription(): string; + public function getDescription(): ?string; /** * Gets the entity types supporting this graph. diff --git a/src/RdfEntityGraphListBuilder.php b/src/RdfEntityGraphListBuilder.php new file mode 100644 index 00000000..ad8e0005 --- /dev/null +++ b/src/RdfEntityGraphListBuilder.php @@ -0,0 +1,85 @@ + $this->t('Name'), + 'description' => [ + 'data' => $this->t('Description'), + 'class' => [RESPONSIVE_PRIORITY_MEDIUM], + ], + 'entity_types' => $this->t('Entity types'), + ] + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $rdf_entity_graph) { + /** @var \Drupal\rdf_entity\RdfEntityGraphInterface $rdf_entity_graph */ + $row['label'] = $rdf_entity_graph->label(); + $row['description'] = ['#markup' => $rdf_entity_graph->getDescription()]; + + if ($entity_types = $rdf_entity_graph->getEntityTypeIds()) { + $labels = implode(', ', array_intersect_key(\Drupal::service('entity_type.repository') + ->getEntityTypeLabels(), array_flip($entity_types))); + } + else { + $labels = $this->t('All RDF entity types'); + } + $row['entity_types'] = ['#markup' => $labels]; + + return $row + parent::buildRow($rdf_entity_graph); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOperations(EntityInterface $entity) { + $operations = parent::getDefaultOperations($entity); + + /** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */ + $access_manager = \Drupal::service('access_manager'); + foreach (['enable', 'disable'] as $operation) { + if (isset($operations[$operation])) { + $route_name = "entity.{$this->entityTypeId}.$operation"; + $parameters = [$this->entityTypeId => $entity->id()]; + if (!$access_manager->checkNamedRoute($route_name, $parameters)) { + unset($operations[$operation]); + } + } + } + + return $operations; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form[$this->entitiesKey]['#caption'] = $this->t('Reorder graphs to establish the graphs priority.'); + return $form; + } + +} diff --git a/src/RdfEntityMappingInterface.php b/src/RdfEntityMappingInterface.php index b09f0499..07927762 100644 --- a/src/RdfEntityMappingInterface.php +++ b/src/RdfEntityMappingInterface.php @@ -16,6 +16,7 @@ interface RdfEntityMappingInterface extends ConfigEntityInterface { * Gets the referred entity type ID. * * @return string + * The referred entity type ID. */ public function getTargetEntityTypeId(): string; @@ -31,6 +32,7 @@ public function getTargetEntityType(): ?ContentEntityTypeInterface; * Gets the referred bundle ID. * * @return string + * The referred bundle ID. */ public function getTargetBundle(): string; @@ -113,7 +115,7 @@ public function getGraphs(): array; * Gets the URI value given a specific graph ID. * * @param string $graph - * The graph ID. + * The graph ID. * * @return string|null * The graph URI or NULL if doesn't exist. @@ -137,8 +139,9 @@ public function unsetGraphs(array $graphs): self; * already exists will be overridden with the new passed value. * * @param array $mappings - * A structured associative array having the next structure: - * @code + * A structured associative array having the next structure:. + * + * @code * [ * 'field1' => [ * 'column1' => [ @@ -154,7 +157,7 @@ public function unsetGraphs(array $graphs): self; * ... * ], * ] - * @endcode + * @endcode * - The first level are field names, * - The second level are field columns, such as 'value', 'target_id'. * - The values are arrays with two keys: 'predicate', 'format'. @@ -171,7 +174,7 @@ public function addMappings(array $mappings): self; * * @param array $mappings * A structured associative array with the same structure as the parameter - * from ::addMappings(). + * from ::addMappings(). * * @return $this * @@ -186,6 +189,7 @@ public function setMappings(array $mappings): self; * Returns all base fields mappings. * * @return array + * All base fields mappings. */ public function getMappings(): array; @@ -218,7 +222,7 @@ public function unsetMappings(array $field_names): self; * @param string $entity_type_id * The entity type ID. * @param string $bundle - * The bundle. + * The bundle. * * @return static|null * The rdf_entity_mapping entity of NULL on failure. diff --git a/src/RdfEntitySparqlStorageInterface.php b/src/RdfEntitySparqlStorageInterface.php new file mode 100644 index 00000000..bdc95c64 --- /dev/null +++ b/src/RdfEntitySparqlStorageInterface.php @@ -0,0 +1,135 @@ + [ - * $entity_id => [ - * graph1, - * graph2 - * ], - * $entity_id2 => [ - * graph1, - * graph2, - * ], - * ] - * @endcode - */ - protected $requestGraphs; - - /** - * Holds the graphs that the entity is going to be saved in. - * - * @var string|null - * - * @deprecated - * This will be replaced with an event listener before the 1.0 release. - * - * @see https://www.drupal.org/node/2901490 - */ - protected $targetGraph = NULL; - /** * The RDF entity graph config entity storage. * @@ -101,409 +40,142 @@ class RdfGraphHandler { * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager service. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) { + public function __construct(EntityTypeManagerInterface $entity_type_manager) { $this->entityTypeManager = $entity_type_manager; - $this->moduleHandler = $module_handler; - - // Allow altering the default active graph. - $graph = $this->enabledGraphs; - $this->moduleHandler->alter('rdf_default_active_graph', $entity_type, $graph); - $this->enabledGraphs = $graph; - - // By default, all graphs are available. - $this->resetRequestGraphs(); } /** - * Reset the mapping of entity - graphs. - * - * @param array $entity_ids - * An array of entity ids that the request graphs are going to be reset - * for. If an empty array is passed, all graphs will be reset. + * {@inheritdoc} */ - public function resetRequestGraphs(array $entity_ids = []) { - if (empty($entity_ids)) { - $this->requestGraphs = ['default' => $this->enabledGraphs]; - } - else { - foreach ($entity_ids as $entity_id) { - unset($this->requestGraphs[SparqlArg::uri($entity_id)]); - } - } - } - - /** - * Get the defined graph types for this entity type. - * - * A default graph is provided here already because there has to exist at - * least one available graph for the entities to be saved in. - * - * @param string $entity_type_id - * The entity type machine name. - * - * @return array - * A structured array of graph definitions containing a title and a - * description. The array keys are the machine names of the graphs. - */ - public function getGraphDefinitions($entity_type_id) { - $query = $this->getRdfEntityGraphStorage()->getQuery(); - $ids = $query->condition($query->orConditionGroup() - // An empty value, such as NULL or [], means "all entity types". - ->notExists('entity_types') - ->condition('entity_types', []) - ->condition('entity_types.*', $entity_type_id) - )->condition('status', TRUE) - ->execute(); - $graphs = $this->getRdfEntityGraphStorage()->loadMultiple($ids); - - return array_map(function (RdfEntityGraphInterface $graph): array { - return [ - 'title' => $graph->label(), - 'description' => $graph->getDescription(), - ]; - }, $graphs); - } - - /** - * Returns the active graphs as an array. - * - * @return array - * An array of graph machine names. - */ - public function getEntityTypeEnabledGraphs() { - return $this->enabledGraphs; - } - - /** - * Returns the graph uri for the passed bundle of the passed entity type. - * - * @param string $entity_type_bundle_key - * The bundle entity id of an entity type e.g. 'node_type'. - * @param string $bundle - * The bundle machine name. - * @param string $graph_name - * The graph type. Defaults to 'default'. - * - * @return string - * The uri of the requested graph. - * - * @throws \Exception - * Thrown when the passed graph cannot be determined. - */ - public function getBundleGraphUri($entity_type_bundle_key, $bundle, $graph_name) { - return $this->getBundleGraphUriFromSettings($entity_type_bundle_key, $bundle, $graph_name); - } - - /** - * Returns the graph uris for bundles of the passed entity type. - * - * @param string $entity_type_bundle_key - * The bundle entity id of an entity type e.g. 'node_type'. - * @param array $graph_names - * The graph type. Defaults to 'default'. - * - * @return array - * An array of graphs uris mapped by bundle id and graph id. - * - * @throws \Exception - * Thrown when the passed graph cannot be determined. - */ - public function getEntityTypeGraphUris($entity_type_bundle_key, array $graph_names = []) { - if (empty($graph_names)) { - $graph_names = $this->getEntityTypeEnabledGraphs(); - } - - sort($graph_names); - $cache_key = implode(':', $graph_names); - if (!isset($this->entityTypeGraphUris[$entity_type_bundle_key][$cache_key])) { - $bundle_entities = $this->entityTypeManager->getStorage($entity_type_bundle_key)->loadMultiple(); - $graphs = []; - foreach ($bundle_entities as $bundle_entity) { - foreach ($graph_names as $graph_name) { - $graph = $this->getBundleGraphUriFromSettings($entity_type_bundle_key, $bundle_entity->id(), $graph_name); - $graphs[$bundle_entity->id()][$graph_name] = $graph; - } - } - - $this->entityTypeGraphUris[$entity_type_bundle_key][$cache_key] = $graphs; - } - - return $this->entityTypeGraphUris[$entity_type_bundle_key][$cache_key]; - } + public function getGraphDefinitions(string $entity_type_id): array { + if (!isset($this->cache['definition'][$entity_type_id])) { + $query = $this->getRdfEntityGraphStorage()->getQuery(); + $ids = $query->condition($query->orConditionGroup() + ->condition('entity_types.*', $entity_type_id) + // A NULL value means "all entity types". + ->notExists('entity_types') + )->condition('status', TRUE) + // A determined order is a key feature. + ->sort('weight', 'ASC') + ->execute(); - /** - * Returns a plain list of graphs related to the passed entity type. - * - * @param string $entity_type_bundle_key - * The entity type bundle key e.g. 'node_type'. - * @param array $graph_names - * Optionally filter the graphs to be returned. - * - * @todo: Need to pass only the entity type id here. - * - * @return array - * A plain list of graph uris. - */ - public function getEntityTypeGraphUrisList($entity_type_bundle_key, array $graph_names = []) { - if (empty($graph_names)) { - $graph_names = $this->getEntityTypeEnabledGraphs(); - } - $graph_list = []; - $entity_graphs = $this->getEntityTypeGraphUris($entity_type_bundle_key, $graph_names); - foreach ($entity_graphs as $bundle_id => $bundle_graphs) { - foreach ($graph_names as $graph_name) { - $graph_list[] = $entity_graphs[$bundle_id][$graph_name]; + if (!$ids) { + // Do not cache an empty set, it may occur because this runs before any + // configuration has been imported, so the entities are not yet in. + return []; } - } - return $graph_list; - } + $graphs = $this->getRdfEntityGraphStorage()->loadMultiple($ids); - /** - * Returns the request graphs stored in the service. - * - * @param string $entity_id - * The entity id associated with the requested graphs. - * - * @return array - * The request graphs. - */ - public function getRequestGraphs($entity_id) { - $entity_id = SparqlArg::uri($entity_id); - if (empty($entity_id)) { - return $this->requestGraphs['default']; + $this->cache['definition'][$entity_type_id] = array_map(function (RdfEntityGraphInterface $graph): array { + return [ + 'title' => $graph->label(), + 'description' => $graph->getDescription(), + ]; + }, $graphs); } - if (!isset($this->requestGraphs[$entity_id])) { - $this->requestGraphs[$entity_id] = $this->getEntityTypeEnabledGraphs(); - } - return $this->requestGraphs[$entity_id]; + return $this->cache['definition'][$entity_type_id]; } /** - * Set the graph type to use when interacting with entities. - * - * @param string $entity_id - * The entity id associated with the requested graphs. - * @param string $entity_type_id - * The entity type machine name. - * @param array $graph_names - * An array of graph machine names. - * - * @todo: This occurs in almost every method. Can we inject the entity type? - * - * @todo: Need to check whether a new instance is created when multiple types - * are being loaded e.g. when an entity with entity references are loaded. - * In this case, each entity might have a different graph definition from - * where it needs to be loaded. - * - * @throws \Exception - * Thrown if there is an invalid graph in the argument array or if the - * final array is empty as there must be at least one active graph. + * {@inheritdoc} */ - public function setRequestGraphs($entity_id, $entity_type_id, array $graph_names) { - $definitions = $this->getGraphDefinitions($entity_type_id); - $graphs_array = []; - foreach ($graph_names as $graph_name) { - if (!isset($definitions[$graph_name])) { - throw new \Exception('Unknown graph type ' . $graph_name); - } - $graphs_array[] = $graph_name; - } - - // @todo: Should we have the default one set if the result set is empty? - if (empty($graphs_array)) { - throw new \Exception("There must be at least one active graph."); + public function getEntityTypeGraphIds(string $entity_type_id, array $limit_to_graph_ids = NULL): array { + $graph_ids = array_keys($this->getGraphDefinitions($entity_type_id)); + if ($limit_to_graph_ids) { + $graph_ids = array_intersect($graph_ids, $limit_to_graph_ids); } - - // Remove duplicates as there might be occurrences after the loop above. - $this->requestGraphs[SparqlArg::uri($entity_id)] = array_unique($graphs_array); + return $graph_ids; } /** - * Returns the stored target graph. - * - * @return string - * The target graph to save to. - * - * @deprecated - * This will be replaced with an event listener before the 1.0 release. - * - * @see https://www.drupal.org/node/2901490 + * {@inheritdoc} */ - public function getTargetGraph() { - return $this->targetGraph; + public function getDefaultGraphId(string $entity_type_id): string { + $graph_ids = $this->getEntityTypeGraphIds($entity_type_id); + return reset($graph_ids); } /** - * Sets the target graph. - * - * The target graph is the graph that the entity is going to be saved in. - * - * @param string $target_graph - * The target graph machine name. - * - * @deprecated - * This will be replaced with an event listener before the 1.0 release. - * - * @see https://www.drupal.org/node/2901490 + * {@inheritdoc} */ - public function setTargetGraph($target_graph) { - trigger_error(__METHOD__ . ' will be removed before the 1.0 release.', E_USER_DEPRECATED); - $this->targetGraph = $target_graph; + public function getBundleGraphUri(string $entity_type_id, string $bundle, string $graph_id): ?string { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + $bundle_key = ($entity_type->hasKey('bundle') && $entity_type->getBundleEntityType()) ? $bundle : $entity_type_id; + return $this->getEntityTypeGraphUris($entity_type_id)[$bundle_key][$graph_id] ?? NULL; } /** - * Returns the save graph for the entity. - * - * The priority of the graphs is: - * - If there is only one graph enabled for the requested entity type, return - * this graph. - * - If there is a target graph set, this is used. This allows other modules - * to interact with the graphs. - * - The graph from where the entity is loaded. - * - The default graph from the enabled. - * - The first available graph. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity to determine the save graph for. - * - * @return string - * The graph id. + * {@inheritdoc} */ - public function getTargetGraphFromEntity(EntityInterface $entity) { - if (!empty($this->getTargetGraph())) { - return $this->getTargetGraph(); - } - elseif ($graph = $this->getGraphIdFromEntity($entity)) { - return $graph; - } - else { - $enabled_graphs = $this->getEntityTypeEnabledGraphs(); - if (in_array('default', $enabled_graphs)) { - return 'default'; + public function getEntityTypeGraphUris(string $entity_type_id, array $limit_to_graph_ids = NULL): array { + if (!isset($this->cache['structure'][$entity_type_id])) { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + if ($entity_type->hasKey('bundle') && ($bundle_entity_id = $entity_type->getBundleEntityType())) { + $bundle_keys = array_values($this->entityTypeManager->getStorage($bundle_entity_id)->getQuery()->execute()); } else { - return reset($enabled_graphs); + $bundle_keys = [$entity_type_id]; } - } - } - - /** - * Sets the target graph to the entity's graph field. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity to determine the save graph for. - * @param string $graph - * The graph id. - */ - public function setTargetGraphToEntity(EntityInterface $entity, $graph) { - $entity->set('graph', $graph); - } - /** - * Returns the graph id from the graph entity field. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object. - * - * @return string|null - * Returns the graph id or null if none is found. - * - * @todo: Maybe an exception should be thrown if no graph is found here. - */ - public function getGraphIdFromEntity(EntityInterface $entity) { - if ($entity->get('graph') && !empty($entity->get('graph')->first()->getValue())) { - return $entity->get('graph')->first()->getValue()['value']; + foreach ($bundle_keys as $bundle_key) { + $graphs = ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_key)) ? $mapping->getGraphs() : []; + $this->cache['structure'][$entity_type_id][$bundle_key] = $graphs; + } } - return NULL; - } - /** - * Returns the graph uri according to the graph id in the graph entity field. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object. - * - * @return string - * The graph id. If no graph is found in the entity, the default graph uri - * is returned. - */ - public function getGraphUriFromEntity(EntityInterface $entity) { - if ($graph_id = $this->getGraphIdFromEntity($entity)) { - return $this->getBundleGraphUri($entity->getEntityType()->getBundleEntityType(), $entity->bundle(), $graph_id); + // Limit the results. + if ($limit_to_graph_ids) { + return array_map(function (array $graphs) use ($limit_to_graph_ids): array { + return array_intersect_key($graphs, array_flip($limit_to_graph_ids)); + }, $this->cache['structure'][$entity_type_id]); } - return $this->getBundleGraphUri($entity->getEntityType()->getBundleEntityType(), $entity->bundle(), $graph_id); + return $this->cache['structure'][$entity_type_id]; } /** - * Returns the graph machine name, given the graph uri. - * - * This is basically a reverse search to get the id of the graph. - * - * @param string $entity_type_bundle_key - * The entity type bundle key e.g. 'node_type'. - * @param string $bundle_id - * The for which we are searching a graph. This is mandatory as multiple - * bundles can use the same graph. - * @param string $graph_uri - * The uri of the graph. - * - * @return string - * The id of the graph. + * {@inheritdoc} */ - public function getBundleGraphId($entity_type_bundle_key, $bundle_id, $graph_uri) { - $graphs = $this->getEntityTypeGraphUris($entity_type_bundle_key); - return array_search($graph_uri, $graphs[$bundle_id]); + public function bundleHasGraph(string $entity_type_id, string $bundle, string $graph_id): bool { + $entity_type_graphs = $this->getEntityTypeGraphUris($entity_type_id); + return !empty($entity_type_graphs[$bundle][$graph_id]); } /** - * Clears the entity type graph URIs static cache. + * {@inheritdoc} */ - public function clearEntityTypeGraphUrisCache() { - $this->entityTypeGraphUris = []; + public function getEntityTypeGraphUrisFlatList(string $entity_type_id, array $limit_to_graph_ids = NULL): array { + $graphs = $this->getEntityTypeGraphUris($entity_type_id, $limit_to_graph_ids); + return array_reduce($graphs, function (array $uris, array $bundle_graphs): array { + return array_merge($uris, array_values($bundle_graphs)); + }, []); } /** - * Clears the bundle graph URIs static cache. + * {@inheritdoc} */ - public function clearBundleGraphUrisCache() { - $this->bundleGraphUris = []; + public function getBundleGraphId(string $entity_type_id, string $bundle, string $graph_uri): ?string { + $graphs = $this->getEntityTypeGraphUris($entity_type_id); + $search = array_search($graph_uri, $graphs[$bundle]); + return $search !== FALSE ? $search : NULL; } /** - * Retrieves the uri of a bundle's graph from the settings. - * - * @param string $bundle_entity_type_id - * The bundle type key. E.g. 'node_type'. - * @param string $bundle_id - * The bundle machine name. - * @param string $graph_name - * The graph name. - * - * @return string - * The graph uri. - * - * @throws \Exception - * Thrown if the graph is not found. + * {@inheritdoc} */ - protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle_id, $graph_name) { - if (!isset($this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name])) { - $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id); - $entity_type_id = $bundle_entity_type->getBundleOf(); - $graph_uri = NULL; - if ($mapping = RdfEntityMapping::loadByName($entity_type_id, $bundle_id)) { - if (!$graph_uri = $mapping->getGraphUri($graph_name)) { - throw new \Exception("Unable to determine graph $graph_name for bundle $bundle_id"); - } - } - $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name] = $graph_uri; + public function clearCache(array $path = NULL): void { + if (empty($path)) { + $this->cache = static::EMPTY_CACHE; + return; } + NestedArray::unsetValue($this->cache, $path); - return $this->bundleGraphUris[$bundle_entity_type_id][$bundle_id][$graph_name]; + // If the path was a top-level cache category, restore its "empty version". + if (count($path) === 1 && array_key_exists($path[0], static::EMPTY_CACHE)) { + $this->cache[$path[0]] = static::EMPTY_CACHE[$path[0]]; + } } /** @@ -511,8 +183,11 @@ protected function getBundleGraphUriFromSettings($bundle_entity_type_id, $bundle * * @return \Drupal\Core\Config\Entity\ConfigEntityStorageInterface * The RDF entity graph config entity storage service. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * If the RDF entity type is not found. */ - protected function getRdfEntityGraphStorage() { + protected function getRdfEntityGraphStorage(): ConfigEntityStorageInterface { if (!isset($this->rdfEntityGraphStorage)) { $this->rdfEntityGraphStorage = $this->entityTypeManager->getStorage('rdf_entity_graph'); } diff --git a/src/RdfGraphHandlerInterface.php b/src/RdfGraphHandlerInterface.php new file mode 100644 index 00000000..a9d3ddc5 --- /dev/null +++ b/src/RdfGraphHandlerInterface.php @@ -0,0 +1,160 @@ + [], + 'structure' => [], + ]; + + /** + * Get the defined graph types for this entity type. + * + * A default graph is provided here already because there has to exist at + * least one available graph for the entities to be saved in. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return array + * A structured array of graph definitions containing a title and a + * description. The array keys are the machine names of the graphs. + */ + public function getGraphDefinitions(string $entity_type_id): array; + + /** + * Returns a list of RDF entity graph IDs given a entity type ID. + * + * This is similar to ::getGraphDefinitions() but it returns only the RDF + * entity graph IDs as an indexed array. Additionally, the list can be limited + * to a given set of IDs by passing the second argument. + * + * @param string $entity_type_id + * The entity type ID. + * @param string[]|null $limit_to_graph_ids + * (optional) A list of RDF entity graph IDs to restrict the results. If + * omitted all the RDF entity graph IDs supported by the given entity type + * will be returned. + * + * @return array + * A list of RDF entity graph IDs. + */ + public function getEntityTypeGraphIds(string $entity_type_id, array $limit_to_graph_ids = NULL): array; + + /** + * Gets the default graph ID for an entity type. + * + * The default graph is the topmost graph entity when sorting all enabled + * graphs by weight ascending for a given entity type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The default graph ID. + */ + public function getDefaultGraphId(string $entity_type_id): string; + + /** + * Returns the graph URI given an entity type ID, a bundle and the graph ID. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The entity bundle. + * @param string $graph_id + * The graph ID. + * + * @return string|null + * The URI of the requested graph. + */ + public function getBundleGraphUri(string $entity_type_id, string $bundle, string $graph_id): ?string; + + /** + * Returns the graph URIs for a given entity type. + * + * The list could be restricted to match only the passed graph IDs. + * + * @param string $entity_type_id + * The entity type to be checked. + * @param string[]|null $limit_to_graph_ids + * (optional) A list of graph IDs to limit the results. NULL means that all + * graphs are allowed. Defaults to NULL. + * + * @return string[] + * A list of graph URIs. + */ + public function getEntityTypeGraphUris(string $entity_type_id, array $limit_to_graph_ids = NULL): array; + + /** + * Checks is a graph is available for a given bundle. + * + * @param string $entity_type_id + * The entity type of the bundle. + * @param string $bundle + * The bundle to be checked. + * @param string $graph_id + * The graph to be checked. + * + * @return bool + * If the graph is available for the specific bundle. + */ + public function bundleHasGraph(string $entity_type_id, string $bundle, string $graph_id): bool; + + /** + * Returns a plain, flat list of graph URIs related to the passed entity type. + * + * @param string $entity_type_id + * The ID of the entity type. + * @param array $limit_to_graph_ids + * Optionally filter the graphs to be returned. + * + * @return array + * A flat list of graph URIs. + */ + public function getEntityTypeGraphUrisFlatList(string $entity_type_id, array $limit_to_graph_ids = NULL): array; + + /** + * Returns the graph ID for a given graph URI. + * + * This is basically a reverse search to get the ID of the graph for a given + * entity type and bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle. + * @param string $graph_uri + * The URI of the graph. + * + * @return string|null + * The ID of the graph or NULL. + */ + public function getBundleGraphId(string $entity_type_id, string $bundle, string $graph_uri): ?string; + + /** + * Clear the internal cache. + * + * @param string[]|null $path + * (optional) The path to a specific cache entry to be cleared. This + * parameter allows to clear only a specific cache entry. Defaults to NULL. + * + * @see \Drupal\Component\Utility\NestedArray::unsetValue() + */ + public function clearCache(array $path = NULL): void; + +} diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml index 246f4f09..5a925b4b 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.dummy.yml @@ -1,6 +1,10 @@ langcode: en status: true -dependencies: { } +dependencies: + config: + - rdf_entity.graph.default + - rdf_entity.graph.draft + - rdf_entity.rdfentity.dummy third_party_settings: { } id: rdf_entity.dummy entity_type_id: rdf_entity @@ -10,7 +14,7 @@ base_fields_mapping: rid: target_id: predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' + format: resource uid: target_id: predicate: '' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml index b2e3055a..0cb09379 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.multifield.yml @@ -1,6 +1,10 @@ langcode: en status: true -dependencies: { } +dependencies: + config: + - rdf_entity.graph.default + - rdf_entity.graph.draft + - rdf_entity.rdfentity.multifield third_party_settings: { } id: rdf_entity.multifield entity_type_id: rdf_entity @@ -10,11 +14,11 @@ base_fields_mapping: rid: target_id: predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' + format: resource uid: target_id: predicate: 'http://example.com/multifield_with_owner/uid' - format: 'integer' + format: integer label: value: predicate: 'http://example.com/multifield_label' diff --git a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml index e565bfc6..5c319116 100644 --- a/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml +++ b/tests/modules/rdf_entity_test/config/install/rdf_entity.mapping.rdf_entity.with_owner.yml @@ -1,6 +1,10 @@ langcode: en status: true -dependencies: { } +dependencies: + config: + - rdf_entity.graph.default + - rdf_entity.graph.draft + - rdf_entity.rdfentity.with_owner third_party_settings: { } id: rdf_entity.with_owner entity_type_id: rdf_entity @@ -10,15 +14,15 @@ base_fields_mapping: rid: target_id: predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - format: 'resource' + format: resource uid: target_id: predicate: 'http://example.com/dummy_with_owner/uid' - format: 'integer' + format: integer label: value: predicate: 'http://example.com/dummy_with_owner_label' - format: 'literal' + format: literal uuid: value: predicate: '' diff --git a/tests/src/Kernel/SparqlEntityInsertTest.php b/tests/src/Kernel/SparqlEntityInsertTest.php index 9a71cb00..05a281ac 100644 --- a/tests/src/Kernel/SparqlEntityInsertTest.php +++ b/tests/src/Kernel/SparqlEntityInsertTest.php @@ -1,6 +1,6 @@ Date: Fri, 19 Jan 2018 10:43:23 +0200 Subject: [PATCH 19/30] Additional fixes. --- modules/rdf_draft/rdf_draft.module | 15 +++++++--- src/Entity/RdfEntityGraph.php | 2 ++ src/Entity/RdfEntityMapping.php | 35 ++++++++++++++--------- src/Entity/RdfEntitySparqlStorage.php | 27 +++++++++++++---- src/Form/RdfEntityGraphForm.php | 9 ++++-- src/ParamConverter/RdfEntityConverter.php | 2 +- src/RdfEntitySparqlStorageInterface.php | 14 +++++++++ 7 files changed, 78 insertions(+), 26 deletions(-) diff --git a/modules/rdf_draft/rdf_draft.module b/modules/rdf_draft/rdf_draft.module index f78296f2..e3885f02 100644 --- a/modules/rdf_draft/rdf_draft.module +++ b/modules/rdf_draft/rdf_draft.module @@ -6,7 +6,7 @@ */ use Drupal\Core\Entity\EntityInterface; -use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; +use Drupal\rdf_entity\RdfEntitySparqlStorageInterface; use Drupal\rdf_entity\RdfEntityGraphInterface; /** @@ -42,11 +42,18 @@ function rdf_draft_entity_type_alter(array &$entity_types) { */ function rdf_draft_entity_operation(EntityInterface $entity) { $operations = []; - $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); - if ($storage instanceof RdfEntitySparqlStorage) { + try { + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); + } + catch (\Exception $exception) { + return []; + } + + if ($storage instanceof RdfEntitySparqlStorageInterface) { foreach ($storage->getGraphDefinitions() as $name => $definition) { $link_template = "rdf-draft-$name"; - if ($entity->hasLinkTemplate($link_template) && $entity->hasGraph($name)) { + if ($entity->hasLinkTemplate($link_template) && $storage->hasGraph($entity, $name)) { $operations[$name] = [ 'title' => t('View @graph', ['@graph' => $name]), 'weight' => 100, diff --git a/src/Entity/RdfEntityGraph.php b/src/Entity/RdfEntityGraph.php index b141702b..ffcae870 100644 --- a/src/Entity/RdfEntityGraph.php +++ b/src/Entity/RdfEntityGraph.php @@ -1,5 +1,7 @@ entityTypeManager()->getStorage($values['entity_type_id'])) { + try { + $storage = $this->entityTypeManager()->getStorage($values['entity_type_id']); + } + catch (\Exception $exception) { throw new \InvalidArgumentException("Invalid entity type: {$values['entity_type_id']}."); } - if ($storage->getEntityType()->hasKey('bundle')) { - // If this entity type requires a bundle, the bundle should be passed. + // Only entities with RDF storage are eligible. + if (!$storage instanceof RdfEntitySparqlStorageInterface) { + throw new \InvalidArgumentException("Cannot handle non-RDF entity type: {$values['entity_type_id']}."); + } + + if ($storage->getEntityType()->hasKey('bundle') && $storage->getEntityType()->getBundleEntityType()) { + // If this entity type supports bundles as config entities, then the + // bundle should have been passed. if (empty($values['bundle'])) { throw new \InvalidArgumentException('Missing required property: bundle.'); } } else { - // The bundle is the entiy type ID, regardless of the passed value. + // The bundle is the entity type ID, regardless of the passed value. $values['bundle'] = $values['entity_type_id']; } - // Only entities with RDF storage are eligible. - if (!$storage instanceof RdfEntitySparqlStorage) { - throw new \InvalidArgumentException("Cannot handle non-RDF entity type: {$values['entity_type_id']}."); - } - parent::__construct($values, $entity_type); } @@ -124,7 +129,7 @@ public function __construct(array $values, $entity_type) { * {@inheritdoc} */ public function id() { - return "{$this->entity_type_id}.{$this->bundle}"; + return "{$this->getTargetEntityTypeId()}.{$this->getTargetBundle()}"; } /** @@ -271,7 +276,7 @@ public function calculateDependencies() { parent::calculateDependencies(); /** @var \Drupal\rdf_entity\RdfEntityGraphInterface $graph */ - foreach (RdfEntityGraph::loadMultiple(array_keys($this->get('graph'))) as $graph) { + foreach (RdfEntityGraph::loadMultiple(array_keys($this->getGraphs())) as $graph) { // Add dependency to graph. $this->addDependency($graph->getConfigDependencyKey(), $graph->getConfigDependencyName()); } @@ -279,11 +284,15 @@ public function calculateDependencies() { // Add dependency to the paired bundle entity. if ($entity_type = $this->getTargetEntityType()) { if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { - if ($bundle_storage = $this->entityTypeManager()->getStorage($bundle_entity_type_id)) { + try { + $bundle_storage = $this->entityTypeManager()->getStorage($bundle_entity_type_id); if ($bundle_entity = $bundle_storage->load($this->getTargetBundle())) { $this->addDependency($bundle_entity->getConfigDependencyKey(), $bundle_entity->getConfigDependencyName()); } } + catch (\Exception $exception) { + // Fail silently to allow the next graphs to be added. + } } } @@ -305,7 +314,7 @@ public function onDependencyRemoval(array $dependencies) { if ($graph->id() !== RdfEntityGraphInterface::DEFAULT) { // Remove the reference to the deleted graph and flag this mapping // entity to be re-saved. - unset($this->graph[$graph->id()]); + $this->unsetGraphs([$graph->id()]); $changed = TRUE; } } diff --git a/src/Entity/RdfEntitySparqlStorage.php b/src/Entity/RdfEntitySparqlStorage.php index 8cba91e6..e713411c 100644 --- a/src/Entity/RdfEntitySparqlStorage.php +++ b/src/Entity/RdfEntitySparqlStorage.php @@ -176,7 +176,7 @@ public function getGraphDefinitions(): array { /** * {@inheritdoc} */ - public function doLoadMultiple(array $ids = NULL, array $graph_ids = []) { + protected function doLoadMultiple(array $ids = NULL, array $graph_ids = []) { // Attempt to load entities from the persistent cache. This will remove IDs // that were loaded from $ids. $entities_from_cache = $this->getFromPersistentCache($ids, $graph_ids); @@ -474,7 +474,7 @@ public function loadMultiple(array $ids = NULL, array $graph_ids = NULL): array } // We copy this part from parent::loadMultiple(), otherwise we cannot pass - // the $graph_ids to self::getFromStaticCache() and self::doLoadMultiple(). + // the $graph_ids to self::getFromStaticCache() and self::doLoadMultiple(). // START parent::loadMultiple() fork. $entities = []; $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; @@ -564,7 +564,7 @@ protected function doPreSave(EntityInterface $entity) { } // END forking from ContentEntityStorageBase::doPreSave(). // Finally reset the entity original graph property so that that its updated - // value it's available for the rest of this request. + // value is available for the rest of this request. $entity->rdfEntityOriginalGraph = $entity->graph->value; return $id; @@ -631,7 +631,16 @@ public function hasGraph(EntityInterface $entity, string $graph_id): bool { /** * {@inheritdoc} */ - public function loadByProperties(array $values = []) { + public function loadByProperties(array $values = [], array $graph_ids = NULL): array { + $entity_type_graph_ids = $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); + // Filter out unknown graphs and provide sane defaults. + $graph_ids = $graph_ids ? array_values(array_intersect($graph_ids, $entity_type_graph_ids)) : $entity_type_graph_ids; + + // If there aro no valid graph candidates, there are no entities. + if (!$graph_ids) { + return []; + } + // If UUID is queried, just swap it with the ID. They are the same but UUID // is not stored, while on ID we can rely. $uuid_key = $this->entityType->getKey('uuid'); @@ -639,7 +648,15 @@ public function loadByProperties(array $values = []) { $values[$this->entityType->getKey('id')] = $values[$uuid_key]; unset($values[$uuid_key]); } - return parent::loadByProperties($values); + + /** @var \Drupal\rdf_entity\Entity\Query\Sparql\Query $query */ + $query = $this->getQuery(); + $query->setGraphType($graph_ids); + $query->accessCheck(FALSE); + $this->buildPropertyQuery($query, $values); + $result = $query->execute(); + + return $result ? $this->loadMultiple($result, $graph_ids) : []; } /** diff --git a/src/Form/RdfEntityGraphForm.php b/src/Form/RdfEntityGraphForm.php index 26c5d78a..2dae3c41 100644 --- a/src/Form/RdfEntityGraphForm.php +++ b/src/Form/RdfEntityGraphForm.php @@ -1,8 +1,11 @@ getValue('entity_types'); $form_state->setValue('entity_types', array_values(array_filter($entity_types))); @@ -91,7 +94,7 @@ public function buildEntity(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function save(array $form, FormStateInterface $form_state) { + public function save(array $form, FormStateInterface $form_state): int { $form_state->setRedirect('entity.rdf_entity_graph.collection'); drupal_set_message($this->t("Graph %name (%id) has been saved.", [ '%name' => $this->getEntity()->label(), diff --git a/src/ParamConverter/RdfEntityConverter.php b/src/ParamConverter/RdfEntityConverter.php index 4800cea3..24c2006e 100644 --- a/src/ParamConverter/RdfEntityConverter.php +++ b/src/ParamConverter/RdfEntityConverter.php @@ -43,7 +43,7 @@ public function applies($definition, $name, Route $route) { * {@inheritdoc} */ public function convert($value, $definition, $name, array $defaults) { - // Here the escaped uri is transformed into a valid URI. + // Here the escaped URI is transformed into a valid URI. if (!SparqlArg::isValidResource($value)) { $value = UriEncoder::decodeUrl($value); } diff --git a/src/RdfEntitySparqlStorageInterface.php b/src/RdfEntitySparqlStorageInterface.php index bdc95c64..ee62e6ba 100644 --- a/src/RdfEntitySparqlStorageInterface.php +++ b/src/RdfEntitySparqlStorageInterface.php @@ -132,4 +132,18 @@ public function loadMultiple(array $ids = NULL, array $graph_ids = NULL): array; */ public function loadUnchanged($id, array $graph_ids = NULL): ?ContentEntityInterface; + /** + * Load entities by their property values. + * + * @param array $values + * An associative array where the keys are the property names and the + * values are the values those properties must have. + * @param string[]|null $graph_ids + * An ordered list of candidate graph IDs. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * An array of entity objects indexed by their ids. + */ + public function loadByProperties(array $values = [], array $graph_ids = NULL): array; + } From 39f43666132f12eb6d0e40e8350d853a65acd1d0 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:24:22 +0200 Subject: [PATCH 20/30] Add reference to #15 follow-up. --- rdf_entity.module | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rdf_entity.module b/rdf_entity.module index 60c3885f..09f6587e 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -37,7 +37,8 @@ function rdf_entity_entity_base_field_info_alter(&$fields, EntityTypeInterface $ } // @todo Now that graphs are entities, transform this field into an entity - // reference field. + // reference field in #15. + // @see https://github.com/ec-europa/rdf_entity/issues/15 $fields['graph'] = BaseFieldDefinition::create('uri') ->setLabel(t('The graph where the entity is stored.')) ->setTargetEntityTypeId('rdf_entity') From 4d00706785fe223d15f3f2a8e707caa29d690a99 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:25:13 +0200 Subject: [PATCH 21/30] Rename rdf_entity_get_third_party_property() to rdf_entity_get_mapping_property(). --- rdf_entity.module | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/rdf_entity.module b/rdf_entity.module index 09f6587e..a8704cf1 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -89,7 +89,7 @@ function rdf_entity_form_field_storage_config_edit_form_alter(&$form, FormStateI } } - $settings = rdf_entity_get_third_party_property($entity, 'mapping', $column); + $settings = rdf_entity_get_mapping_property($entity, 'mapping', $column); $form['rdf_mapping'][$column] = [ '#type' => 'details', @@ -120,11 +120,21 @@ function rdf_entity_form_field_storage_config_edit_form_alter(&$form, FormStateI /** * Retrieve nested third party settings from object. * - * The object may be either a bundle entity or a field storage config entity. + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $object + * The object may be either a bundle entity or a field storage config entity. + * @param string $property + * The property for which to retrieve the mapping. + * @param string $column + * The field column. + * @param mixed $default + * (optional) The default value. Defaults to NULL. + * + * @return mixed + * The mapping. * * @todo Move this to a service (or to RDF storage?) */ -function rdf_entity_get_third_party_property(ConfigEntityInterface $object, $property, $column, $default = NULL) { +function rdf_entity_get_mapping_property(ConfigEntityInterface $object, string $property, string $column, $default = NULL) { // Mapping data requested for a configurable field. if ($object instanceof FieldStorageConfigInterface) { $property_value = $object->getThirdPartySetting('rdf_entity', $property, FALSE); From 4f369a03ba35e43217aaa137fcc356e66b37dd1c Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:26:08 +0200 Subject: [PATCH 22/30] Allow the exception to propagate when saving a mapping in the UI. --- rdf_entity.module | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rdf_entity.module b/rdf_entity.module index a8704cf1..781a2104 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -317,6 +317,9 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state object. * + * @throws \Exception + * If the mapping fails to save. + * * @see rdf_entity_form_alter() */ function rdf_entity_save_mapping(string $entity_type_id, ConfigEntityInterface $bundle_entity, array $form, FormStateInterface $form_state): void { @@ -335,6 +338,7 @@ function rdf_entity_save_mapping(string $entity_type_id, ConfigEntityInterface $ } catch (\Exception $exception) { \Drupal::logger('rdf_entity')->critical(t('Could not save RDF entity mapping'), ['exception' => $exception]); + throw $exception; } } From 322d6d85352b5a66554045d8d139b23b4b30eb1e Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:26:41 +0200 Subject: [PATCH 23/30] Remove stale @todo. --- modules/rdf_draft/src/RdfGraphAccessCheck.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/rdf_draft/src/RdfGraphAccessCheck.php b/modules/rdf_draft/src/RdfGraphAccessCheck.php index d093e877..b15dfc17 100644 --- a/modules/rdf_draft/src/RdfGraphAccessCheck.php +++ b/modules/rdf_draft/src/RdfGraphAccessCheck.php @@ -55,7 +55,6 @@ public function access(Route $route, AccountInterface $account, RdfInterface $rd // The active graph is the published graph. It is handled by the default // operation handler. - // @todo: getActiveGraph is not the default. We should load from settings. $default_graph = $storage->getGraphHandler()->getBundleGraphUri($rdf_entity->getEntityTypeId(), $rdf_entity->bundle(), RdfEntityGraphInterface::DEFAULT); $requested_graph = $storage->getGraphHandler()->getBundleGraphUri($rdf_entity->getEntityTypeId(), $rdf_entity->bundle(), $graph); if ($requested_graph == $default_graph) { From ccc59d81c8d1ce4dfec9c15fe4afbf94459ca7c7 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:29:32 +0200 Subject: [PATCH 24/30] Throw exception when invalid graphs are passed to storage loaders. --- .../tests/src/Kernel/RdfDraftGraphTest.php | 17 ++----- src/Entity/RdfEntitySparqlStorage.php | 47 ++++++++++++------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php b/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php index 3e241f2d..73121125 100644 --- a/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php +++ b/modules/rdf_draft/tests/src/Kernel/RdfDraftGraphTest.php @@ -76,19 +76,6 @@ public function test() { $this->assertTrue($storage->hasGraph($apple, 'default')); $this->assertTrue($storage->hasGraph($apple, 'draft')); - // Try to request the entity from a non-existing graph. - $apple = $storage->load($id, ['invalid graph']); - $this->assertNull($apple); - - // Check cascading over the graph candidate list. - $apple = $storage->load($id, ['invalid graph', 'default', 'draft']); - $this->assertEquals('default', $apple->graph->value); - $this->assertEquals('Apple', $apple->label()); - - $apple = $storage->load($id, ['invalid graph', 'draft', 'default']); - $this->assertEquals('draft', $apple->graph->value); - $this->assertEquals('Draft of Apple', $apple->label()); - // Create a new graph and add it to the mapping. RdfEntityGraph::create([ 'id' => 'arbitrary', @@ -107,6 +94,10 @@ public function test() { $this->assertEquals('arbitrary', $apple->graph->value); $this->assertEquals('Apple in arbitrary graph', $apple->label()); + // Try to request the entity from a non-existing graph. + $this->setExpectedException(\InvalidArgumentException::class, "Graph 'invalid graph' doesn't exist for entity type 'rdf_entity'."); + $apple = $storage->load($id, ['invalid graph', 'default', 'draft']); + // Delete the draft version. $storage->deleteFromGraph($apple->id(), 'draft'); $this->assertNull($storage->load($id, ['draft'])); diff --git a/src/Entity/RdfEntitySparqlStorage.php b/src/Entity/RdfEntitySparqlStorage.php index e713411c..be019806 100644 --- a/src/Entity/RdfEntitySparqlStorage.php +++ b/src/Entity/RdfEntitySparqlStorage.php @@ -465,13 +465,7 @@ public function load($id, array $graph_ids = NULL): ?ContentEntityInterface { * {@inheritdoc} */ public function loadMultiple(array $ids = NULL, array $graph_ids = NULL): array { - $entity_type_graph_ids = $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); - $graph_ids = $graph_ids ? array_values(array_intersect($graph_ids, $entity_type_graph_ids)) : $entity_type_graph_ids; - - // If there aro no valid graph candidates, there are no entities. - if (!$graph_ids) { - return []; - } + $this->checkGraphs($graph_ids); // We copy this part from parent::loadMultiple(), otherwise we cannot pass // the $graph_ids to self::getFromStaticCache() and self::doLoadMultiple(). @@ -574,10 +568,10 @@ protected function doPreSave(EntityInterface $entity) { * {@inheritdoc} */ public function loadUnchanged($id, array $graph_ids = NULL): ?ContentEntityInterface { + $this->checkGraphs($graph_ids); + // Code forked from parent::loadUnchanged() and adapted to accept graph // candidates. - $graph_ids = $graph_ids ?: $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); - $ids = [$id]; parent::resetCache($ids); $entities = $this->getFromPersistentCache($ids, $graph_ids); @@ -632,14 +626,7 @@ public function hasGraph(EntityInterface $entity, string $graph_id): bool { * {@inheritdoc} */ public function loadByProperties(array $values = [], array $graph_ids = NULL): array { - $entity_type_graph_ids = $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); - // Filter out unknown graphs and provide sane defaults. - $graph_ids = $graph_ids ? array_values(array_intersect($graph_ids, $entity_type_graph_ids)) : $entity_type_graph_ids; - - // If there aro no valid graph candidates, there are no entities. - if (!$graph_ids) { - return []; - } + $this->checkGraphs($graph_ids); // If UUID is queried, just swap it with the ID. They are the same but UUID // is not stored, while on ID we can rely. @@ -1256,4 +1243,30 @@ public function idExists(string $id, string $graph = NULL): bool { return $this->sparql->query($query)->isTrue(); } + /** + * Validates a list of graphs and provide defaults. + * + * @param string[]|null $graph_ids + * An ordered list of candidate graph IDs. + * + * @throws \InvalidArgumentException + * If at least one of passed graphs doesn't exist for this entity type. + */ + protected function checkGraphs(array &$graph_ids = NULL): void { + $entity_type_graph_ids = $this->getGraphHandler()->getEntityTypeGraphIds($this->getEntityTypeId()); + + if (!$graph_ids) { + // No passed graph means "all graphs for this entity type". + $graph_ids = $entity_type_graph_ids; + return; + } + + // Validate each passed graph. + array_walk($graph_ids, function (string $graph_id) use ($entity_type_graph_ids): void { + if (!in_array($graph_id, $entity_type_graph_ids)) { + throw new \InvalidArgumentException("Graph '$graph_id' doesn't exist for entity type '{$this->getEntityTypeId()}'."); + } + }); + } + } From 0ae10cde3e2c51e7543f6936a2ff3eb8330840ad Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:30:05 +0200 Subject: [PATCH 25/30] Missing strict type declaration. --- modules/rdf_taxonomy/src/TermRdfStorage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/rdf_taxonomy/src/TermRdfStorage.php b/modules/rdf_taxonomy/src/TermRdfStorage.php index abee5496..5927d751 100644 --- a/modules/rdf_taxonomy/src/TermRdfStorage.php +++ b/modules/rdf_taxonomy/src/TermRdfStorage.php @@ -1,5 +1,7 @@ Date: Mon, 22 Jan 2018 17:30:53 +0200 Subject: [PATCH 26/30] Typo in RdfEntityGraphToggle docs. --- src/Controller/RdfEntityGraphToggle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/RdfEntityGraphToggle.php b/src/Controller/RdfEntityGraphToggle.php index a181adcb..9c01506e 100644 --- a/src/Controller/RdfEntityGraphToggle.php +++ b/src/Controller/RdfEntityGraphToggle.php @@ -10,7 +10,7 @@ use Drupal\rdf_entity\RdfEntityGraphInterface; /** - * Toggles a RDf entity graph to enable or disable. + * Toggles an RDF entity graph to enabled or disabled. */ class RdfEntityGraphToggle extends ControllerBase { From 11b92c763b923360f567f5cfd07c4e8a009c0ef8 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Mon, 22 Jan 2018 17:31:17 +0200 Subject: [PATCH 27/30] Improve wording in API.md. --- API.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/API.md b/API.md index f3ce3429..469194b3 100644 --- a/API.md +++ b/API.md @@ -4,9 +4,9 @@ ## RDF Graphs -Entities using the SPARQL storage can be stored in different graphs, which are -actually versions of the same entity, You can use graphs, for example, to store -a draft version of the entity. +Entities using the SPARQL storage can be stored in different graphs. Graphs can +be used to store different versions or states of the same entity. Depending on +the use case you can use graphs to store a draft version of the entity. ### RDF graphs storage @@ -16,12 +16,12 @@ offered to handle the graphs. ### Graphs CRUD -Graphs are config entities of type `rdf_entity_graph` and are supporting the -whole Drupal API regarding config entities. You can add, edit, delete graphs -also using the UI, at `/admin/config/rdf_entity/graph`. The `default` graph, -shipped with `rdf_entity` module cannot be deleted or restricted to specific -entity types. However, you can still edit its name and description. Only enabled -graphs are taken into account by the SPARQL backend. +Graphs are config entities of type `rdf_entity_graph` and are supported by the +Drupal API. You can add, edit, delete graphs also by using the UI provided at +`/admin/config/rdf_entity/graph`. The `default` graph, shipped with `rdf_entity` +module cannot be deleted or restricted to specific entity types. However, you +can still edit its name and description. Only enabled graphs are taken into +account by the SPARQL backend. The order of graph entities is important. You can configure a priority by settings the `weight` property. Also this could be done in the UI. @@ -68,7 +68,8 @@ $entity = $storage->load($id, ['draft']); // Load from the first graph where the entity exists. First, the storage will // attempt to load the entity from the 'draft' graph. If this entity doesn't // exist in the 'draft' graph, will fallback to the next one which is 'sync' and -// so on. If fails with all, the normal behaviour is in place: will return NULL. +// so on. If the entity is not found in any of the graphs, normal behaviour is +// in place: will return NULL. $entity = $storage->load($id, ['draft', 'sync', 'obsolete', ...]); // Load multiple entities using a graph candidate list. From de7fb03cb2dce0f5205ae60e714d47f5ced412b0 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 23 Jan 2018 14:49:07 +0200 Subject: [PATCH 28/30] Use the RdfGraphHandlerInterface instead of class on type hinting. --- modules/rdf_draft/rdf_draft.module | 2 +- src/Entity/Query/Sparql/Query.php | 8 ++++---- src/Entity/Query/Sparql/QueryFactory.php | 8 ++++---- src/Entity/Query/Sparql/SparqlCondition.php | 6 +++--- src/Entity/RdfEntitySparqlStorage.php | 7 +++---- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/modules/rdf_draft/rdf_draft.module b/modules/rdf_draft/rdf_draft.module index e3885f02..0b54dc04 100644 --- a/modules/rdf_draft/rdf_draft.module +++ b/modules/rdf_draft/rdf_draft.module @@ -19,7 +19,7 @@ function rdf_draft_entity_type_alter(array &$entity_types) { return; } - /** @var \Drupal\rdf_entity\RdfGraphHandler $graph_handler */ + /** @var \Drupal\rdf_entity\RdfGraphHandlerInterface $graph_handler */ $graph_handler = \Drupal::service('sparql.graph_handler'); /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ diff --git a/src/Entity/Query/Sparql/Query.php b/src/Entity/Query/Sparql/Query.php index 4b0b5daf..28b2dd4f 100644 --- a/src/Entity/Query/Sparql/Query.php +++ b/src/Entity/Query/Sparql/Query.php @@ -9,8 +9,8 @@ use Drupal\Core\Entity\Query\Sql\ConditionAggregate; use Drupal\rdf_entity\Database\Driver\sparql\Connection; use Drupal\rdf_entity\Entity\RdfEntitySparqlStorage; -use Drupal\rdf_entity\RdfGraphHandler; use Drupal\rdf_entity\RdfFieldHandler; +use Drupal\rdf_entity\RdfGraphHandlerInterface; /** * The base entity query class for Rdf entities. @@ -73,7 +73,7 @@ class Query extends QueryBase implements QueryInterface { /** * The rdf graph handler service object. * - * @var \Drupal\rdf_entity\RdfGraphHandler + * @var \Drupal\rdf_entity\RdfGraphHandlerInterface */ protected $graphHandler; @@ -98,7 +98,7 @@ class Query extends QueryBase implements QueryInterface { * List of potential namespaces of the classes belonging to this query. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager service object. - * @param \Drupal\rdf_entity\RdfGraphHandler $rdf_graph_handler + * @param \Drupal\rdf_entity\RdfGraphHandlerInterface $rdf_graph_handler * The rdf graph handler service. * @param \Drupal\rdf_entity\RdfFieldHandler $rdf_field_handler * The rdf mapping handler service. @@ -108,7 +108,7 @@ class Query extends QueryBase implements QueryInterface { * * @todo: Is this exception check needed? */ - public function __construct(EntityTypeInterface $entity_type, $conjunction, Connection $connection, array $namespaces, EntityTypeManagerInterface $entity_type_manager, RdfGraphHandler $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { + public function __construct(EntityTypeInterface $entity_type, $conjunction, Connection $connection, array $namespaces, EntityTypeManagerInterface $entity_type_manager, RdfGraphHandlerInterface $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { // Assign the handlers before calling the parent so that they can be passed // to the condition class properly. $this->graphHandler = $rdf_graph_handler; diff --git a/src/Entity/Query/Sparql/QueryFactory.php b/src/Entity/Query/Sparql/QueryFactory.php index faf12339..21a2a704 100644 --- a/src/Entity/Query/Sparql/QueryFactory.php +++ b/src/Entity/Query/Sparql/QueryFactory.php @@ -7,8 +7,8 @@ use Drupal\Core\Entity\Query\QueryBase; use Drupal\Core\Entity\Query\QueryFactoryInterface; use Drupal\rdf_entity\Database\Driver\sparql\Connection; -use Drupal\rdf_entity\RdfGraphHandler; use Drupal\rdf_entity\RdfFieldHandler; +use Drupal\rdf_entity\RdfGraphHandlerInterface; /** * Provides a factory for creating entity query objects for the null backend. @@ -39,7 +39,7 @@ class QueryFactory implements QueryFactoryInterface { /** * The rdf graph helper service object. * - * @var \Drupal\rdf_entity\RdfGraphHandler + * @var \Drupal\rdf_entity\RdfGraphHandlerInterface */ protected $graphHandler; @@ -57,12 +57,12 @@ class QueryFactory implements QueryFactoryInterface { * The connection object. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\rdf_entity\RdfGraphHandler $rdf_graph_handler + * @param \Drupal\rdf_entity\RdfGraphHandlerInterface $rdf_graph_handler * The rdf graph helper service. * @param \Drupal\rdf_entity\RdfFieldHandler $rdf_field_handler * The rdf mapping helper service. */ - public function __construct(Connection $connection, EntityTypeManagerInterface $entity_type_manager, RdfGraphHandler $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { + public function __construct(Connection $connection, EntityTypeManagerInterface $entity_type_manager, RdfGraphHandlerInterface $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { $this->connection = $connection; $this->namespaces = QueryBase::getNamespaces($this); $this->entityTypeManager = $entity_type_manager; diff --git a/src/Entity/Query/Sparql/SparqlCondition.php b/src/Entity/Query/Sparql/SparqlCondition.php index 93c5c7b5..2c359cff 100644 --- a/src/Entity/Query/Sparql/SparqlCondition.php +++ b/src/Entity/Query/Sparql/SparqlCondition.php @@ -6,8 +6,8 @@ use Drupal\Core\Entity\Query\ConditionInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\rdf_entity\RdfGraphHandler; use Drupal\rdf_entity\RdfFieldHandler; +use Drupal\rdf_entity\RdfGraphHandlerInterface; use EasyRdf\Serialiser\Ntriples; /** @@ -25,7 +25,7 @@ class SparqlCondition extends ConditionFundamentals implements ConditionInterfac /** * The rdf graph handler service object. * - * @var \Drupal\rdf_entity\RdfGraphHandler + * @var \Drupal\rdf_entity\RdfGraphHandlerInterface */ protected $graphHandler; @@ -188,7 +188,7 @@ class SparqlCondition extends ConditionFundamentals implements ConditionInterfac /** * {@inheritdoc} */ - public function __construct($conjunction, QueryInterface $query, array $namespaces, RdfGraphHandler $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { + public function __construct($conjunction, QueryInterface $query, array $namespaces, RdfGraphHandlerInterface $rdf_graph_handler, RdfFieldHandler $rdf_field_handler) { $conjunction = strtoupper($conjunction); parent::__construct($conjunction, $query, $namespaces); $this->graphHandler = $rdf_graph_handler; diff --git a/src/Entity/RdfEntitySparqlStorage.php b/src/Entity/RdfEntitySparqlStorage.php index be019806..0bd20528 100644 --- a/src/Entity/RdfEntitySparqlStorage.php +++ b/src/Entity/RdfEntitySparqlStorage.php @@ -23,7 +23,6 @@ use Drupal\rdf_entity\Exception\DuplicatedIdException; use Drupal\rdf_entity\RdfEntityIdPluginManager; use Drupal\rdf_entity\RdfEntitySparqlStorageInterface; -use Drupal\rdf_entity\RdfGraphHandler; use Drupal\rdf_entity\RdfFieldHandler; use Drupal\rdf_entity\RdfGraphHandlerInterface; use EasyRdf\Graph; @@ -67,7 +66,7 @@ class RdfEntitySparqlStorage extends ContentEntityStorageBase implements RdfEnti /** * The rdf graph helper service object. * - * @var \Drupal\rdf_entity\RdfGraphHandler + * @var \Drupal\rdf_entity\RdfGraphHandlerInterface */ protected $graphHandler; @@ -102,14 +101,14 @@ class RdfEntitySparqlStorage extends ContentEntityStorageBase implements RdfEnti * The language manager service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. - * @param \Drupal\rdf_entity\RdfGraphHandler $rdf_graph_handler + * @param \Drupal\rdf_entity\RdfGraphHandlerInterface $rdf_graph_handler * The rdf graph helper service. * @param \Drupal\rdf_entity\RdfFieldHandler $rdf_field_handler * The rdf mapping helper service. * @param \Drupal\rdf_entity\RdfEntityIdPluginManager $entity_id_plugin_manager * The RDF entity ID generator plugin manager. */ - public function __construct(EntityTypeInterface $entity_type, Connection $sparql, EntityManagerInterface $entity_manager, EntityTypeManagerInterface $entity_type_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, RdfGraphHandler $rdf_graph_handler, RdfFieldHandler $rdf_field_handler, RdfEntityIdPluginManager $entity_id_plugin_manager) { + public function __construct(EntityTypeInterface $entity_type, Connection $sparql, EntityManagerInterface $entity_manager, EntityTypeManagerInterface $entity_type_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, RdfGraphHandlerInterface $rdf_graph_handler, RdfFieldHandler $rdf_field_handler, RdfEntityIdPluginManager $entity_id_plugin_manager) { parent::__construct($entity_type, $entity_manager, $cache); $this->sparql = $sparql; $this->languageManager = $language_manager; From 31828f7f824640f665ce7376592ba13784fafae9 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 23 Jan 2018 14:49:43 +0200 Subject: [PATCH 29/30] Unmute some exceptions. --- modules/rdf_draft/rdf_draft.module | 10 ++----- rdf_entity.install | 46 +++++++++++++++--------------- rdf_entity.module | 23 ++++++--------- src/Entity/RdfEntityMapping.php | 18 ++++++------ 4 files changed, 42 insertions(+), 55 deletions(-) diff --git a/modules/rdf_draft/rdf_draft.module b/modules/rdf_draft/rdf_draft.module index 0b54dc04..21c198e2 100644 --- a/modules/rdf_draft/rdf_draft.module +++ b/modules/rdf_draft/rdf_draft.module @@ -42,13 +42,9 @@ function rdf_draft_entity_type_alter(array &$entity_types) { */ function rdf_draft_entity_operation(EntityInterface $entity) { $operations = []; - try { - /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ - $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); - } - catch (\Exception $exception) { - return []; - } + + /** @var \Drupal\rdf_entity\RdfEntitySparqlStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); if ($storage instanceof RdfEntitySparqlStorageInterface) { foreach ($storage->getGraphDefinitions() as $name => $definition) { diff --git a/rdf_entity.install b/rdf_entity.install index 1fea387c..5c3f810a 100644 --- a/rdf_entity.install +++ b/rdf_entity.install @@ -49,32 +49,32 @@ function rdf_entity_update_8001() { // RdfEntitySparqlStorage and move their 3rd party settings belonging to // rdf_entity module into their dedicated rdf_entity_mapping config entities. foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) { - if ($storage = $entity_type_manager->getStorage($entity_type_id)) { - if ($storage instanceof RdfEntitySparqlStorage) { - if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { - if ($bundle_storage = $entity_type_manager->getStorage($bundle_entity_type_id)) { - /** @var \Drupal\Core\Config\Entity\ConfigEntityBase $bundle_entity */ - foreach ($bundle_storage->loadMultiple() as $bundle => $bundle_entity) { - $third_party_settings = $bundle_entity->getThirdPartySettings('rdf_entity'); - $values = [ - 'entity_type_id' => $entity_type_id, - 'bundle' => $bundle, - ] + $third_party_settings; - // Rename key 'mapping' to 'base_fields_mapping'. - $values['base_fields_mapping'] = $values['mapping'] ?? []; - unset($values['mapping']); + $storage = $entity_type_manager->getStorage($entity_type_id); + if (!$storage instanceof RdfEntitySparqlStorage) { + continue; + } + + if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { + $bundle_storage = $entity_type_manager->getStorage($bundle_entity_type_id); + /** @var \Drupal\Core\Config\Entity\ConfigEntityBase $bundle_entity */ + foreach ($bundle_storage->loadMultiple() as $bundle => $bundle_entity) { + $third_party_settings = $bundle_entity->getThirdPartySettings('rdf_entity'); + $values = [ + 'entity_type_id' => $entity_type_id, + 'bundle' => $bundle, + ] + $third_party_settings; + // Rename key 'mapping' to 'base_fields_mapping'. + $values['base_fields_mapping'] = $values['mapping'] ?? []; + unset($values['mapping']); - // Create the new 'rdf_entity_mapping' entity. - RdfEntityMapping::create($values)->save(); + // Create and save the new 'rdf_entity_mapping' entity. + RdfEntityMapping::create($values)->save(); - // Cleanup 3rd party settings from the bundle entity. - foreach ($third_party_settings as $key => $value) { - $bundle_entity->unsetThirdPartySetting('rdf_entity', $key); - } - $bundle_entity->save(); - } - } + // Cleanup 3rd party settings from the bundle entity. + foreach ($third_party_settings as $key => $value) { + $bundle_entity->unsetThirdPartySetting('rdf_entity', $key); } + $bundle_entity->save(); } } } diff --git a/rdf_entity.module b/rdf_entity.module index 781a2104..47924356 100755 --- a/rdf_entity.module +++ b/rdf_entity.module @@ -323,23 +323,16 @@ function rdf_entity_form_alter(&$form, FormStateInterface $form_state) { * @see rdf_entity_form_alter() */ function rdf_entity_save_mapping(string $entity_type_id, ConfigEntityInterface $bundle_entity, array $form, FormStateInterface $form_state): void { + $values = $form_state->getValue('rdf_entity'); /** @var \Drupal\rdf_entity\RdfEntityMappingInterface $mapping */ $mapping = $form_state->get('rdf_entity_mapping'); - $values = $form_state->getValue('rdf_entity'); - - try { - $mapping - ->setRdfType($values['rdf_type']) - ->setEntityIdPlugin($values['entity_id_plugin']) - // Add only non-empty values. - ->setGraphs(array_filter($values['graph'])) - ->setMappings($values['base_fields_mapping']) - ->save(); - } - catch (\Exception $exception) { - \Drupal::logger('rdf_entity')->critical(t('Could not save RDF entity mapping'), ['exception' => $exception]); - throw $exception; - } + $mapping + ->setRdfType($values['rdf_type']) + ->setEntityIdPlugin($values['entity_id_plugin']) + // Add only non-empty values. + ->setGraphs(array_filter($values['graph'])) + ->setMappings($values['base_fields_mapping']) + ->save(); } /** diff --git a/src/Entity/RdfEntityMapping.php b/src/Entity/RdfEntityMapping.php index 9c1eed7c..c660361a 100644 --- a/src/Entity/RdfEntityMapping.php +++ b/src/Entity/RdfEntityMapping.php @@ -143,7 +143,10 @@ public function getTargetEntityTypeId(): string { * {@inheritdoc} */ public function getTargetEntityType(): ?ContentEntityTypeInterface { - return $this->entityTypeManager()->getDefinition($this->entity_type_id); + if (!$this->getTargetEntityTypeId()) { + return NULL; + } + return $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId()); } /** @@ -277,21 +280,16 @@ public function calculateDependencies() { /** @var \Drupal\rdf_entity\RdfEntityGraphInterface $graph */ foreach (RdfEntityGraph::loadMultiple(array_keys($this->getGraphs())) as $graph) { - // Add dependency to graph. + // Add dependency to each graph. $this->addDependency($graph->getConfigDependencyKey(), $graph->getConfigDependencyName()); } // Add dependency to the paired bundle entity. if ($entity_type = $this->getTargetEntityType()) { if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { - try { - $bundle_storage = $this->entityTypeManager()->getStorage($bundle_entity_type_id); - if ($bundle_entity = $bundle_storage->load($this->getTargetBundle())) { - $this->addDependency($bundle_entity->getConfigDependencyKey(), $bundle_entity->getConfigDependencyName()); - } - } - catch (\Exception $exception) { - // Fail silently to allow the next graphs to be added. + $bundle_storage = $this->entityTypeManager()->getStorage($bundle_entity_type_id); + if ($bundle_entity = $bundle_storage->load($this->getTargetBundle())) { + $this->addDependency($bundle_entity->getConfigDependencyKey(), $bundle_entity->getConfigDependencyName()); } } } From 477f582c19e92436a11179da7864779c8f1721a3 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Tue, 23 Jan 2018 15:29:02 +0200 Subject: [PATCH 30/30] Add followups to some @todos. --- src/Entity/RdfEntitySparqlStorage.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Entity/RdfEntitySparqlStorage.php b/src/Entity/RdfEntitySparqlStorage.php index 0bd20528..8e2f772a 100644 --- a/src/Entity/RdfEntitySparqlStorage.php +++ b/src/Entity/RdfEntitySparqlStorage.php @@ -260,6 +260,7 @@ protected function loadFromStorage(array $ids, array $graph_ids): ?array { // @todo: We should filter per entity per graph and not load the whole // database only to filter later on. + // @see https://github.com/ec-europa/rdf_entity/issues/19 $ids_string = SparqlArg::serializeUris($ids, ' '); $graphs = $this->getGraphHandler()->getEntityTypeGraphUrisFlatList($this->getEntityTypeId()); $named_graph = ''; @@ -270,6 +271,7 @@ protected function loadFromStorage(array $ids, array $graph_ids): ?array { // @todo Get rid of the language filter. It's here because of eurovoc: // \Drupal\taxonomy\Form\OverviewTerms::buildForm loads full entities // of the whole tree: 7000+ terms in 24 languages is just too much. + // @see https://github.com/ec-europa/rdf_entity/issues/19 $query = <<deserializeGraphResults($results);