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.'); } } }