content_deploy-1.0.1/src/DependencyResolver/ImportQueueResolver.php
src/DependencyResolver/ImportQueueResolver.php
<?php
namespace Drupal\content_deploy\DependencyResolver;
use Drupal\Core\Serialization\Yaml;
/**
* Class ImportQueueResolver.
*
* @package Drupal\content_deploy\DependencyResolver
*/
class ImportQueueResolver implements ContentSyncResolverInterface {
/**
* Builds a graph placing the deepest vertexes at the first place.
*
* @param array $visited
* Array of vertexes to return.
* @param array $identifiers
* Array of entity identifiers to process.
* @param array $normalized_entities
* Parsed entities to import.
*/
protected function depthFirstSearch(array &$visited, array $identifiers, array $normalized_entities) {
foreach ($identifiers as $identifier) {
// Get a decoded entity. FALSE means no need to import.
try {
$entity = $this->getEntity($identifier, $normalized_entities);
} catch (\Exception $e) {
$entity = FALSE;
$visited['Missing'][$identifier][] = $e->getMessage();
}
// We already visited that entity, but update it again.
if (isset($visited[$identifier])) {
// Add the entity again if it has back reference.
if ($this->hasBackReference($visited, $identifier)) {
// Remove the previous added entity to move it to the end of the list,
// in case we have several referenced entities.
unset($visited['duplicate.' . $identifier]);
list($entity_type_id, $bundle, $uuid) = explode('.', $identifier);
$visited['duplicate.' . $identifier] = [
'entity_type_id' => $entity_type_id,
'decoded_entity' => $entity
];
}
return;
}
// Process dependencies first.
if (!empty($entity['_content_deploy']['entity_dependencies'])) {
foreach ($entity['_content_deploy']['entity_dependencies'] as $ref_entity_type_id => $references) {
// Add the entity to the visited list, so we avoid infinite loops.
$visited[$identifier] = [
'entity_type_id' => $ref_entity_type_id,
'decoded_entity' => $entity
];
$this->depthFirstSearch($visited, $references, $normalized_entities);
}
}
// Process translations' dependencies if any.
if (!empty($entity["_translations"])) {
foreach ($entity["_translations"] as $translation) {
if (!empty($translation['_content_deploy']['entity_dependencies'])) {
foreach ($translation['_content_deploy']['entity_dependencies'] as $ref_entity_type_id => $references) {
$this->depthFirstSearch($visited, $references, $normalized_entities);
}
}
}
}
if (!isset($visited[$identifier]) && $entity) {
list($entity_type_id, $bundle, $uuid) = explode('.', $identifier);
$visited[$identifier] = [
'entity_type_id' => $entity_type_id,
'decoded_entity' => $entity,
];
}
}
}
/**
* Check if the entity has back reference.
*
* @param array $visited
* Array of vertexes to return.
* @param $identifier
* An entity identifier to check.
*/
protected function hasBackReference($visited, $identifier) {
if (!isset($visited[$identifier])) {
return FALSE;
}
// Get entity and their dependencies.
$entity = $visited[$identifier];
$entity_dependencies = $entity['decoded_entity']['_content_deploy']['entity_dependencies'];
if (!empty($entity_dependencies)) {
foreach ($entity_dependencies as $dependency_identifiers) {
foreach ($dependency_identifiers as $dependency_identifier) {
if (!isset($visited[$dependency_identifier])) {
continue;
}
// Try to find a back reference.
$dependency_entity = $visited[$dependency_identifier];
$dependency_entity_dependencies = $dependency_entity['decoded_entity']['_content_deploy']['entity_dependencies'];
foreach ($dependency_entity_dependencies as $identifiers) {
if (in_array($identifier, $identifiers)) {
return TRUE;
}
}
}
}
}
return FALSE;
}
/**
* Gets an entity.
*
* @param $identifier
* An entity identifier to process.
* @param $normalized_entities
* An array of entity identifiers to process.
*
* @return bool|mixed
* Decoded entity or FALSE if an entity already exists and doesn't require to be imported.
*
* @throws \Exception
*/
protected function getEntity($identifier, $normalized_entities) {
if (!empty($normalized_entities[$identifier])) {
$entity = $normalized_entities[$identifier];
}
else {
list($entity_type_id, $bundle, $uuid) = explode('.', $identifier);
$file_path = content_deploy_get_content_directory('sync')."/entities/".$entity_type_id."/".$bundle."/".$identifier.".yml";
$raw_entity = file_get_contents($file_path);
// Problems to open the .yml file.
if (!$raw_entity) throw new \Exception("Dependency {$identifier} is missing.");
$entity = Yaml::decode($raw_entity);
}
return $entity;
}
/**
* Checks if a dependency exists in the site.
*
* @param $identifier
* An entity identifier to process.
*
* @return bool
*/
protected function entityExists($identifier) {
return (bool) \Drupal::database()->queryRange('SELECT 1 FROM {cs_db_snapshot} WHERE name = :name', 0, 1, [
':name' => $identifier])->fetchField();
}
/**
* Creates a queue.
*
* @param array $normalized_entities
* Parsed entities to import.
*
* @return array
* Queue to be processed within a batch process.
*/
public function resolve(array $normalized_entities, $visited = []) {
$visited = [];
foreach ($normalized_entities as $identifier => $entity) {
$this->depthFirstSearch($visited, [$identifier], $normalized_entities);
}
// Reverse the array to adjust it to an array_pop-driven iterator.
return array_reverse($visited);
}
}
