merged CER submodule
This commit is contained in:
437
sites/all/modules/contrib/fields/cer/handler.inc
Normal file
437
sites/all/modules/contrib/fields/cer/handler.inc
Normal file
@@ -0,0 +1,437 @@
|
||||
<?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));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user