437 lines
12 KiB
PHP
437 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Contains base code for CER handlers, which are objects responsible for
|
|
* creating, updating and deleting corresponding references between entities.
|
|
*/
|
|
|
|
/**
|
|
* Exception related to CER operations.
|
|
*/
|
|
class CerException extends Exception {
|
|
}
|
|
|
|
interface CerHandlerInterface {
|
|
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @param string $preset
|
|
* The CER preset string, in the format:
|
|
* entity_a*bundle_a*field_a*entity_b*bundle_b*field_b.
|
|
*
|
|
* @param $entity.
|
|
* The local (home) entity to be wrapped by this instance.
|
|
*/
|
|
public function __construct($preset, $entity);
|
|
|
|
/**
|
|
* Create reciprocal references on referenced entities after the
|
|
* local entity has been created.
|
|
*/
|
|
public function insert();
|
|
|
|
/**
|
|
* Delete reciprocal references on entities the local entity is no
|
|
* longer referencing, and create new reciprocal references, after
|
|
* the local entity has been updated.
|
|
*/
|
|
public function update();
|
|
|
|
/**
|
|
* Delete all reciprocal references after the local entity is deleted.
|
|
*/
|
|
public function delete();
|
|
|
|
/**
|
|
* Check if $entity is referenced by the local entity.
|
|
*
|
|
* @param object $entity
|
|
* The remote entity.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function references($entity);
|
|
|
|
/**
|
|
* Check if the local entity is referenced by $entity.
|
|
*
|
|
* @param object $entity
|
|
* The remote entiy.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function referencedBy($entity);
|
|
|
|
/**
|
|
* Check if the remote entity can reference the local entity, and vice-versa.
|
|
*
|
|
* @param object $entity
|
|
* The remote entity.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function referenceable($entity);
|
|
|
|
/**
|
|
* Create a reference to the local entity on the remote entity, and vice-versa
|
|
* if needed. Should throw CerException if the reference(s) can't be created
|
|
* for any reason.
|
|
*
|
|
* @param object $entity
|
|
*/
|
|
public function reference($entity);
|
|
|
|
/**
|
|
* Delete all references to the remote entity from the local entity,
|
|
* and delete reciprocal references from the remote entity.
|
|
*
|
|
* @param object $entity.
|
|
*/
|
|
public function dereference($entity);
|
|
|
|
}
|
|
|
|
/**
|
|
* @class
|
|
* Base class for CER handlers. All this does is parse the preset
|
|
* and store instance info about the local and remote fields.
|
|
*/
|
|
abstract class CerHandlerBase {
|
|
|
|
/**
|
|
* Local field instance definition.
|
|
*/
|
|
protected $local;
|
|
|
|
/**
|
|
* Remote field instance definition.
|
|
*/
|
|
protected $remote;
|
|
|
|
public function __construct($preset) {
|
|
$keys = explode('*', $preset);
|
|
|
|
if (sizeof($keys) != 6) {
|
|
throw new CerException(t('Invalid configuration: @preset', array('@preset' => $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));
|
|
}
|
|
|
|
} |