dcat-8.x-1.x-dev/dcat_export/src/DcatExportService.php
dcat_export/src/DcatExportService.php
<?php
namespace Drupal\dcat_export;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Drupal\dcat\Exception\MissingConfigurationException;
use Drupal\dcat_export\Event\AddResourceEvent;
use Drupal\dcat_export\Event\SerializeGraphEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use EasyRdf_Graph;
use EasyRdf_Resource;
use EasyRdf_Format;
use InvalidArgumentException;
/**
* Class DcatExportService.
*
* @package Drupal\dcat_export
*/
class DcatExportService {
/**
* Config object.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* Entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Event dispatcher object.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* EasyRdf graph object.
*
* @var \EasyRdf_Graph
*/
protected $graph;
/**
* DcatExportService constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface
* Entity type manager service.
*
* @throws \Drupal\dcat\Exception\MissingConfigurationException
* When the module is not configured properly.
*/
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher) {
$this->config = $config_factory->get('dcat_export.settings');
$this->entityTypeManager = $entity_type_manager;
$this->eventDispatcher = $event_dispatcher;
$this->graph = new EasyRdf_Graph();
$this->checkConfiguration();
// Set namespaces according to the DCAT-AP standard.
\EasyRdf_Namespace::set('adms', 'http://www.w3.org/ns/adms#');
\EasyRdf_Namespace::set('dct', 'http://purl.org/dc/terms/');
\EasyRdf_Namespace::delete('dcterms');
\EasyRdf_Namespace::delete('dc');
}
/**
* Export DCAT entities as serialised data.
*
* @param string $format
* The output format.
*
* @return string
* The exported dcat string.
*
* @throws \EasyRdf_Exception
* Thrown if EasyRdf fails in exporting data.
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown if the entity type doesn't exist.
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown if the storage handler couldn't be loaded.
*/
public function export($format) {
// Add Catalog information.
$catalog = $this->addCatalogResource($this->graph);
// Add datasets and their related resources.
foreach ($this->addResources($this->graph, $this->loadDatasetEntities()) as $dataset_resource) {
$this->addResourceSilently($catalog, 'dcat:dataset', $dataset_resource);
}
$format = $this->sanitizeFormat($format);
$rdf_format = EasyRdf_Format::getFormat($format);
// Allow other modules to alter the resource being added to the graph.
$event = new SerializeGraphEvent($this->graph);
$this->eventDispatcher->dispatch('dcat_export.graph.serialize', $event);
return $this->graph->serialise($rdf_format);
}
/**
* Add a value to a resource, only when the value is not empty.
*
* @param \EasyRdf_Resource $resource
* The resource to add the value to.
* @param string $property
* The property name.
* @param mixed $values
* Value as string or array.
* @param string $lang
* The language code.
*
* @return int
* The number of values added.
*/
public function addLiteral(EasyRdf_Resource $resource, $property, $values, $lang = NULL) {
if ($values) {
return $resource->addLiteral($property, $values, $lang);
}
return 0;
}
/**
* Add a resource to another resource without throwing errors when empty.
*
* @param \EasyRdf_Resource $resource1
* The resource to add another resource to.
* @param string $property
* The property name.
* @param string|\EasyRdf_Resource $resource2
* The resource to be the value of the property.
*
* @return int
* The number of values added (1 or 0).
*/
public function addResourceSilently(EasyRdf_Resource $resource1, $property, $resource2) {
if ($resource2) {
return $resource1->addResource($property, $resource2);
}
return 0;
}
/**
* Add resources to the graph and return them as objects.
*
* @param \EasyRdf_Graph $graph
* The RDF graph.
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
* The entities of the same type to transform to RDF resources.
* @param string|null $type
* Set type of resource. If not set, the type will be based on entity type.
*
* @return \EasyRdf_Resource[]
*
* @throws \InvalidArgumentException
* When a resource type has not supporting method.
*/
protected function addResources(EasyRdf_Graph $graph, array $entities, $type = NULL) {
$resources = [];
if (!$entities) {
return $resources;
}
$type = $type ?: reset($entities)->getEntityTypeId();
$method = 'add' . ucfirst(str_replace('dcat_', '', $type)) . 'Resource';
if (!method_exists($this, $method)) {
throw new InvalidArgumentException('The resource type has no supporting add method.');
}
foreach ($entities as $entity) {
$resource = $this->{$method}($graph, $entity);
// Allow other modules to alter the resource.
$event = new AddResourceEvent($resource, $entity);
$this->eventDispatcher->dispatch('dcat_export.resource.add', $event);
$resources[] = $resource;
}
return $resources;
}
/**
* Add catalog information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
*
* @return \EasyRdf_Resource
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown if the entity type doesn't exist.
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown if the storage handler couldn't be loaded.
*/
protected function addCatalogResource(EasyRdf_Graph $graph) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($this->config->get('catalog_uri'), ['dcat:Catalog']);
$this->addLiteral($resource, 'dct:title', $this->config->get('catalog_title'));
$this->addLiteral($resource, 'dct:description', $this->config->get('catalog_description'));
$this->addLiteral($resource, 'dct:issued', new \DateTime((string) $this->config->get('catalog_issued')));
$this->addLiteral($resource, 'dct:modified', new \DateTime($this->lastModified()));
$this->addResourceSilently($resource, 'foaf:homepage', $this->createCustomResource(
$graph,
'foaf:Document',
$this->config->get('catalog_homepage_uri')
));
$this->addResourceSilently($resource, 'dct:language', $this->createCustomResource(
$graph,
'dct:LinguisticSystem',
$this->config->get('catalog_language_uri')
));
$this->addResourceSilently($resource, 'dct:license', $this->createCustomResource(
$graph,
'dct:LicenseDocument',
$this->config->get('catalog_license_uri')
));
$this->addResourceSilently($resource, 'dct:publisher', $this->createCustomResource(
$graph,
'foaf:Agent',
$this->config->get('catalog_publisher_uri'),
['literals' => ['foaf:name' => $this->config->get('catalog_publisher_name')]]
));
return $resource;
}
/**
* Add dataset information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param \Drupal\Core\Entity\ContentEntityInterface $dataset
* The dataset entity.
*
* @return \EasyRdf_Resource
* The created RDF resource.
*
* @throws \InvalidArgumentException
* When a resource type is not supported.
*/
protected function addDatasetResource(EasyRdf_Graph $graph, ContentEntityInterface $dataset) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($this->getDatasetUrl($dataset), ['dcat:Dataset']);
$this->addLiteral($resource, 'dct:title', $dataset->label());
$this->addLiteral($resource, 'dct:description', $dataset->get('description')->getString());
$this->addLiteral($resource, 'dct:identifier', $dataset->uuid());
$this->addResourceSilently($resource,'dct:accrualPeriodicity', $dataset->get('accrual_periodicity')->getString());
$this->addLiteral($resource, 'dct:issued', new \DateTime($dataset->get('issued')->getString()));
$this->addLiteral($resource, 'dct:modified', new \DateTime($dataset->get('modified')->getString()));
$this->addResourceSilently($resource, 'dcat:landingPage', $this->getDatasetUrl($dataset));
foreach ($this->addResources($this->graph, $dataset->get('contact_point')->referencedEntities()) as $vcard_resource) {
$this->addResourceSilently($resource, 'dcat:contactPoint', $vcard_resource);
}
foreach ($this->addResources($this->graph, $dataset->get('publisher')->referencedEntities()) as $agent_resource) {
$this->addResourceSilently($resource, 'dcat:publisher', $agent_resource);
}
foreach ($dataset->get('keyword')->referencedEntities() as $keyword) {
$this->addLiteral($resource, 'dcat:keyword', $keyword->label());
}
foreach ($this->addResources($graph, $dataset->get('theme')->referencedEntities(), 'theme') as $theme_resource) {
$this->addResourceSilently($resource, 'dcat:theme', $theme_resource);
}
foreach ($this->addResources($graph, $dataset->get('distribution')->referencedEntities()) as $distribution_resource) {
$this->addResourceSilently($resource, 'dcat:distribution', $distribution_resource);
}
return $resource;
}
/**
* Add vcard information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param \Drupal\Core\Entity\ContentEntityInterface $vcard
* The vcard entity.
*
* @return \EasyRdf_Resource
*/
protected function addVcardResource(EasyRdf_Graph $graph, ContentEntityInterface $vcard) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($vcard->get('external_id')->getString(), ['vcard:Kind']);
$this->addLiteral($resource, 'vcard:hasFN', $vcard->label());
switch ($vcard->bundle()) {
case 'individual':
$this->addLiteral($resource, 'vcard:hasNickname', $vcard->get('nickname')->getString());
case 'organization':
$email = $vcard->get('email')->getString();
$telephone = $vcard->get('telephone')->getString();
$this->addResourceSilently($resource, 'vcard:hasEmail', $email ? 'mailto:' . $email : '');
$this->addResourceSilently($resource, 'vcard:hasTelephone', $telephone ? 'tel:' . $telephone : '');
$this->addResourceSilently($resource, 'vcard:hasURL', $vcard->get('external_id')->getString());
break;
case 'location':
$this->addLiteral($resource, 'vcard:hasStreetAddress', $vcard->get('street_address')->getString());
$this->addLiteral($resource, 'vcard:hasPostalCode', $vcard->get('postal_code')->getString());
$this->addLiteral($resource, 'vcard:hasLocality', $vcard->get('locality')->getString());
$this->addLiteral($resource, 'vcard:hasRegion', $vcard->get('region')->getString());
$this->addLiteral($resource, 'vcard:hasCountryName', $vcard->get('county')->getString());
break;
}
return $resource;
}
/**
* Add agent information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param \Drupal\Core\Entity\ContentEntityInterface $agent
* The agent entity.
*
* @return \EasyRdf_Resource
*/
protected function addAgentResource(EasyRdf_Graph $graph, ContentEntityInterface $agent) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($agent->get('external_id')->getString(), ['foaf:Agent']);
$this->addLiteral($resource, 'foaf:name', $agent->label());
return $resource;
}
/**
* Add distribution information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param \Drupal\Core\Entity\ContentEntityInterface $distribution
* The distribution entity.
*
* @return \EasyRdf_Resource
*/
protected function addDistributionResource(EasyRdf_Graph $graph, ContentEntityInterface $distribution) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($distribution->get('external_id')->getString(), ['dcat:Distribution']);
$this->addResourceSilently($resource, 'dcat:accessURL', $distribution->get('access_url')->getString());
$this->addResourceSilently($resource, 'dcat:downloadURL', $distribution->get('download_url')->getString());
$this->addLiteral($resource,'dct:title', $distribution->label());
$this->addLiteral($resource, 'dct:description', $distribution->get('description')->getString());
$this->addLiteral($resource, 'dcat:mediaType', $distribution->get('media_type')->getString());
$this->addLiteral($resource, 'dct:issued', new \DateTime($distribution->get('issued')->getString()));
$this->addLiteral($resource, 'dct:license', $distribution->get('license')->getString());
$this->addLiteral($resource, 'dct:format', $distribution->get('format')->getString());
$this->addLiteral($resource, 'dcat:byteSize', $distribution->get('byte_size')->getString());
$this->addResourceSilently($resource, 'adms:status', $distribution->get('dcat_status')->getString());
$this->addLiteral($resource, 'dcat:rights', $this->createCustomResource(
$graph,
'dct:RightsStatement',
$distribution->get('rights')->getString()
));
return $resource;
}
/**
* Add theme information to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param \Drupal\Core\Entity\ContentEntityInterface $theme
* The theme entity.
*
* @return \EasyRdf_Resource
*/
protected function addThemeResource(EasyRdf_Graph $graph, ContentEntityInterface $theme) {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($theme->get('external_id')->getString(), ['dcat:Theme']);
$this->addLiteral($resource, 'dct:label', $theme->label());
return $resource;
}
/**
* Add a custom resource to the RDF graph.
*
* @param \EasyRdf_Graph $graph
* The graph object.
* @param string $type
* The type of the resource.
* @param string $uri
* The URI of the resource.
* @param array $properties
* An array containing optional literals and resources with property and
* value as key => value respectively.
* E.g.:
* [
* 'literals' => [
* 'rdf:value' => 'foo',
* ...
* ],
* 'resources' => [
* 'dct:LicenseDocument' => 'https://foo.bar',
* ...
* ],
* ]
*
* @return \EasyRdf_Resource|false
* The RDF resource object or false if not able to create it.
*/
protected function createCustomResource(EasyRdf_Graph $graph, $type, $uri, array $properties = []) {
try {
/** @var \EasyRdf_Resource $resource */
$resource = $graph->resource($uri, [$type]);
// Merge in defaults.
$properties += [
'literals' => [],
'resources' => [],
];
foreach ($properties['literals'] as $property => $value) {
$this->addLiteral($resource, $property, $value);
}
foreach ($properties['resources'] as $property => $value) {
$this->addResourceSilently($resource, $property, $value);
}
return $resource;
}
catch (InvalidArgumentException $ex) {
return FALSE;
}
}
/**
* Get the node page of a dataset.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $dataset
* The dataset entity.
*
* @return string
* The node page full url.
*/
protected function getDatasetUrl(ContentEntityInterface $dataset) {
$url = Url::fromUri('internal:/dataset/' . $dataset->id(), ['absolute' => TRUE])->toString();
return $url;
}
/**
* Get the landing page of a dataset.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $dataset
* The dataset entity.
*
* @return string
* The landing page full url.
*/
protected function getLandingPage(ContentEntityInterface $dataset) {
$url = $dataset->get('landing_page')->getString();
if (!$url) {
$url = Url::fromRoute('view.dataset_landingpage.page', ['arg_0' => $dataset->id()], ['absolute' => TRUE])->toString();
}
return $url;
}
/**
* Get the date of the latest modified dataset in ISO 8601 format.
*
* @return string|false
* The modified date of the latest changed dataset or false when no dataset
* found.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown if the entity type doesn't exist.
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown if the storage handler couldn't be loaded.
*/
protected function lastModified() {
$storage = $this->entityTypeManager->getStorage('dcat_dataset');
$ids = $storage
->getQuery()
->condition('status', 1)
->sort('changed', 'DESC')
->range(0, 1)
->execute();
if ($ids) {
$id = reset($ids);
$entity = $storage->load($id);
return date('c', $entity->get('changed'));
}
return FALSE;
}
/**
* Load active DCAT dataset entities.
*
* @return \Drupal\Core\Entity\ContentEntityInterface[]
* Active DCAT dataset entities.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown if the entity type doesn't exist.
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown if the storage handler couldn't be loaded.
*/
protected function loadDatasetEntities() {
$storage = $this->entityTypeManager->getStorage('dcat_dataset');
$query = $storage
->getQuery()
->condition('status', 1)
->sort('source', 'ASC');
if ($sources = array_filter($this->config->get('sources'))) {
$query->condition('source', $sources, 'IN');
}
$ids = $query->execute();
return $storage->loadMultiple($ids);
}
/**
* Set the format in right form suited for the EasyRdf library.
*
* @param string $format
* The output format.
*
* @return string
* The sanitized format.
*/
protected function sanitizeFormat($format) {
switch ($format) {
case 'rdf':
$format = 'rdfxml';
break;
case 'ttl':
$format = 'turtle';
break;
case 'nt':
$format = 'ntriples';
break;
}
return $format;
}
/**
* Check if the required export settings are all set.
*
* @throws \Drupal\dcat\Exception\MissingConfigurationException
* When configuration is missing.
*/
protected function checkConfiguration() {
if (empty($this->config->get('catalog_title')) ||
empty($this->config->get('catalog_description')) ||
empty($this->config->get('catalog_uri')) ||
empty($this->config->get('catalog_language_uri')) ||
empty($this->config->get('catalog_homepage_uri')) ||
empty($this->config->get('catalog_issued')) ||
empty($this->config->get('catalog_publisher_uri')) ||
empty($this->config->get('catalog_publisher_name')) ||
empty($this->config->get('catalog_license_uri'))
) {
throw new MissingConfigurationException('Required configuration is missing for the dcat_export module.');
}
}
}
