/** * Adds a relationship to the running list of relationships. * * @param Entity $source The source entity. * @param Entity $destination The destination entity that the source entity relates to. * @param string $field_name The field on the source entity that the relationship belongs to. * @param int $delta The delta the relationship belongs to. * @param array $handler_arguments Any arguments to pass to the handler (like the field name). * @param bool $source_vuuid The VUUID of the source (if false, defaults to the current * VUUID). * @param bool $destination_vuuid The VUUID of the destination (if false, the VUUID of the * destination isn't taken into account). */ protected function addRelationship(Entity $source, Entity $destination, $field_name, $delta = 0, array $handler_arguments = array(), $source_vuuid = false, $destination_vuuid = false) { // Get the relationship handler name. $handler = $this->getRelationshipHandlerName(); // Generate the fields for the relationship. $record = array(); $record['source_type'] = $source->type(); $record['source_uuid'] = $source->uuid(); $record['destination_type'] = $destination->type(); $record['destination_uuid'] = $destination->uuid(); $record['field_name'] = $field_name; $record['delta'] = $delta; // Add the source_vuuid column. if ($source->supportsRevisions()) { $record['source_vuuid'] = $source_vuuid === false ? $source->vuuid() : $source_vuuid; } else { $record['source_vuuid'] = false; } // Add the destination vuuid column. $record['destination_vuuid'] = $destination->supportsRevisions() ? $destination_vuuid : false; // Add the relationship handler and arguments. $record['relationship_handler'] = $handler; $record['relationship_arguments'] = $handler_arguments; // Check to see if the relationship already exists... foreach ($this->relationships as $relationship) { if ($record['source_type'] != $relationship['source_type']) { continue; } if ($record['source_uuid'] != $relationship['source_uuid']) { continue; } if ($record['source_vuuid'] != $relationship['source_vuuid']) { continue; } if ($record['destination_type'] != $relationship['destination_type']) { continue; } if ($record['destination_uuid'] != $relationship['destination_uuid']) { continue; } if ($record['destination_vuuid'] != $relationship['destination_vuuid']) { continue; } if ($record['relationship_handler'] != $relationship['relationship_handler']) { continue; } if (count(array_diff($record['relationship_arguments'], $relationship['relationship_arguments'])) !== 0) { continue; } return; // The relationship already exists... } // Add the relationship to the list. $this->relationships[] = $record; }
public function diff() { // Get the preparer registry. $preparer_registry = new PreparerRegistry(); if (!$this->revisions_table) { // Prepare the entity. $preparer_registry->beforeSend($this->entity); $resolver = new Resolver($this->entity); return array($this->entity->vuuid() => array('additions' => $resolver->resolvedDefinition(), 'deletions' => array())); } // Get the revisions in between the two mentioned. If the old is blank, get the entire history. $revision_ids = $this->getRevisionHistory($this->old_revision_id, $this->new_revision_id); if ($revision_ids === false) { return false; } $payload = array(); $before_id = $this->old_revision_id ? $this->old_revision_id : -1; foreach ($revision_ids as $current_id) { // Load the entities. if ($before_id === -1) { $old_entity = null; } else { $old_entity = clone $this->entity; $old_entity->setRevision($before_id); } $new_entity = clone $this->entity; $new_entity->setRevision($current_id); // Pass the entity through the preparers to add any additional field metadata. if ($old_entity !== null) { $preparer_registry->beforeSend($old_entity); } $preparer_registry->beforeSend($new_entity); $payload[$current_id . '|' . $new_entity->vuuid()] = $this->singleDiff($old_entity, $new_entity); // Update the before ID. $before_id = $current_id; } return $payload; }
/** * Create reference definition. * * Creates an entity reference definition, storing the UUID, VUUID and entity * type for the provided entity. * * @param Entity $entity The entity to generate the definition for. * * @return array The generated definition. */ public static function createReferenceDefinition(Entity $entity) { $value = array(); $value['uuid'] = $entity->uuid(); $value['vuuid'] = $entity->vuuid(); $value['entity_type'] = $entity->type(); $value['original'] = $entity->id(); $value['original_revision'] = $entity->revision(); $value['revisions'] = Entity::getAllRevisions($entity->id(), $entity->type()); return $value; }
public function resolveDependencies($recurse = true, $subset = false, $child = false, $subtype = false) { // Check to see if the resolved definition already exists in the cached list. $cached_resolutions =& drupal_static('publisher_cached_resolutions', array()); $key = $this->base_entity->uuid() . '|' . $this->base_entity->type(); if ($this->base_entity->supportsRevisions()) { $key .= '|' . $this->base_entity->vuuid(); } $key .= $recurse ? '|recurse' : ''; if (array_key_exists($key, $cached_resolutions) && $child === false && $subset === false) { $this->dependencies = array_replace_recursive($this->dependencies, $cached_resolutions[$key]['dependencies']); $this->relationships = array_merge($this->relationships, $cached_resolutions[$key]['relationships']); $this->resolved_definition = $cached_resolutions[$key]['resolved_definition']; return; } if (count($cached_resolutions) > 200) { // For memory consumption purposes. drupal_static_reset('publisher_cached_resolutions'); } // Create a clone of the definition so we don't muck up the entity cache. $this->resolved_definition = $subset !== false ? $subset : clone $this->entity->definition; if (!is_object($this->resolved_definition)) { $this->resolved_definition = (object) $this->resolved_definition; } // Generate the list of handlers for each of the fields. $handlers = DefinitionHandlerRegistry::getFieldHandlers($this->entity, $this->resolved_definition, $subtype); foreach ($handlers as $field_name => $handler) { if (!isset($this->resolved_definition->{$field_name})) { continue; } foreach ($handler['handlers'] as $single_handler) { if (!$single_handler instanceof DefinitionHandlerBase) { continue; } try { $single_handler->dependencies =& $this->dependencies; $single_handler->entity =& $this->entity; $single_handler->original_entity = $this->base_entity; if (property_exists($single_handler, 'relationships')) { $single_handler->relationships =& $this->relationships; } $single_handler->handleField($this->entity->type(), $handler['type'], $field_name, $this->resolved_definition->{$field_name}); } catch (\Exception $ex) { $message = t('Error processing field "@fieldName" - "@message"', array('@fieldName' => $field_name, '@message' => $ex->getMessage())); \watchdog('publisher', $message, array(), WATCHDOG_WARNING); $this->errors[] = $ex; } } } // Now, recursion. if ($recurse) { foreach ($this->dependencies as $identifier => $dependency) { // If the dependency has already been handled, skip it. if (array_key_exists($identifier, self::$handled_dependencies)) { continue; } // Add the dependency to the list of handled ones. self::$handled_dependencies[$identifier] = $dependency; // Load the entity and get its dependencies. $entity = Entity::loadByUUID($dependency['uuid'], $dependency['entity_type']); // If for some reason the entity fails, we need to just skip it. if (!$entity) { continue; } // Make sure the entity is not the current entity to prevent infinite loop. if ($entity->uuid() == $this->entity->uuid()) { continue; } $resolver = new self($entity, false, $this->dependencies); $resolver->relationships =& $this->relationships; $resolver->metadata =& $this->metadata; $resolver->resolveDependencies(true, false, true); $this->dependencies = $resolver->dependencies(); } } // Add a dependency for the entity itself if it doesn't already exist. if (!array_key_exists($this->base_entity->uuid(), $this->dependencies) || $this->base_entity->supportsRevisions() && !empty($this->dependencies[$this->base_entity->uuid()]['original_revision']) && $this->base_entity->revision() > $this->dependencies[$this->base_entity->uuid()]['original_revision']) { $this->dependencies[$this->base_entity->uuid()] = HandlerBase::createReferenceDefinition($this->base_entity); } // Pass the entity through the entity handlers. if (!array_key_exists($this->base_entity->uuid(), $this->metadata) && $recurse) { $this->metadata[$this->base_entity->uuid()] = array(); foreach (EntityHandlerRegistry::getEntityHandlers($this->base_entity) as $handler) { if (!$handler instanceof EntityHandlerBase) { continue; } $handler->entity =& $this->entity; $handler->original_entity = $this->base_entity; $handler->dependencies =& $this->dependencies; $handler->handleEntity($this->metadata[$this->base_entity->uuid()]); } } // Update the cache record. $cached_resolutions[$key] = array('dependencies' => $this->dependencies, 'relationships' => $this->relationships, 'resolved_definition' => $this->resolved_definition); if (!$child) { $dependency_dependencies =& drupal_static('publisher_dependency_dependencies', array()); foreach ($dependency_dependencies as $source => $dependencies) { if (array_key_exists($source, $this->dependencies)) { if (!array_key_exists('dependencies', $this->dependencies[$source]) || !is_array($this->dependencies[$source]['dependencies'])) { $this->dependencies[$source]['dependencies'] = array(); } $this->dependencies[$source]['dependencies'] = array_replace_recursive($this->dependencies[$source]['dependencies'], $dependencies); $this->dependencies[$source]['dependencies'] = array_unique($this->dependencies[$source]['dependencies']); } } // Update the relationships. $this->updateRelationships(); // Loop through all the dependencies and perform cleanup tasks. foreach ($this->dependencies as $dependency_key => &$dependency) { // Cleanup the sources. if (array_key_exists('sources', $dependency) && is_array($dependency['sources'])) { $dependency['sources'] = array_unique($dependency['sources']); } } // Loop through again for the required stuff. foreach ($this->dependencies as $dependency_key => &$dependency) { // Create the 'required' key. if (array_key_exists('has_relationship', $dependency) && $dependency['has_relationship']) { $dependency['required'] = false; continue; } $this->dependencies[$dependency_key]['required'] = true; if (array_key_exists('sources', $this->dependencies[$dependency_key]) && is_array($this->dependencies[$dependency_key]['sources']) && count($this->dependencies[$dependency_key]['sources']) > 0) { // Mark dependencies as not required only if all of their parent paths // are marked as having relationships. $sources = array(); foreach ($this->dependencies[$dependency_key]['sources'] as $source_uuid) { $sources[] = $this->dependencies[$source_uuid]; } $paths = $this->getAllParentTrails($sources); $paths_with_notrequired = 0; foreach ($paths as $path) { foreach ($path as $item) { if (!array_key_exists('required', $this->dependencies[$item]) || !$this->dependencies[$item]['required']) { $paths_with_notrequired++; break; } } } if ($paths_with_notrequired == count($paths)) { $this->dependencies[$dependency_key]['required'] = false; } } else { // If the dependency doesn't have any sources, it is not // required. $this->dependencies[$dependency_key]['required'] = false; } } // Now, loop through the dependencies again and fill in their // 'required_if' values. If a dependency is marked as not required, // go through its trails and find the lowest parent that is marked // as having a relationship and add it to the 'required_if' list // if it isn't already there. foreach ($this->dependencies as $dependency_key => &$dependency) { $dependency['required_if'] = array(); // If the dependency is marked as required already, is a relationship, // or doesn't have any sources, skip it. if ($dependency['required']) { continue; } if (!array_key_exists('sources', $dependency) || !is_array($dependency['sources'])) { continue; } // Loop through its parent trails to find the non-required items. $required_if = array(); $sources = array(); foreach ($dependency['sources'] as $source_uuid) { // If the source UUID is the owner of the relationship, don't add it. $skip = false; foreach ($this->relationships as $relationship) { if ($relationship['source_uuid'] == $source_uuid && $relationship['destination_uuid'] == $dependency['uuid']) { $skip = true; break; } } if ($skip) { continue; } $sources[] = $this->dependencies[$source_uuid]; } $paths = $this->getAllParentTrails($sources); foreach ($paths as $path) { $found = false; foreach ($path as $index => $item) { if (array_key_exists('has_relationship', $this->dependencies[$item]) && $this->dependencies[$item]['has_relationship']) { $found = true; $required_if[] = array_slice($path, 0, $index + 1); break; } } if (!$found) { if (count($path) === 1) { $required_if[] = $path; } else { for ($i = 1; $i < count($path); $i++) { $required_if[] = array_slice($path, 0, $i); } } } } // Add it to the dependency. $required_if_entities = array(); foreach ($required_if as $path) { $required_if_entities[] = end($path); } $dependency['required_if'] = array_unique($required_if_entities); } // Reset the dependency dependencies cache. drupal_static_reset('publisher_dependency_dependencies'); // Finally, perform the topological sort on the dependencies. $this->topologicalSortDependencies(); } }