View source
<?php
require_once dirname(__FILE__) . '/relation.rules.inc';
require_once dirname(__FILE__) . '/relation.ctools.inc';
function relation_entity_info() {
$entities['relation'] = array(
'label' => t('Relation'),
'label callback' => 'relation_label',
'base table' => 'relation',
'revision table' => 'relation_revision',
'fieldable' => TRUE,
'controller class' => 'RelationController',
'save callback' => 'relation_save',
'creation callback' => 'relation_entity_create',
'deletion callback' => 'relation_delete',
'access callback' => 'relation_rules_access',
'uri callback' => 'relation_uri',
'view callback' => 'relation_multiple_view',
'entity keys' => array(
'id' => 'rid',
'revision' => 'vid',
'bundle' => 'relation_type',
'label' => 'rid',
),
'bundle keys' => array(
'bundle' => 'relation_type',
),
'bundles' => array(),
'view modes' => array(),
);
foreach (relation_get_types() as $type => $info) {
$entities['relation']['bundles'][$type] = (array) $info;
$entities['relation']['bundles'][$type]['admin'] = array(
'path' => 'admin/structure/relation/manage/%relation_type',
'real path' => 'admin/structure/relation/manage/' . $type,
'bundle argument' => 4,
'access arguments' => array(
'administer relation types',
),
);
}
return $entities;
}
function relation_entity_property_info() {
$info = array();
$properties =& $info['relation']['properties'];
$properties = array(
'rid' => array(
'label' => t('Relation ID'),
'description' => t('The internal numeric ID of the relation.'),
'type' => 'integer',
'schema field' => 'rid',
),
'relation_type' => array(
'label' => t('Relation type'),
'type' => 'token',
'description' => t('The type of the relation.'),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'edit relations',
'options list' => 'relation_rules_get_type_options',
'required' => TRUE,
'schema field' => 'relation_type',
),
'endpoints' => array(
'label' => t('Endpoints'),
'type' => 'list<entity>',
'description' => t('The endpoints of the relation.'),
'setter callback' => 'relation_rules_set_endpoints',
'getter callback' => 'relation_rules_get_endpoints',
'setter permission' => 'edit relations',
'required' => TRUE,
),
);
return $info;
}
function relation_entity_property_info_alter(&$info) {
foreach (relation_get_types() as $relation_type => $relation) {
foreach ($relation->source_bundles as $key => $bundles) {
list($entity_type, $bundle) = explode(':', $bundles, 2);
$info['relation']['bundles'][$bundle]['properties']['endpoints_source_' . $entity_type] = array(
'label' => t('@type (source endpoint)', array(
'@type' => $entity_type,
)),
'type' => 'list<' . $entity_type . '>',
'getter callback' => 'relation_rules_get_specific_endpoints',
'endpoint_type' => 'source',
'relation_directional' => $relation->directional,
);
}
foreach ($relation->target_bundles as $key => $bundles) {
list($entity_type, $bundle) = explode(':', $bundles, 2);
$info['relation']['bundles'][$bundle]['properties']['endpoints_target_' . $entity_type] = array(
'label' => t('@type (target endpoint)', array(
'@type' => $entity_type,
)),
'type' => 'list<' . $entity_type . '>',
'getter callback' => 'relation_rules_get_specific_endpoints',
'endpoint_type' => 'target',
'relation_directional' => $relation->directional,
);
}
$source_bundles = $relation->source_bundles;
$original_sb = array_values($source_bundles);
$directional = FALSE;
if (count($relation->target_bundles) >= 1) {
$original_tb = array_values($relation->target_bundles);
$target_bundles = array_merge($source_bundles, $relation->target_bundles);
$source_bundles = $target_bundles;
$directional = TRUE;
}
else {
$target_bundles = $source_bundles;
}
foreach ($target_bundles as $target_key => $target_bundle) {
list($entity_target) = explode(':', $target_bundle, 2);
foreach ($source_bundles as $source_key => $source_bundle) {
$property_reverse = $directional && !in_array($source_bundle, $original_sb) && !in_array($target_bundle, $original_tb);
$property = $property_reverse ? 'relation_' . $relation_type . '_' . $entity_target . '_reverse' : 'relation_' . $relation_type . '_' . $entity_target;
list($entity_source) = explode(':', $source_bundle, 2);
if (!$directional || $property_reverse || in_array($target_bundle, $original_tb) && in_array($source_bundle, $original_sb)) {
$info[$entity_source]['properties'][$property] = array(
'label' => t('Relation @relation_type (to @entity' . ($property_reverse ? ' reverse)' : ')'), array(
'@relation_type' => $relation_type,
'@entity' => $entity_target,
)),
'type' => 'list<' . $entity_target . '>',
'relation_type' => $relation_type,
'target_type' => $entity_target,
'description' => t("A list of entities related."),
'getter callback' => 'relation_rules_get_related_entities',
);
}
}
}
}
}
function relation_permission() {
return array(
'administer relation types' => array(
'title' => t('Administer Relation types'),
'description' => t('Create, edit, delete, and perform administration tasks for relation types.'),
),
'export relation types' => array(
'title' => t('Export Relation types'),
),
'use relation import' => array(
'title' => t('Use Relation import'),
'restrict access' => TRUE,
),
'access relations' => array(
'title' => t('View Relations'),
'description' => t('Grant access to view the endpoints of Relations.'),
),
'create relations' => array(
'title' => t('Create Relations'),
),
'edit relations' => array(
'title' => t('Edit Relations'),
),
'delete relations' => array(
'title' => t('Delete Relations'),
),
'administer relations' => array(
'title' => t('Administer Relations'),
),
);
}
function relation_type_create($info = array()) {
$info = (array) $info;
if (empty($info['relation_type'])) {
return FALSE;
}
$info += array(
'min_arity' => 2,
'max_arity' => 2,
'directional' => FALSE,
'transitive' => FALSE,
'r_unique' => FALSE,
'source_bundles' => array(),
'target_bundles' => array(),
);
if (empty($info['label'])) {
$info['label'] = $info['relation_type'];
}
if (empty($info['reverse_label'])) {
$info['reverse_label'] = $info['label'];
}
return (object) $info;
}
function relation_type_save($relation_type, $write_record_keys = array(), $rebuild_menu = TRUE) {
$relation_type = relation_type_create($relation_type);
$existing_relation_type = relation_get_types(array(
$relation_type->relation_type,
));
$type = $relation_type->relation_type;
$source_bundles = $relation_type->source_bundles;
$target_bundles = $relation_type->target_bundles;
unset($relation_type->source_bundles, $relation_type->target_bundles);
$transaction = db_transaction();
drupal_write_record('relation_type', $relation_type, $write_record_keys);
db_delete('relation_bundles')
->condition('relation_type', $type)
->execute();
$query = db_insert('relation_bundles')
->fields(array(
'relation_type',
'entity_type',
'bundle',
'r_index',
));
foreach ($source_bundles as $entity_bundles) {
list($entity_type, $bundle) = explode(':', $entity_bundles, 2);
$query
->values(array(
$type,
$entity_type,
$bundle,
0,
));
}
if ($relation_type->directional) {
foreach ($target_bundles as $entity_bundles) {
list($entity_type, $bundle) = explode(':', $entity_bundles, 2);
$query
->values(array(
$type,
$entity_type,
$bundle,
1,
));
}
}
$query
->execute();
relation_type_ensure_instance($type);
if ($rebuild_menu) {
menu_rebuild();
}
if (empty($existing_relation_type)) {
field_attach_create_bundle('relation', $relation_type->relation_type);
}
}
function relation_type_ensure_instance($type) {
if (!drupal_static('relation_install') && !field_info_instance('relation', 'endpoints', $type)) {
$instance = array(
'field_name' => 'endpoints',
'entity_type' => 'relation',
'bundle' => $type,
);
field_create_instance($instance);
field_info_instance('relation', 'endpoints', $type);
}
}
function relation_type_load($relation_type) {
$types = relation_get_types(array(
$relation_type,
));
return isset($types[$relation_type]) ? $types[$relation_type] : FALSE;
}
function relation_get_types($types = array()) {
if (module_exists('ctools')) {
ctools_include('export');
}
if (function_exists('ctools_export_crud_load_multiple')) {
$relation_types = $types ? ctools_export_crud_load_multiple('relation_type', $types) : ctools_export_crud_load_all('relation_type');
static $recurse = FALSE;
if (!$recurse) {
$recurse = TRUE;
foreach ($relation_types as $type => $data) {
if (!empty($data->in_code_only)) {
relation_type_ensure_instance($type);
}
}
}
$recurse = FALSE;
}
else {
$query = db_select('relation_type', 'rt')
->fields('rt', array(
'relation_type',
'label',
'reverse_label',
'directional',
'transitive',
'r_unique',
'min_arity',
'max_arity',
));
if ($types) {
$query
->condition('relation_type', $types);
}
$results = $query
->execute();
$relation_types = array();
foreach ($results as $relation_type) {
$relation_types[$relation_type->relation_type] = $relation_type;
}
_relation_get_types_bundles($relation_types);
}
return $relation_types;
}
function relation_get_types_options() {
$types = relation_get_types();
$options = array();
foreach ($types as $type => $relation_type) {
$options[$type] = $relation_type->label;
}
return $options;
}
function _relation_get_types_bundles(&$relation_types) {
foreach ($relation_types as &$relation_type) {
if (empty($relation_type->in_code_only) && empty($relation_type->bundles_loaded)) {
$relation_type->source_bundles = array();
$relation_type->target_bundles = array();
foreach (db_query('SELECT relation_type, entity_type, bundle, r_index FROM {relation_bundles} WHERE relation_type = :relation_type', array(
':relation_type' => $relation_type->relation_type,
)) as $record) {
$endpoint = $record->r_index ? 'target_bundles' : 'source_bundles';
$relation_type->{$endpoint}[] = "{$record->entity_type}:{$record->bundle}";
}
$relation_type->bundles_loaded = TRUE;
}
}
}
function relation_list_types() {
$results = relation_get_types();
$relation_types = array();
foreach ($results as $type => $relation_type) {
$relation_types[$type] = $relation_type->label . ' (' . $type . ')';
}
return $relation_types;
}
function relation_type_delete($relation_type) {
db_delete('relation_type')
->condition('relation_type', $relation_type)
->execute();
db_delete('relation_bundles')
->condition('relation_type', $relation_type)
->execute();
if (field_info_instance('relation', 'endpoints', $relation_type)) {
$instance = array(
'field_name' => 'endpoints',
'entity_type' => 'relation',
'bundle' => $relation_type,
);
field_delete_instance($instance, FALSE);
}
}
function relation_load($rid, $vid = NULL, $reset = FALSE) {
$conditions = isset($vid) ? array(
'vid' => $vid,
) : array();
$relations = relation_load_multiple(array(
$rid,
), $conditions, $reset);
return reset($relations);
}
function relation_load_multiple($rids, $conditions = array(), $reset = FALSE) {
return entity_load('relation', $rids, $conditions, $reset);
}
function relation_view($relation, $view_mode = 'full') {
$entity_type = 'relation';
$entity = $relation;
$entities = array(
$relation->rid => $relation,
);
field_attach_prepare_view($entity_type, $entities, $view_mode);
entity_prepare_view($entity_type, $entities);
$build = field_attach_view($entity_type, $entity, $view_mode);
$build += array(
'#entity' => $relation,
'#view_mode' => $view_mode,
'#language' => LANGUAGE_NONE,
);
module_invoke_all('entity_view', $entity, $entity_type, $view_mode, LANGUAGE_NONE);
drupal_alter('entity_view', $build, $entity_type);
return $build;
}
function relation_multiple_view($relations, $view_mode) {
$build = array();
foreach ($relations as $relation) {
$build['relation'][$relation->rid] = relation_view($relation, $view_mode);
}
return $build;
}
function relation_relation_exists($entity_keys, $relation_type = NULL, $enforce_direction = FALSE) {
$query = relation_query();
foreach ($entity_keys as $r_index => $entity_key) {
$query
->related($entity_key['entity_type'], $entity_key['entity_id'], $enforce_direction ? $r_index : NULL);
}
if ($relation_type) {
$query
->propertyCondition('relation_type', $relation_type);
}
$query
->propertyCondition('arity', count($entity_keys));
$query
->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');
if (!$enforce_direction) {
$query
->addTag('enforce_distinct_endpoints');
}
$relation_ids = $query
->execute();
return $relation_ids ? $relation_ids : FALSE;
}
function relation_query_enforce_distinct_endpoints_alter(QueryAlterableInterface $query) {
$relation_query = $query
->getMetaData('entity_field_query');
foreach ($relation_query->propertyConditions as $condition) {
if ($condition['column'] == 'arity') {
$arity = $condition['value'];
break;
}
}
for ($i = 0; $i < $arity; $i++) {
for ($k = $i + 1; $k < $arity; $k++) {
$column_left = 'field_data_endpoints_delta_' . $i . '.endpoints_r_index';
$column_right = 'field_data_endpoints_delta_' . $k . '.endpoints_r_index';
$query
->where("{$column_left} != {$column_right}");
}
}
}
function relation_create($type, $endpoints, $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
$relation = new stdClass();
$relation->is_new = TRUE;
$relation->relation_type = $type;
$relation->uid = $account->uid;
$relation->endpoints[LANGUAGE_NONE] = $endpoints;
return $relation;
}
function relation_save($relation, $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
try {
field_attach_validate('relation', $relation);
} catch (FieldValidationException $e) {
foreach ($e->errors as $field_name => $field_errors) {
foreach ($field_errors as $langcode => $multiple_errors) {
foreach ($multiple_errors as $delta => $item_errors) {
foreach ($item_errors as $item_error) {
watchdog_exception('relation', $e, $item_error['message']);
}
}
}
}
return FALSE;
}
if (!isset($relation->is_new)) {
$relation->is_new = empty($relation->rid);
}
$transaction = db_transaction();
$endpoints = field_get_items('relation', $relation, 'endpoints');
$relation->arity = count($endpoints);
$relation->changed = time();
if (!$relation->is_new) {
$keys = array(
'rid',
);
$op = 'update';
$relation->original = relation_load($relation->rid, $relation->vid);
}
else {
$relation->created = $relation->changed;
$keys = array();
$op = 'insert';
}
field_attach_presave('relation', $relation);
module_invoke_all('entity_presave', $relation, 'relation');
unset($relation->vid);
$temp_uid = $relation->uid;
$relation->uid = $account->uid;
drupal_write_record('relation_revision', $relation);
$relation->uid = $temp_uid;
drupal_write_record('relation', $relation, $keys);
if ($relation->is_new) {
db_update('relation_revision')
->fields(array(
'rid' => $relation->rid,
))
->condition('vid', $relation->vid)
->execute();
}
call_user_func("field_attach_{$op}", 'relation', $relation);
module_invoke_all('entity_' . $op, $relation, 'relation');
module_invoke('rules', 'invoke_event', 'relation_' . $op, $relation);
relation_clear_related_entities_cache($endpoints);
unset($relation->is_new);
unset($relation->original);
return $relation->rid;
}
function relation_clear_related_entities_cache($endpoints) {
drupal_static_reset('relation_get_related_entity');
foreach ($endpoints as $endpoint) {
cache_clear_all('relation:' . $endpoint['entity_type'] . ':' . $endpoint['entity_id'], 'cache', TRUE);
}
}
function relation_insert($type, $endpoints) {
$relation = relation_create($type, $endpoints);
return relation_save($relation);
}
function relation_update($relation) {
relation_save($relation);
}
function relation_delete($rid) {
relation_delete_multiple(array(
$rid,
));
}
function relation_delete_multiple($rids) {
$relations = relation_load_multiple($rids);
foreach ($relations as $rid => $relation) {
db_delete('relation')
->condition('rid', $rid)
->execute();
db_delete('relation_revision')
->condition('rid', $rid)
->execute();
module_invoke_all('entity_delete', $relation, 'relation');
module_invoke('rules', 'invoke_event', 'relation_delete', $relation);
field_attach_delete('relation', $relation);
$endpoints = field_get_items('relation', $relation, 'endpoints');
relation_clear_related_entities_cache($endpoints);
}
}
function relation_label($relation) {
return t('Relation !id', array(
'!id' => $relation->rid,
));
}
function relation_uri($relation) {
return array(
'path' => 'relation/' . $relation->rid,
);
}
function relation_query($entity_type = NULL, $entity_id = NULL, $r_index = NULL) {
return new RelationQuery($entity_type, $entity_id, $r_index);
}
function relation_get_endpoints($relation, $entity_type = NULL, $endpoint_type = NULL, $load_entities = TRUE) {
$items = array();
$endpoints = relation_get_endpoints_by_endpoint_type($relation, $endpoint_type);
foreach ($endpoints as $endpoint) {
if (empty($entity_type) || $endpoint['entity_type'] == $entity_type) {
$items[$endpoint['entity_type']][] = $endpoint['entity_id'];
}
}
if ($load_entities) {
foreach ($items as $entity_type => $ids) {
$items[$entity_type] = entity_load($entity_type, $ids);
}
}
return $items;
}
function relation_get_endpoints_by_endpoint_type($relation, $endpoint_type = 'source') {
if (empty($endpoint_type)) {
return $relation->endpoints[LANGUAGE_NONE];
}
if (!in_array($endpoint_type, array(
'source',
'target',
))) {
return array();
}
$relation_type = relation_type_load($relation->relation_type);
$endpoint_type = $relation_type->directional ? $endpoint_type : 'source';
if ($endpoint_type == 'source') {
if ($relation_type->directional) {
$endpoints = array(
$relation->endpoints[LANGUAGE_NONE][0],
);
}
else {
$endpoints = $relation->endpoints[LANGUAGE_NONE];
}
}
else {
$endpoints = array_slice($relation->endpoints[LANGUAGE_NONE], 1);
}
return $endpoints;
}
function relation_get_related_entity($entity_type, $entity_id, $relation_type = NULL, $r_index = NULL) {
$items =& drupal_static(__FUNCTION__);
$request_key = "{$entity_type}:{$entity_id}";
$cache_key = "{$request_key}:{$relation_type}:{$r_index}";
if (isset($items[$cache_key])) {
$entities = $items[$cache_key];
}
elseif ($cached = cache_get('relation:' . $cache_key)) {
$entities = $cached->data;
$items[$cache_key] = $entities;
}
else {
$query = relation_query($entity_type, $entity_id, $r_index)
->range(0, 1);
if ($relation_type) {
$query
->entityCondition('bundle', $relation_type);
}
$results = $query
->execute();
$result = reset($results);
if ($result) {
$relation = relation_load($result->rid);
if ($relation->arity == 1) {
$entities = FALSE;
}
else {
$entities = field_get_items('relation', $relation, 'endpoints');
}
}
else {
$entities = FALSE;
}
cache_set('relation:' . $cache_key, $entities);
$items[$cache_key] = $entities;
}
if ($entities) {
$first_entity_key = $entities[0]['entity_type'] . ':' . $entities[0]['entity_id'];
if (isset($r_index)) {
$request_key = $request_key . ':' . $r_index;
$first_entity_key = $first_entity_key . ':' . $entities[0]['r_index'];
}
if ($request_key == $first_entity_key) {
$other_endpoints = entity_load($entities[1]['entity_type'], array(
$entities[1]['entity_id'],
));
return reset($other_endpoints);
}
$other_endpoints = entity_load($entities[0]['entity_type'], array(
$entities[0]['entity_id'],
));
return reset($other_endpoints);
}
return FALSE;
}
function relation_get_available_types($entity_type, $bundle, $endpoint = 'source') {
$bundle_key = $entity_type . ':' . $bundle;
$all_bundle_key = $entity_type . ':*';
$relation_types = relation_get_types();
foreach ($relation_types as $type => $relation_type) {
$available = FALSE;
if ($endpoint == 'source' || $endpoint == 'both') {
if (in_array($bundle_key, $relation_type->source_bundles) || in_array($all_bundle_key, $relation_type->source_bundles)) {
$available = TRUE;
}
}
if ($endpoint == 'target' || $endpoint == 'both') {
if (in_array($bundle_key, $relation_type->target_bundles) || in_array($all_bundle_key, $relation_type->target_bundles)) {
$available = TRUE;
}
}
if (!$available) {
unset($relation_types[$type]);
}
}
return $relation_types;
}
function relation_entity_delete($entity, $entity_type) {
list($entity_id) = entity_extract_ids($entity_type, $entity);
$relations = relation_query($entity_type, $entity_id)
->execute();
$relations_to_delete = array();
foreach ($relations as $row) {
$relation = relation_load($row->rid);
foreach ($relation->endpoints[LANGUAGE_NONE] as $key => $endpoint) {
if ($endpoint['entity_id'] == $entity_id && $endpoint['entity_type'] == $entity_type) {
unset($relation->endpoints[LANGUAGE_NONE][$key]);
}
}
$type = relation_get_types(array(
$relation->relation_type,
));
$min_arity = $type[$relation->relation_type]->min_arity;
$arity = count($relation->endpoints[LANGUAGE_NONE]);
if ($arity < $min_arity) {
array_push($relations_to_delete, $relation->rid);
}
else {
relation_save($relation);
}
}
if (!empty($relations_to_delete)) {
relation_delete_multiple($relations_to_delete);
drupal_set_message(t('Relations @relations have been deleted.', array(
'@relations' => implode(', ', $relations_to_delete),
)));
}
}
function relation_views_api() {
return array(
'api' => 3.0,
'path' => drupal_get_path('module', 'relation') . '/views',
);
}
function relation_get_type_label($relation, $reverse = FALSE) {
$type = relation_type_load($relation->relation_type);
if ($type->directional && $reverse) {
return $type->reverse_label;
}
else {
return $type->label;
}
}
function _relation_get_bundle_label($entity_type, $entity_bundle) {
$entity_info = entity_get_info($entity_type);
return $entity_info['bundles'][$entity_bundle]['label'];
}
class RelationController extends DrupalDefaultEntityController {
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
$query = parent::buildQuery($ids, $conditions, $revision_id);
$fields =& $query
->getFields();
$fields['uid']['table'] = 'base';
$query
->addField('revision', 'uid', 'revision_uid');
$fields['changed']['table'] = 'base';
$query
->addField('revision', 'changed', 'changed');
return $query;
}
}
function relation_entity_create($values = array()) {
$relation = relation_create($values['relation_type'], array());
foreach ($values as $key => $value) {
$relation->{$key} = $value;
}
return $relation;
}