$preset))); } $this->local = field_info_instance($keys[0], $keys[2], $keys[1]); if ($this->local) { $this->local['field'] = field_info_field($keys[2]); } else { throw new CerException(t('Local field instance does not exist.')); } $this->remote = field_info_instance($keys[3], $keys[5], $keys[4]); if ($this->remote) { $this->remote['field'] = field_info_field($keys[5]); } else { throw new CerException(t('Remote field instance does not exist.')); } } } /** * @class * Generic CER handler with rudimentary language handling. */ class CerHandler extends CerHandlerBase implements CerHandlerInterface { /** * The local (home) entity. */ protected $entity; /** * The local entity's ID. */ protected $id; /** * Implements CerHandlerInterface::__construct(). */ public function __construct($preset, $entity) { parent::__construct($preset); // If $entity is of the wrong type, entity_extract_IDs() // will throw EntityMalformedException here. $extract_ids = entity_extract_IDs($this->local['entity_type'], $entity); $this->id = array_shift($extract_ids); $this->entity = $entity; } /** * Implements CerHandlerInterface::insert(). */ public function insert() { foreach ($this->getReferencedEntities() as $referenced_entity) { $this->reference($referenced_entity); _cer_update($this->remote['entity_type'], $referenced_entity); } } /** * Implements CerHandlerInterface::update(). */ public function update() { $original = isset($this->entity->original) ? $this->entity->original : $this->entity; $deleted = array_diff($this->getReferenceIDs($original, $this->local), $this->getLocalReferenceIDs()); if ($deleted) { $entities = entity_load($this->remote['entity_type'], $deleted); foreach ($entities as $referenced_entity) { $this->dereference($referenced_entity); _cer_update($this->remote['entity_type'], $referenced_entity); } } $this->insert(); } /** * Implements CerHandlerInterface::delete(). */ public function delete() { foreach ($this->getReferencedEntities() as $referenced_entity) { $this->dereference($referenced_entity); _cer_update($this->remote['entity_type'], $referenced_entity); } } /** * Implements CerHandlerInterface::references(). */ public function references($entity) { return in_array($this->getRemoteEntityID($entity), $this->getLocalReferenceIDs()); } /** * Implements CerHandlerInterface::referencedBy(). */ public function referencedBy($entity) { return in_array($this->id, $this->getRemoteReferenceIDs($entity)); } /** * Implements CerHandlerInterface::referenceable(). */ public function referenceable($entity) { $id = $this->getRemoteEntityID($entity); $allowed = array( entityreference_get_selection_handler( $this->local['field'], $this->local, $this->local['entity_type'], $this->entity ) ->validateReferencableEntities(array($id)), entityreference_get_selection_handler( $this->remote['field'], $this->remote, $this->remote['entity_type'], $entity ) ->validateReferencableEntities(array($this->id)), ); return in_array($id, $allowed[0]) && in_array($this->id, $allowed[1]); } /** * Implements CerHandlerInterface::reference(). */ public function reference($entity) { if ($this->referenceable($entity)) { try { $this->addReferenceTo($entity); } catch (CerException $e) { // Fail silently } try { $this->addReferenceFrom($entity); } catch (CerException $e) { // Fail silently } } else { throw new CerException(t('Cannot create invalid reference to remote entity.')); } } /** * Implements CerHandlerInterface::dereference(). */ public function dereference($entity) { if ($this->references($entity)) { $id = $this->getRemoteEntityID($entity); foreach ($this->entity->{$this->local['field_name']} as $language => $references) { foreach ($references as $delta => $reference) { if ($reference['target_id'] == $id) { unset($this->entity->{$this->local['field_name']}[$language][$delta]); } } } } if ($this->referencedBy($entity)) { foreach ($entity->{$this->remote['field_name']} as $language => $references) { foreach ($references as $delta => $reference) { if ($reference['target_id'] == $this->id) { unset($entity->{$this->remote['field_name']}[$language][$delta]); } } } } } /** * Creates a reference to the local entity on the remote entity. Throws CerException * if the local entity is already referenced by the remote entity, or if the remote * field cannot hold any more values. * * @param object $entity * The remote entity. */ protected function addReferenceFrom($entity) { if ($this->referencedBy($entity)) { throw new CerException(t('Cannot create duplicate reference from remote entity.')); } elseif ($this->filled($this->getRemoteReferenceIDs($entity), $this->remote['field'])) { throw new CerException(t('Remote field cannot support any more references.')); } else { $languages = field_available_languages($this->remote['entity_type'], $this->remote['field']); foreach ($languages as $language) { $entity->{$this->remote['field_name']}[$language][] = array('target_id' => $this->id); } } } /** * Creates a reference to the remote entity on the local entity. Throws CerException * if the local entity already references the remote entity, or if the field cannot * hold any more values. * * @param object $entity * The remote entity. */ protected function addReferenceTo($entity) { $id = $this->getRemoteEntityID($entity); if ($this->references($entity)) { throw new CerException(t('Cannot create duplicate reference to remote entity.')); } elseif ($this->filled($this->getLocalReferenceIDs(), $this->local['field'])) { throw new CerException(t('Local field cannot support any more references.')); } else { $languages = field_available_languages($this->local['entity_type'], $this->local['field']); foreach ($languages as $language) { $this->entity->{$this->local['field_name']}[$language][] = array('target_id' => $id); } } } /** * Get the ID of the remote entity. If the entity is of the wrong type, * EntityMalformedException will be thrown. * * @param object $entity * The remote entity. * * @return mixed * The remote entity ID. */ protected function getRemoteEntityID($entity) { $extract_ids = entity_extract_IDs($this->remote['entity_type'], $entity); return array_shift($extract_ids); } /** * Gets all the entities referenced by the local entity. * * @return array * Array of fully loaded referenced entities keyed by ID, or empty * array if nothing has been referenced. */ protected function getReferencedEntities() { $IDs = $this->getLocalReferenceIDs(); return $IDs ? entity_load($this->remote['entity_type'], $IDs) : array(); } /** * Gets the IDs of the entities referenced by the local entity. * * @return array * Array of entity IDs, empty array if there are no references. */ protected function getLocalReferenceIDs() { return $this->getReferenceIDs($this->entity, $this->local); } /** * Gets the IDs of the entities referenced by $entity. * * @param object $entity * The remote entity. * * @return array * Array of entity IDs, empty array if there are no references. */ protected function getRemoteReferenceIDs($entity) { return $this->getReferenceIDs($entity, $this->remote); } /** * Check if a field can support any more values. Formerly known as * "reference overloading". * * @param array $references * The values in the field. * * @param $field * Field definition (i.e., from field_info_field). * * @return boolean */ private function filled($references, $field) { return $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && sizeof($references) >= $field['cardinality']; } /** * Gets all the referenced entity IDs from a specific field on $entity. * * @param object $entity * The entity to scan for references. * * @param array $field * Field or instance definition. * * @return array * Array of unique IDs, empty if there are no references or the field * does not exist on $entity. */ private function getReferenceIDs($entity, $field) { $IDs = array(); if (isset($entity->{$field['field_name']})) { foreach ($entity->{$field['field_name']} as $references) { foreach ($references as $reference) { $IDs[] = $reference['target_id']; } } } return array_unique(array_filter($IDs)); } }