updated some more modules
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains CerEndPointIterator.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* The purpose of this iterator is to wrap around all the "endpoints" in a field chain.
|
||||
* An endpoint is a CerFieldHandler for a field that hasn't got a child. This is necessary
|
||||
* in order to support infinite levels of embedded entities (read: field collections).
|
||||
* This class is only instantiated by CerFieldChainHandler if its initial field handler
|
||||
* has a child (@see CerFieldHandler::__construct()).
|
||||
*/
|
||||
class CerEndPointIterator implements RecursiveIterator {
|
||||
|
||||
/**
|
||||
* @var CerField
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* @var CerFieldHandler
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
public function __construct(CerField $field, EntityDrupalWrapper $entity) {
|
||||
$this->field = $field;
|
||||
$this->handler = $field->getHandler($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::current().
|
||||
*/
|
||||
public function current() {
|
||||
return $this->field->child()->getHandler($this->handler->current());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::key().
|
||||
*/
|
||||
public function key() {
|
||||
return $this->handler->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::next().
|
||||
*/
|
||||
public function next() {
|
||||
$this->handler->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::rewind().
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->handler->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::handler().
|
||||
*/
|
||||
public function valid() {
|
||||
return $this->handler->valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements RecursiveIterator::hasChildren().
|
||||
*/
|
||||
public function hasChildren() {
|
||||
return ($this->field->child() instanceof CerEntityContainerInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements RecursiveIterator::getChildren().
|
||||
*/
|
||||
public function getChildren() {
|
||||
return new CerEndPointIterator($this->field->child(), $this->handler->current());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* This interface should be implemented by field plugins which can create embedded
|
||||
* entities on the fly.
|
||||
*
|
||||
* The prime example is field collections. CER might need to add a backreference on
|
||||
* a field which is in a field collection, yet there might be no field collection items
|
||||
* on which to add the reference. In that case, a new field collection item must be
|
||||
* be created and referenced properly by the owner.
|
||||
*/
|
||||
interface CerEntityContainerInterface {
|
||||
|
||||
/**
|
||||
* Creates an embedded entity.
|
||||
*
|
||||
* @return EntityDrupalWrapper
|
||||
* The newly created embedded entity, in a metadata wrapper.
|
||||
*/
|
||||
public function createInnerEntity(EntityDrupalWrapper $owner);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the base class for CER field plugins.
|
||||
*
|
||||
* A field plugin tells CER how to interact with fields of a certain type. If a particular
|
||||
* field type integrates with Entity API, its CER plugin can be as simple as extending
|
||||
* CerField and implementing the getTargetType() method.
|
||||
*
|
||||
* @todo
|
||||
* More info about extending CerFieldHandler to further customize field plugins.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Represents a single field instance.
|
||||
*/
|
||||
abstract class CerField extends FieldInstance {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* The plugin definition.
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $fieldTypeLabel;
|
||||
|
||||
/**
|
||||
* Gets the type of entity that can be referenced by this field, e.g. 'node'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getTargetType();
|
||||
|
||||
/**
|
||||
* Constructor. Pretty self-explanatory!
|
||||
*/
|
||||
public function __construct(array $plugin) {
|
||||
// Store a copy of our plugin definition.
|
||||
$this->plugin = $plugin;
|
||||
|
||||
list ($entity_type, $bundle, $field_name) = explode(':', $plugin['identifier']);
|
||||
parent::__construct($entity_type, $bundle, $field_name);
|
||||
|
||||
// Store a copy of the field settings for convenience. At the time of this
|
||||
// writing, this is needed by the Entity Reference, Node Reference,
|
||||
// and Term Reference plugins.
|
||||
$info = field_info_field($this->name);
|
||||
$this->settings = $info['settings'];
|
||||
|
||||
$type_info = field_info_field_types($info['type']);
|
||||
$this->fieldTypeLabel = $type_info['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CerFieldHandler subclass instance for the given entity.
|
||||
*
|
||||
* @param object $entity
|
||||
* The entity to be wrapped by the handler.
|
||||
*
|
||||
* @return CerFieldHandler
|
||||
*/
|
||||
public function getHandler(EntityDrupalWrapper $entity) {
|
||||
return new $this->plugin['handler']($this, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundles that this field instance can reference.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTargetBundles() {
|
||||
$info = entity_get_info($this->getTargetType());
|
||||
return array_keys($info['bundles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden.
|
||||
*/
|
||||
public function requireParent() {
|
||||
return $this->plugin['require parent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden.
|
||||
*/
|
||||
public function getParents() {
|
||||
return array_map('CerField::getPlugin', $this->plugin['parents']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about a particular field plugin by its identifier, or all
|
||||
* available plugins (i.e., defined by hook_cer_fields()) if no identifier is given.
|
||||
* The aggregated result of hook_cer_fields() is statically cached.
|
||||
*/
|
||||
public static function getPluginInfo($identifier = NULL) {
|
||||
$info = &drupal_static(__METHOD__);
|
||||
|
||||
if (! isset($info)) {
|
||||
$info = module_invoke_all('cer_fields');
|
||||
|
||||
foreach ($info as $key => &$field) {
|
||||
$field += array(
|
||||
'identifier' =>
|
||||
$key,
|
||||
'parents' =>
|
||||
array(),
|
||||
'require parent' =>
|
||||
FALSE,
|
||||
'handler' =>
|
||||
'CerFieldHandler',
|
||||
);
|
||||
}
|
||||
drupal_alter('cer_fields', $info);
|
||||
}
|
||||
|
||||
return ($identifier ? (isset($info[$identifier]) ? $info[$identifier] : NULL) : $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single field plugin instance, by its identifier. All plugin instances
|
||||
* are statically cached.
|
||||
*
|
||||
* @param string $identifier
|
||||
* The plugin's identifier, in the format entity_type:bundle:field_name.
|
||||
*
|
||||
* @return CerField
|
||||
*
|
||||
* @throws Exception if there's no plugin for the given identifier. Why so harsh, you
|
||||
* ask? Because CerFieldChain::unpack() utterly depends on being able to get plugin
|
||||
* instances for every field in the chain, and if it can't, it could result in myriad
|
||||
* weird and serious problems. Throwing an exception will head that off at the pass.
|
||||
*/
|
||||
public static function getPlugin($identifier) {
|
||||
$instances = &drupal_static(__METHOD__);
|
||||
|
||||
if (! isset($instances[$identifier])) {
|
||||
$info = self::getPluginInfo($identifier);
|
||||
if ($info) {
|
||||
$instances[$identifier] = new $info['class']($info);
|
||||
}
|
||||
else {
|
||||
throw new Exception("Cannot get instance of invalid plugin $identifier.");
|
||||
}
|
||||
}
|
||||
|
||||
return $instances[$identifier];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CerFieldChain class.
|
||||
*/
|
||||
|
||||
class CerFieldChain extends FieldChain {
|
||||
|
||||
/**
|
||||
* Convenience method. Returns a handler for this chain in the context of
|
||||
* the given entity.
|
||||
*
|
||||
* @return CerFieldChainHandler
|
||||
*/
|
||||
public function getHandler(EntityDrupalWrapper $entity) {
|
||||
return new CerFieldChainHandler($this, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression to match field chain identifiers that this chain
|
||||
* can reference, e.g. /^node:(page|article):/
|
||||
*/
|
||||
public function regex() {
|
||||
$end = $this->end();
|
||||
return '/^' . $end->getTargetType() . ':(' . implode('|', $end->getTargetBundles()) . '):/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Features export pipe for this chain, including every field and
|
||||
* field instance in it.
|
||||
*/
|
||||
public function export() {
|
||||
$pipe = array();
|
||||
|
||||
foreach ($this->chain as $field) {
|
||||
$pipe['field_instance'][] = "{$field->entityType}-{$field->bundle}-{$field->name}";
|
||||
}
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of every possible field chain for every field defined in
|
||||
* hook_cer_fields().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function collectAll() {
|
||||
$chains = array();
|
||||
|
||||
foreach (array_keys(CerField::getPluginInfo()) as $identifier) {
|
||||
$chains = array_merge($chains, self::collect($identifier));
|
||||
}
|
||||
|
||||
return $chains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of every possible field chain for a single field,
|
||||
* identified by its key in hook_cer_fields().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function collect($identifier) {
|
||||
$chains = array();
|
||||
|
||||
$chain = new CerFieldChain();
|
||||
$chain->addField(CerField::getPlugin($identifier), $chains);
|
||||
|
||||
return $chains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and returns a CerFieldChain object from an encoded string
|
||||
* of field plugin identifiers glued together with ::.
|
||||
*
|
||||
* @return CerFieldChain
|
||||
*/
|
||||
public static function unpack($identifier) {
|
||||
$chain = new CerFieldChain();
|
||||
|
||||
foreach (array_reverse(explode('::', $identifier)) as $field) {
|
||||
$chain->addField(CerField::getPlugin($field));
|
||||
}
|
||||
|
||||
return $chain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CerFieldChainHandler object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Wraps around every CerFieldHandler object in a chain. In any given chain, there
|
||||
* could be many entities that need to be processed -- think about multi-value field
|
||||
* collections embedded within other multi-value field collections, and you quickly
|
||||
* see how extensive the recursion can be. CER needs to be able to handle crazy
|
||||
* scenarios like that and still perform add/delete operations transparently. That's
|
||||
* what this guy does.
|
||||
*/
|
||||
class CerFieldChainHandler {
|
||||
|
||||
/**
|
||||
* @var CerFieldChain
|
||||
*/
|
||||
protected $chain;
|
||||
|
||||
/**
|
||||
* @var EntityDrupalWrapper
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @var array or RecursiveIteratorIterator
|
||||
*/
|
||||
protected $handlers;
|
||||
|
||||
public function __construct(CerFieldChain $chain, EntityDrupalWrapper $entity) {
|
||||
$this->chain = $chain;
|
||||
$this->entity = $entity;
|
||||
|
||||
$chain->__wakeup();
|
||||
$chain->seek($entity->cer->depth->value());
|
||||
|
||||
$field = $chain->current();
|
||||
|
||||
// If the field has a child, there could be extensive recusion here. So we'll need
|
||||
// to iterate over the entire chain recursively -- luckily, SPL provides the
|
||||
// RecursiveIteratorIterator class for this purpose. But if there are no children,
|
||||
// we don't need to recurse; the only handler we'll need to load is the current
|
||||
// field's, for the current entity.
|
||||
if ($field->child()) {
|
||||
$this->handlers = new RecursiveIteratorIterator(new CerEndPointIterator($field, $entity));
|
||||
}
|
||||
else {
|
||||
// Wrap the handler in an array, just to normalize things internally.
|
||||
$this->handlers = array( $field->getHandler($entity) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of every entity referenced in this chain. If there are no references,
|
||||
* an empty array is returned.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIDs() {
|
||||
$IDs = array();
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
$IDs = array_merge($handler->getIDs(), $IDs);
|
||||
}
|
||||
|
||||
return array_unique($IDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reference to the given entity.
|
||||
*/
|
||||
public function add(EntityDrupalWrapper $entity) {
|
||||
$owner = $this->entity;
|
||||
|
||||
foreach ($this->chain as $field) {
|
||||
// If the current field implements CerEntityContainerInterface, we can
|
||||
// create an entity on-the-fly to receive the reference, if there isn't
|
||||
// one already.
|
||||
if ($field instanceof CerEntityContainerInterface) {
|
||||
$items = $field->getHandler($owner);
|
||||
|
||||
// If there is are items which could receive the reference, seek to the
|
||||
// last one. Otherwise, create one.
|
||||
if (sizeof($items) == 0) {
|
||||
$owner = $field->createInnerEntity($owner);
|
||||
}
|
||||
else {
|
||||
$items->seek(-1);
|
||||
$owner = $items->current();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$field->getHandler($owner)->add($entity);
|
||||
}
|
||||
|
||||
public function delete(EntityDrupalWrapper $entity) {
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->delete($entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains CerFieldHandler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Handles low-level operations for a single field on a single entity. Exposes
|
||||
* methods to add, delete and check for references. This will also iterate over
|
||||
* the references, returning each one as an EntityDrupalWrapper object.
|
||||
*/
|
||||
class CerFieldHandler implements Countable, SeekableIterator {
|
||||
|
||||
/**
|
||||
* @var CerField
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* @var EntityDrupalWrapper
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @var EntityMetadataWrapper
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $delta = 0;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isMultiValue;
|
||||
|
||||
public function __construct(CerField $field, EntityDrupalWrapper $entity) {
|
||||
$this->field = $field;
|
||||
$this->entity = $entity;
|
||||
$this->value = $entity->{ $field->name };
|
||||
$this->isMultiValue = ($this->value instanceof EntityListWrapper);
|
||||
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reference to $entity, validating it first.
|
||||
*
|
||||
* @param EntityDrupalWrapper $entity
|
||||
* The wrapped entity to reference.
|
||||
*/
|
||||
public function add(EntityDrupalWrapper $entity) {
|
||||
if ($this->validate($entity)) {
|
||||
$this->write();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all references to $entity.
|
||||
*
|
||||
* @param EntityDrupalWrapper $entity
|
||||
* The wrapped entity to dereference.
|
||||
*/
|
||||
public function delete(EntityDrupalWrapper $entity) {
|
||||
$entityID = $entity->getIdentifier();
|
||||
|
||||
if ($this->isMultiValue) {
|
||||
foreach ($this->value as $delta => $ref) {
|
||||
if ($entityID == $ref->getIdentifier()) {
|
||||
$this->value[$delta]->set(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($entityID == $this->value->getIdentifier()) {
|
||||
$this->value->set(NULL);
|
||||
}
|
||||
|
||||
$this->write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a potential reference. After doing a cardinality check, the
|
||||
* reference is validated through the Field Attach API, allowing the module
|
||||
* which owns the field to do its normal validation logic. If validation
|
||||
* fails, the error(s) are logged.
|
||||
*
|
||||
* @param EntityDrupalWrapper $entity
|
||||
* The wrapped entity to validate.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function validate(EntityDrupalWrapper $entity) {
|
||||
// Before we do anything else, check that the field has enough space to add the
|
||||
// reference. If there isn't, bail out so we don't blindly overwrite existing
|
||||
// field data.
|
||||
if ($this->checkCardinality()) {
|
||||
// Keep the previous value so we can restore it if validation fails.
|
||||
$prev_value = $this->value->value();
|
||||
|
||||
if ($this->isMultiValue) {
|
||||
$value = $this->value->value();
|
||||
$value[] = $entity->value();
|
||||
$this->value->set($value);
|
||||
}
|
||||
else {
|
||||
$this->value->set( $entity->value() );
|
||||
}
|
||||
|
||||
// Leverage the Field Attach API to validate the reference. If errors occur,
|
||||
// field_attach_validate() throws FieldValidationException, containing an array
|
||||
// of every validation error.
|
||||
try {
|
||||
// Only validate this field.
|
||||
field_attach_validate($this->entity->type(), $this->entity->value(), array('field_name' => $this->field->name));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
foreach ($e->errors as $field) {
|
||||
foreach ($field as $language) {
|
||||
foreach ($language as $errors) {
|
||||
foreach ($errors as $error) {
|
||||
$this->logError($error['message'], $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->value->set($prev_value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->logError('Cannot add reference to !that_link from !field_label on !this_link because there are no more slots available.', $entity);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that there are enough slots in the field to add a reference.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function checkCardinality() {
|
||||
return ($this->field->cardinality == FIELD_CARDINALITY_UNLIMITED ? TRUE : ($this->field->cardinality > $this->count()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves changes to the entity and resets the iterator.
|
||||
*/
|
||||
protected function write() {
|
||||
$entity_type = $this->entity->type();
|
||||
$entityID = $this->entity->getIdentifier();
|
||||
$entity = $this->entity->value();
|
||||
|
||||
$entity->cer_processed = TRUE;
|
||||
entity_save($entity_type, $entity);
|
||||
|
||||
// Reload the entity we just saved and cleared from the static cache.
|
||||
$entities = entity_load($entity_type, (array) $entityID);
|
||||
$this->entity->set($entities[$entityID]);
|
||||
|
||||
$this->__construct($this->field, $this->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error, optionally against a specific entity. If the cer_debug
|
||||
* variable is set, the error will also be set as a message.
|
||||
*
|
||||
* @param string $message
|
||||
* The untranslated message to log.
|
||||
*
|
||||
* @param EntityDrupalWrapper $entity
|
||||
* The entity that has caused the error, if any.
|
||||
*/
|
||||
protected function logError($message, EntityDrupalWrapper $entity = NULL) {
|
||||
$variables = array(
|
||||
'!field_name' => $this->field->name,
|
||||
'!field_type' => $this->field->fieldTypeLabel,
|
||||
'!field_label' => $this->field->label,
|
||||
);
|
||||
|
||||
$variables['!this_type'] = $this->entity->type();
|
||||
$variables['!this_label'] = $this->entity->label();
|
||||
|
||||
// If the entity has a URI, provide a link to it. Otherwise, its "link"
|
||||
// will just be an unlinked label. Entity API doesn't reliably expose a url
|
||||
// property on entities, and there doesn't appear to be a way to check for
|
||||
// it without risking an EntityMetadataWrapperException. So I need to use
|
||||
// this clunky BS instead...ugh.
|
||||
$this_uri = entity_uri($this->entity->type(), $this->entity->value());
|
||||
if (isset($this_uri)) {
|
||||
$variables['!this_url'] = url($this_uri['path'], $this_uri['options']);
|
||||
$variables['!this_link'] = l($this->entity->label(), $this_uri['path'], $this_uri['options']);
|
||||
}
|
||||
else {
|
||||
$variables['!this_link'] = $this->entity->label();
|
||||
}
|
||||
|
||||
if ($entity) {
|
||||
$variables['!that_type'] = $entity->type();
|
||||
$variables['!that_label'] = $entity->label();
|
||||
|
||||
// If the entity has a URI, link to it.
|
||||
$that_uri = entity_uri($entity->type(), $entity->value());
|
||||
if (isset($that_uri)) {
|
||||
$variables['!that_url'] = url($that_uri['path'], $that_uri['options']);
|
||||
$variables['!that_link'] = l($entity->label(), $that_uri['path'], $that_uri['options']);
|
||||
}
|
||||
else {
|
||||
$variables['!that_link'] = $entity->label();
|
||||
}
|
||||
}
|
||||
|
||||
watchdog('cer', $message, $variables, WATCHDOG_ERROR);
|
||||
|
||||
if (variable_get('cer_debug', FALSE)) {
|
||||
drupal_set_message(t($message, $variables), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function getIDs() {
|
||||
$IDs = array();
|
||||
|
||||
if ($this->isMultiValue) {
|
||||
foreach ($this->value as $ref) {
|
||||
$IDs[] = $ref->raw();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$IDs[] = $this->value->raw();
|
||||
}
|
||||
|
||||
return array_unique(array_filter($IDs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Countable::count().
|
||||
*/
|
||||
public function count() {
|
||||
if ($this->isMultiValue) {
|
||||
return sizeof($this->value);
|
||||
}
|
||||
else {
|
||||
return ($this->value->value() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements SeekableIterator::seek().
|
||||
*/
|
||||
public function seek($position) {
|
||||
$length = $this->count();
|
||||
|
||||
if ($position < 0) {
|
||||
$position += $length;
|
||||
}
|
||||
|
||||
if ($position >= 0 && $position < $length) {
|
||||
$this->delta = $position;
|
||||
}
|
||||
else {
|
||||
throw new OutOfBoundsException(t('Cannot seek to invalid position.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::current().
|
||||
*/
|
||||
public function current() {
|
||||
return ($this->isMultiValue ? $this->value[$this->delta] : $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::key().
|
||||
*/
|
||||
public function key() {
|
||||
return $this->current()->getIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::next().
|
||||
*/
|
||||
public function next() {
|
||||
$this->delta++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::rewind().
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->delta = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Iterator::valid().
|
||||
*/
|
||||
public function valid() {
|
||||
return ($this->delta < $this->count());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the entity class for CER presets.
|
||||
*/
|
||||
|
||||
class CerPreset extends Entity {
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* The preset's numeric ID in the database.
|
||||
*/
|
||||
public $pid = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* The export identifier, in the format $this->cer_left*$this->cer_right.
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* @var EntityMetadataWrapper
|
||||
* A metadata wrapper around this preset, for convenience.
|
||||
*/
|
||||
public $wrapper;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* The exportable status of this preset.
|
||||
*/
|
||||
public $status = 0x01; // ENTITY_CUSTOM
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* The module exporting this preset.
|
||||
*/
|
||||
public $module = 'cer';
|
||||
|
||||
/**
|
||||
* Overrides Entity::__construct().
|
||||
*/
|
||||
public function __construct(array $values = array()) {
|
||||
parent::__construct($values, 'cer');
|
||||
$this->wrapper = new EntityDrupalWrapper('cer', $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::label().
|
||||
*/
|
||||
public function label() {
|
||||
return isset($this->label_variables) ? t('@left !operator @right', $this->label_variables) : $this->defaultLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::save().
|
||||
*/
|
||||
public function save() {
|
||||
// Generate the export identifier automagically before saving.
|
||||
$this->identifier = $this->wrapper->cer_left->value() . '*' . $this->wrapper->cer_right->value();
|
||||
parent::save();
|
||||
}
|
||||
|
||||
public function invert() {
|
||||
$init = get_object_vars($this);
|
||||
|
||||
unset($init['pid'], $init['wrapper'], $init['identifier'], $init['status']);
|
||||
|
||||
$buf = $init['cer_left'];
|
||||
$init['cer_left'] = $init['cer_right'];
|
||||
$init['cer_right'] = $buf;
|
||||
|
||||
return entity_create('cer', $init);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The controller class for CerPreset entities.
|
||||
*/
|
||||
class CerPresetController extends EntityAPIControllerExportable {
|
||||
|
||||
/**
|
||||
* Overridden.
|
||||
*/
|
||||
public function export($entity, $prefix = '') {
|
||||
$variables = get_object_vars($entity);
|
||||
|
||||
// I really wish Entity API tried to notify the entity that it's being
|
||||
// exported so that it could clean itself up first, but it doesn't. So we
|
||||
// gotta do this bizness.
|
||||
unset($variables['pid'], $variables['wrapper'], $variables['status'], $variables['module'], $variables['label_variables'], $variables['uid']);
|
||||
// Features 2.x checks for overriddenness using sorted keys, which means
|
||||
// that if the variables aren't key-sorted the presets will always be
|
||||
// considered overridden, even if they actually aren't.
|
||||
ksort($variables);
|
||||
|
||||
return entity_var_json_export($variables, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden.
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
|
||||
parent::attachLoad($queried_entities, $revision_id);
|
||||
|
||||
foreach ($queried_entities as $preset) {
|
||||
// Attach variables used to build the human-readable preset label. These
|
||||
// need to be attached after the Field API has done its magic (i.e.,
|
||||
// during parent::attachLoad()), since the label depends on field values.
|
||||
// @see CerPreset::label().
|
||||
$fields = field_attach_view('cer', $preset, 'default');
|
||||
|
||||
$preset->label_variables = array(
|
||||
'@left' =>
|
||||
render($fields['cer_left'][0]),
|
||||
'@right' =>
|
||||
render($fields['cer_right'][0]),
|
||||
'!operator' =>
|
||||
$preset->wrapper->cer_bidirectional->value() ? '<>' : '>',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Contains the controller class for exporting CER presets via Features.
|
||||
*/
|
||||
|
||||
class CerPresetFeaturesController extends EntityDefaultFeaturesController {
|
||||
|
||||
/**
|
||||
* Overridden.
|
||||
*/
|
||||
public function export($data, &$export, $module = '') {
|
||||
$pipe = parent::export($data, $export, $module);
|
||||
|
||||
// Every field in both chains may need to export additional things (the
|
||||
// field base and instance definitions at least, plus any extra dependencies).
|
||||
// All that logic is delegated to CerFieldChain.
|
||||
foreach (entity_load_multiple_by_name($this->type, $data) as $preset) {
|
||||
$pipe = array_merge_recursive($pipe, $preset->wrapper->cer_left->chain->value()->export());
|
||||
$pipe = array_merge_recursive($pipe, $preset->wrapper->cer_right->chain->value()->export());
|
||||
}
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class is a unified way for CER to find the presets that apply to a given
|
||||
* entity. The result set is segmented into two parts: presets where the entity
|
||||
* is on the left side, and bidirectional presets with the entity on the right
|
||||
* side (i.e., the ones which need to be inverted before use). The execute()
|
||||
* method will return a merged and sorted array of presets, but the segmented
|
||||
* result set is exposed to the world as well for other uses (i.e., CER Entity
|
||||
* Settings' selection handler).
|
||||
*/
|
||||
class CerPresetFinder extends EntityFieldQuery {
|
||||
|
||||
public $result = array();
|
||||
|
||||
protected $entity;
|
||||
|
||||
public function __construct(EntityDrupalWrapper $entity) {
|
||||
$this->entity = $entity;
|
||||
|
||||
$this
|
||||
->entityCondition('entity_type', 'cer')
|
||||
->addTag('cer_presets')
|
||||
->addMetaData('entity', $entity);
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$lineage = $this->entity->cer->lineage->value();
|
||||
|
||||
$this->result['cer'] = $this
|
||||
->fieldCondition('cer_enabled', 'value', TRUE)
|
||||
->fieldCondition('cer_left', 'path', $lineage, 'STARTS_WITH')
|
||||
->_load(parent::execute());
|
||||
|
||||
$this->fieldConditions = array();
|
||||
|
||||
$this->result['cer__invert'] = $this
|
||||
->fieldCondition('cer_enabled', 'value', TRUE)
|
||||
->fieldCondition('cer_bidirectional', 'value', TRUE)
|
||||
->fieldCondition('cer_right', 'path', $lineage, 'STARTS_WITH')
|
||||
->_load(parent::execute());
|
||||
|
||||
$result = $this->result['cer'];
|
||||
foreach ($this->result['cer__invert'] as $preset) {
|
||||
$result[] = $preset->invert();
|
||||
}
|
||||
usort($result, array($this, '_sort'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function _load(array $result) {
|
||||
return isset($result['cer']) ? entity_load('cer', array_keys($result['cer'])) : array();
|
||||
}
|
||||
|
||||
private function _sort(CerPreset $a, CerPreset $b) {
|
||||
$a_weight = $a->wrapper->cer_weight->value();
|
||||
$b_weight = $b->wrapper->cer_weight->value();
|
||||
|
||||
if ($a_weight > $b_weight) {
|
||||
return 1;
|
||||
}
|
||||
elseif ($b_weight > $a_weight) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains CerPresetHandler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Contains the logic for performing CER operations on a single entity,
|
||||
* using a single preset.
|
||||
*/
|
||||
class CerPresetHandler {
|
||||
|
||||
/**
|
||||
* @var CerFieldChain
|
||||
*/
|
||||
protected $left;
|
||||
|
||||
/**
|
||||
* @var CerFieldChain
|
||||
*/
|
||||
protected $right;
|
||||
|
||||
/**
|
||||
* @var EntityDrupalWrapper
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $refIDs;
|
||||
|
||||
public function __construct(CerPreset $preset, EntityDrupalWrapper $entity) {
|
||||
$this->left = $preset->wrapper->cer_left->chain->value();
|
||||
$this->right = $preset->wrapper->cer_right->chain->value();
|
||||
$this->entity = $entity;
|
||||
|
||||
// Store the current set of reference IDs so that we only need to instantiate
|
||||
// the left handler once.
|
||||
$this->refIDs = $this->left->getHandler( $entity )->getIDs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an entity insert. This loops through the referenced entity $IDs and
|
||||
* adds a reference to this entity if the reference doesn't already have one.
|
||||
*/
|
||||
public function insert(array $IDs = array()) {
|
||||
// If no IDs were passed in, use the current reference set.
|
||||
$IDs = ($IDs ? $IDs : $this->refIDs);
|
||||
|
||||
// Get this entity's ID right now, so we don't have to keep calling
|
||||
// $this->entity->cer->owner->getIdentifier(). Hooray for micro-optimization!
|
||||
$myID = $this->entity->cer->owner->getIdentifier();
|
||||
|
||||
foreach ($this->load( $IDs ) as $ref) {
|
||||
$handler = $this->right->getHandler( $ref );
|
||||
|
||||
// Only create the backreference if the reference doesn't already reference
|
||||
// this entity (which it might, if there is more than one preset that references
|
||||
// a single field instance).
|
||||
if (! in_array($myID, $handler->getIDs())) {
|
||||
$handler->add( $this->entity->cer->owner );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an entity update. This could be either a normal update done by a user,
|
||||
* or a bulk update.
|
||||
*/
|
||||
public function update() {
|
||||
// Get the previous set of reference IDs. $entity->cer->original will return either
|
||||
// $entity->original, if it exists, or the current entity. So, if this is a bulk
|
||||
// update, $originalIDs will be identical to $this->refIDs.
|
||||
$originalIDs = $this->left->getHandler( $this->entity->cer->original )->getIDs();
|
||||
|
||||
// If there are any references that were in the previous set but not the current
|
||||
// set, delete those backreferences. Under normal circumstances, there will be
|
||||
// nothing to delete during a bulk update, since the previous set and current
|
||||
// set should be identical.
|
||||
$deleted = array_diff($originalIDs, $this->refIDs);
|
||||
if ($deleted) {
|
||||
$this->delete($deleted);
|
||||
}
|
||||
|
||||
// If the previous set is identical to the current set, we'll be processing
|
||||
// all existing references (see the first line of $this->insert()).
|
||||
$added = array_diff($this->refIDs, $originalIDs);
|
||||
$this->insert($added);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an entity delete. Loops through the referenced entity IDs and clears
|
||||
* their references to this entity.
|
||||
*/
|
||||
public function delete(array $IDs = array()) {
|
||||
// As with $this->insert(), we can process a specific set of references or
|
||||
// everything in the current set.
|
||||
$IDs = ($IDs ? $IDs : $this->refIDs);
|
||||
|
||||
foreach ($this->load( $IDs ) as $ref) {
|
||||
$this->right->getHandler( $ref )->delete( $this->entity->cer->owner );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads referenced entities. This might seem like a convenience method, but it
|
||||
* is a critical part CER's core logic.
|
||||
*
|
||||
* @param array $IDs
|
||||
* Array of entity IDs to load.
|
||||
*
|
||||
* @return array
|
||||
* The requested entities, wrapped by EntityDrupalWrapper. If nothing could be
|
||||
* loaded, an empty array is returned.
|
||||
*/
|
||||
protected function load(array $IDs) {
|
||||
if (empty($IDs)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$this->right->rewind();
|
||||
$right = $this->right->current();
|
||||
$entity_type = $right->entityType;
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', $entity_type);
|
||||
$query->entityCondition('entity_id', $IDs);
|
||||
|
||||
// If the right entity type has bundles, we need to filter by that too. If we don't,
|
||||
// we could run into a bug where, if the left field can reference multiple bundles,
|
||||
// we might try to modify the wrong entity. Essentially, the loading of referenced
|
||||
// entities should be as targeted as possible to prevent ambiguities and buggery.
|
||||
if ($right->isBundleable) {
|
||||
$query->entityCondition('bundle', $right->bundle);
|
||||
}
|
||||
|
||||
$result = $query->execute();
|
||||
if (isset($result[$entity_type])) {
|
||||
$result[$entity_type] = entity_load($entity_type, array_keys($result[$entity_type]));
|
||||
|
||||
foreach ($result[$entity_type] as $id => $entity) {
|
||||
$result[$entity_type][$id] = new EntityDrupalWrapper($entity_type, $entity);
|
||||
}
|
||||
|
||||
return $result[$entity_type];
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Contains the controller class for CER's UI (i.e., preset management pages),
|
||||
* used by Entity API.
|
||||
*/
|
||||
|
||||
class CerUIController extends EntityDefaultUIController {
|
||||
|
||||
public function hook_menu() {
|
||||
$items = parent::hook_menu();
|
||||
|
||||
$items[$this->path]['title'] = t('Corresponding Entity References');
|
||||
$items["{$this->path}/list"]['title'] = t('Presets');
|
||||
|
||||
$this->setTitle($items["{$this->path}/add"], t('Add preset'));
|
||||
$this->setTitle($items["{$this->path}/import"], t('Import preset'));
|
||||
|
||||
$items["{$this->path}/manage/%entity_object/toggle"] = $this->createCallback('cer_toggle_preset', 'update');
|
||||
$items["{$this->path}/manage/%entity_object/invert"] = $this->createCallback('cer_invert_preset', 'create');
|
||||
|
||||
// Don't provide a page for cloning a preset.
|
||||
unset($items["{$this->path}/manage/%entity_object/clone"]);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function createCallback($function, $operation, array $init = array()) {
|
||||
return $init + array(
|
||||
'type' => MENU_CALLBACK,
|
||||
'page callback' => $function,
|
||||
'page arguments' => array(5),
|
||||
'load arguments' => array('cer'),
|
||||
'access callback' => 'entity_access',
|
||||
'access arguments' => array($operation, 'cer'),
|
||||
'file' => 'cer.admin.inc',
|
||||
'file path' => drupal_get_path('module', 'cer'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a static title on a menu item.
|
||||
*/
|
||||
private function setTitle(array &$item, $title) {
|
||||
$item['title'] = $title;
|
||||
unset($item['title callback'], $item['title arguments']);
|
||||
}
|
||||
|
||||
public function operationForm($form, &$form_state, $entity, $action) {
|
||||
switch ($action) {
|
||||
case 'delete':
|
||||
return confirm_form($form, t('Are you sure you want to delete this preset?'), $this->path, t('@left will no longer synchronize with @right.', $entity->label_variables));
|
||||
|
||||
default:
|
||||
return parent::operationForm($form, $form_state, $entity, $action);
|
||||
}
|
||||
}
|
||||
|
||||
public function overviewForm($form, &$form_state) {
|
||||
$form = parent::overviewForm($form, $form_state);
|
||||
|
||||
$form['actions'] = array(
|
||||
'update' => array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save changes'),
|
||||
),
|
||||
'#type' => 'actions',
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function overviewFormSubmit($form, &$form_state) {
|
||||
foreach ($form_state['values']['table'] as $id => $values) {
|
||||
$preset = entity_object_load($id, $this->entityType);
|
||||
|
||||
$preset->wrapper->cer_enabled->set($values['cer_enabled'][LANGUAGE_NONE][0]['value']);
|
||||
$preset->wrapper->cer_bidirectional->set($values['cer_bidirectional'][LANGUAGE_NONE][0]['value']);
|
||||
$preset->wrapper->cer_weight->set($values['cer_weight'][LANGUAGE_NONE][0]['value']);
|
||||
|
||||
$preset->save();
|
||||
}
|
||||
|
||||
drupal_set_message(t('The changes have been saved.'));
|
||||
}
|
||||
|
||||
public function overviewTable($conditions = array()) {
|
||||
$render = array(
|
||||
'#header' => array(
|
||||
t('Left Field'),
|
||||
t('Right Field'),
|
||||
t('Status'),
|
||||
t('Enabled'),
|
||||
t('Bidirectional'),
|
||||
t('Weight'),
|
||||
t('Operations'),
|
||||
),
|
||||
'#tabledrag' => array(
|
||||
array(
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'cer-weight',
|
||||
),
|
||||
),
|
||||
'#empty' => t('None.'),
|
||||
'#type' => 'table',
|
||||
);
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', $this->entityType);
|
||||
|
||||
// Add all conditions to query.
|
||||
foreach ($conditions as $key => $value) {
|
||||
$query->propertyCondition($key, $value);
|
||||
}
|
||||
|
||||
if ($this->overviewPagerLimit) {
|
||||
$query->pager($this->overviewPagerLimit);
|
||||
}
|
||||
|
||||
$query->fieldOrderBy('cer_weight', 'value');
|
||||
|
||||
$results = $query->execute();
|
||||
$entities = isset($results['cer']) ? entity_load('cer', array_keys($results['cer'])) : array();
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$render[$entity->pid] = $this->overviewTableRow($conditions, $entity->pid, $entity);
|
||||
}
|
||||
|
||||
return $render;
|
||||
}
|
||||
|
||||
protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
|
||||
$render_fields = field_attach_view($this->entityType, $entity, 'default');
|
||||
|
||||
$render_fields['cer_left']['#label_display'] = 'inline';
|
||||
$render_fields['cer_left']['#title'] = $entity->wrapper->cer_left->chain->value()->end()->fieldTypeLabel;
|
||||
$row['cer_left'] = $render_fields['cer_left'];
|
||||
|
||||
$render_fields['cer_right']['#label_display'] = 'inline';
|
||||
$render_fields['cer_right']['#title'] = $entity->wrapper->cer_right->chain->value()->end()->fieldTypeLabel;
|
||||
$row['cer_right'] = $render_fields['cer_right'];
|
||||
|
||||
$row['status'] = array(
|
||||
'#theme' => 'entity_status',
|
||||
'#status' => $entity->status,
|
||||
);
|
||||
|
||||
$form_fields = array();
|
||||
$form_state = form_state_defaults();
|
||||
$form_state['build_info']['form_id'] = 'cer_overview_form';
|
||||
field_attach_form($this->entityType, $entity, $form_fields, $form_state);
|
||||
|
||||
unset($form_fields['cer_enabled'][LANGUAGE_NONE]['#title']);
|
||||
$row['cer_enabled'] = $form_fields['cer_enabled'];
|
||||
|
||||
unset($form_fields['cer_bidirectional'][LANGUAGE_NONE]['#title']);
|
||||
$row['cer_bidirectional'] = $form_fields['cer_bidirectional'];
|
||||
|
||||
unset($form_fields['cer_weight'][LANGUAGE_NONE]['#title']);
|
||||
$form_fields['cer_weight'][LANGUAGE_NONE]['#attributes']['class'][] = 'cer-weight';
|
||||
$row['cer_weight'] = $form_fields['cer_weight'];
|
||||
|
||||
// Add in any passed additional cols.
|
||||
foreach ($additional_cols as $key => $col) {
|
||||
$row[$key] = $col;
|
||||
}
|
||||
|
||||
// I like drop buttons more than an inline set of links.
|
||||
$links = array(
|
||||
'toggle' => array(
|
||||
'title' => $entity->wrapper->cer_enabled->value() ? t('disable') : t('enable'),
|
||||
'href' => "{$this->path}/manage/{$id}/toggle",
|
||||
'query' => drupal_get_destination(),
|
||||
),
|
||||
'edit' => array(
|
||||
'title' => t('edit'),
|
||||
'href' => "{$this->path}/manage/{$id}",
|
||||
),
|
||||
);
|
||||
|
||||
// If the preset is one-directional, expose a link to invert it.
|
||||
if (! $entity->wrapper->cer_bidirectional->value()) {
|
||||
$links['invert'] = array(
|
||||
'title' => t('invert'),
|
||||
'href' => "{$this->path}/manage/{$id}/invert",
|
||||
'query' => drupal_get_destination(),
|
||||
);
|
||||
}
|
||||
|
||||
if (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) {
|
||||
$links['revert'] = array(
|
||||
'title' => t('revert'),
|
||||
'href' => "{$this->path}/manage/{$id}/revert",
|
||||
'query' => drupal_get_destination(),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$links['delete'] = array(
|
||||
'title' => t('delete'),
|
||||
'href' => "{$this->path}/manage/{$id}/delete",
|
||||
'query' => drupal_get_destination(),
|
||||
);
|
||||
}
|
||||
$links['export'] = array(
|
||||
'title' => t('export'),
|
||||
'href' => "{$this->path}/manage/{$id}/export",
|
||||
);
|
||||
|
||||
$row['operations'] = array(
|
||||
'#theme' => 'links__ctools_dropbutton',
|
||||
'#links' => $links,
|
||||
);
|
||||
|
||||
$row['#attributes']['class'][] = 'draggable';
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CER plugin for Entity Reference fields.
|
||||
*/
|
||||
|
||||
class CerEntityReferenceField extends CerField {
|
||||
|
||||
/**
|
||||
* Implements CerField::getTargetType().
|
||||
*/
|
||||
public function getTargetType() {
|
||||
return $this->settings['target_type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override CerField::getTargetBundles().
|
||||
*/
|
||||
public function getTargetBundles() {
|
||||
$bundles = array();
|
||||
|
||||
// If the reference field is using a view, load the view and see if it's filtering by the entity
|
||||
// type's bundle filter. If it is, the filter values are the target bundles. Otherwise,
|
||||
// assume that all bundles can be referenced.
|
||||
//
|
||||
// @todo Support contextual filters?
|
||||
//
|
||||
// NOTE: Selection handlers (i.e., $field['settings']['handler']) are plugins owned by
|
||||
// Entity Reference. There is no method defined to get an array of referenceable
|
||||
// bundles, but hopefully, if CER gains enough traction in the community, such a
|
||||
// method can be added to the EntityReference_SelectionHandler interface. This
|
||||
// function could then be deprecated, which would be a more flexible, future-proof
|
||||
// method of finding a field's target bundles.
|
||||
//
|
||||
if ($this->settings['handler'] == 'views') {
|
||||
$view = views_get_view($this->settings['handler_settings']['view']['view_name']);
|
||||
$view->set_display($this->settings['handler_settings']['view']['display_name']);
|
||||
|
||||
$info = entity_get_info($this->getTargetType());
|
||||
if (isset($info['entity keys']['bundle'])) {
|
||||
$handler = $view->display_handler->get_handler('filter', $info['entity keys']['bundle']);
|
||||
if ($handler) {
|
||||
$bundles = $handler->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$bundles = $this->settings['handler_settings']['target_bundles'];
|
||||
}
|
||||
|
||||
return ($bundles ? $bundles : parent::getTargetBundles());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CER plugin for Field Collection fields.
|
||||
*/
|
||||
|
||||
class CerFieldCollectionField extends CerField implements CerEntityContainerInterface {
|
||||
|
||||
/**
|
||||
* Implements CerField::getTargetType().
|
||||
*/
|
||||
public function getTargetType() {
|
||||
return 'field_collection_item';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override CerField::getTargetBundles().
|
||||
*/
|
||||
public function getTargetBundles() {
|
||||
return array($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements CerEntityContainerInterface::createInnerEntity().
|
||||
*/
|
||||
public function createInnerEntity(EntityDrupalWrapper $owner) {
|
||||
// Create an empty field collection item.
|
||||
$collection = new EntityDrupalWrapper('field_collection_item', entity_create('field_collection_item', array('field_name' => $this->name)));
|
||||
$collection->host_entity->set( $owner );
|
||||
$collection->save(TRUE);
|
||||
|
||||
// 'Reference' the newly created field collection item.
|
||||
$this->getHandler($owner)->add($collection);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CER plugin for Node Reference fields.
|
||||
*/
|
||||
|
||||
class CerNodeReferenceField extends CerField {
|
||||
|
||||
/**
|
||||
* Implements CerField::getTargetType().
|
||||
*/
|
||||
public function getTargetType() {
|
||||
return 'node';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override CerField::getTargetBundles().
|
||||
*/
|
||||
public function getTargetBundles() {
|
||||
$bundles = array();
|
||||
|
||||
$view = $this->settings['view']['view_name'];
|
||||
if ($view) {
|
||||
$view = views_get_view($view);
|
||||
$view->set_display($this->settings['view']['view_display']);
|
||||
|
||||
$handler = $view->display_handler->get_handler('filter', 'type');
|
||||
if ($handler) {
|
||||
$bundles = $handler->value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$bundles = array_filter($this->settings['referenceable_types']);
|
||||
}
|
||||
|
||||
return ($bundles ? $bundles : parent::getTargetBundles());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CER plugin for Taxonomy Term Reference fields.
|
||||
*/
|
||||
|
||||
class CerTaxonomyTermReferenceField extends CerField {
|
||||
|
||||
/**
|
||||
* Implements CerField::getTargetType().
|
||||
*/
|
||||
public function getTargetType() {
|
||||
return 'taxonomy_term';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override CerField::getTargetBundles().
|
||||
*/
|
||||
public function getTargetBundles() {
|
||||
$bundles = array();
|
||||
|
||||
foreach ($this->settings['allowed_values'] as $item) {
|
||||
$bundles[] = $item['vocabulary'];
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the CER plugin for User Reference fields.
|
||||
*/
|
||||
|
||||
class CerUserReferenceField extends CerField {
|
||||
|
||||
/**
|
||||
* Implements CerField::getTargetType().
|
||||
*/
|
||||
public function getTargetType() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user