diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 55060e458898e126558221d24f78fa98d8850206..179d3825a9f6c16e9587ee4ce35fb8c3919ca36e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,4 +2,5 @@ Relation 7.x-1.x, xxxx-xx-xx ---------------------------- +#977016 by sun: Moved Awesomerelationship into Relation project. by sun: Added initial baseline of module files. diff --git a/README.txt b/README.txt index 165d544fc91854a792f8430e527718cf7632ad78..f0f99296c4b198348b77426e39607d9610fd042a 100644 --- a/README.txt +++ b/README.txt @@ -4,6 +4,92 @@ Describes relationships between entities. +The intent of this module is to provide an API that describes the relationships +between Drupal entities. For example: + comments <--> nodes + nodes <--> authors + terms <--> nodes + files <--> users + files <--> nodes + +Some of these relationships are hard-coded properties of an entity; for example, +nodes have an author, a creation date, and a last updated date. Other +relationships exist because of field values; putting a filefield on a node type +creates a relationship between file entities and node type entities. An +entity_reference module could explicitly define relationships between entities. + +This module treats entity relationships as an entity type themselves. Types of +relationships are bundles. Each bundle describes the predicate of an RDF triple; +the two entites are the subject and object. + + Entity relationship type = SUBJECT + PREDICATE + OBJECT + Node author relationship = node + creator + user + Taxonomy field relationship = blog post + is tagged with + some term + +There is a distinction between relationships that are defined by the properties +or behavior of an entity type and relationships that are defined per bundle. We +think we can relate these ways: + + entity type <--> entity type + bundle <--> bundle + +But not these: + entity type <--> bundle + +...because entity types are not extensible in the way that bundles are +extensible via the core Field API. Entity type relationships will need to be +pre-defined, since they're infrastructure; we can describe them but not extend +them. Bundle relationships should be inferred from whatever field configuration +is present on any particular Drupal installation, and users will be able to +create and edit relationships between bundles. + +An entity relationship API will let us visualize the content model of a Drupal +site. With this, we could export an RDF schema of the entire content model of a +site; we could build a "content explorer" that shows the linkings from any one +piece of content (a user -> their nodes -> a particular node -> a term -> nodes +tagged with that term). The first milestone for this module is to provide simple +blocks that display specific corners of this graph, like a user's nodes, and +terms a user has used. In the future, we would like to be able to add filters to +the graph (like "a users nodes" + "only blog posts"). + +Drupal core defines several entity types: +- user +- node +- comment +- taxonomy +- file (within system.module's hook_entity_info()) + +Some related discussions/projects: +- http://drupal.org/node/533222 (nodereference/userreference fields in D7) +- http://drupal.org/project/entity (Entity API) +- http://drupal.org/project/commentreference (CCK Comment Reference) +- http://drupal.pastebin.com/avnKvCD0 (notes from chx) + +A little more thinking/rephrasing... + +Relationship data may not be stored by awesomerelationships itself; +awesomerelationships will provide a way for entity or field providers to +describe any relationships they may create. This way, we can query +awesomerelationships' relationship API about any relationship on the site. + +The awesomerelationships module should be able to ask the relationship bundle +provider things like: +- what is your subject type + predicate + object type? +- what relationships of your type exist, given a set of subject or object ids? + -> what relationships of your type exist for subject id *? + -> what relationships of your type exist for object id (123, 456, 78, 9)? + +We will be able to ask awesomerelationships' relationship API about what +relationships exist, like "given entity type X, find all relationship bundles +where X is the subject (or object, or either)". + +The relationship API will find the shortest path between entities: + get_relationship('subject_type', 'object_type') + get_relationship(user, node) ~~~> user --> node + get_relationship(user, term) ~~~> user --> node --> term + get_relationship(user, file, term) ~~~> user --> file --> node --> term + + For a full description of the module, visit the project page: http://drupal.org/project/relation To submit bug reports and feature suggestions, or to track changes: diff --git a/relation.field.inc b/relation.field.inc new file mode 100644 index 0000000000000000000000000000000000000000..4e2ddd8a6bd22a32827e4cf7a087ca4e7058ba59 --- /dev/null +++ b/relation.field.inc @@ -0,0 +1,174 @@ + array( + 'label' => t('Relation'), + 'description' => t('Stores relationships between entities.'), + 'settings' => array( + 'allowed_values' => '', + 'allowed_values_function' => '', + ), + 'default_widget' => 'relation_default', + 'default_formatter' => 'relation_default', + ), + ); +} + +/** + * Implements hook_field_is_empty(). + */ +function relation_field_is_empty() { + return FALSE; +} + +/** + * Implements hook_field_insert(). + */ +function relation_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + $insert1 = db_insert('relation')->fields(array('predicate')); + $insert2 = db_insert('relation_data')->fields(array('relation_id', 'entity_type', 'entity_id')); + foreach ($items as $item) { + if (!empty($item['entity_id'])) { + $relation_id = $insert1->values(array($field['field_name']))->execute(); + $insert2->values(array($relation_id, $item['entity_type'], $item['entity_id'])); + $insert2->values(array($relation_id, $entity_type, $entity_id)); + } + } + $insert2->execute(); + $items = array(); +} + +/** + * Implements hook_field_update(). + */ +function relation_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + relation_field_delete($entity_type, $entity, $field, $instance, $langcode, $items); + relation_field_insert($entity_type, $entity, $field, $instance, $langcode, $items); +} + +/** + * Implements hook_field_delete(). + */ +function relation_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { + list($entity_id) = entity_extract_ids($entity_type, $entity); + $result = db_query('SELECT relation_id FROM {relation_data} WHERE entity_type = :entity_type AND entity_id = :entity_id', array( + ':entity_type' => $entity_type, + ':entity_id' => $entity_id, + )); + foreach ($result as $row) { + db_delete('relation')->condition('relation_id', $row->relation_id)->execute(); + db_delete('relation_data')->condition('relation_id', $row->relation_id)->execute(); + } +} + +/** + * Implements hook_field_load(). + */ +function relation_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { + foreach ($entities as $entity) { + list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + $entity_ids[] = $id; + } + $query = db_select('relation', 'base') + ->condition('l.entity_type', $entity_type) + ->condition('l.entity_id', $entity_ids); + _relation_entity_query_helper($query); + foreach ($query->execute() as $item) { + $items[$item->left_entity_id][] = array( + 'relation_id' => $item->relation_id, + 'predicate' => $item->predicate, + 'entity_id' => $item->right_entity_id, + 'entity_type' => $item->right_entity_type, + ); + } +} + +/** + * Implements hook_field_widget_info(). + */ +function relation_field_widget_info() { + return array( + 'relation_default' => array( + 'label' => t('Relation selector'), + 'field types' => array('relation'), + ), + ); +} + +/** + * Implements hook_field_widget_form(). + */ +function relation_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + $element['#type'] = 'fieldset'; + $element['entity_type'] = array( + '#type' => 'select', + '#title' => t('Entity type'), + '#options' => drupal_map_assoc(array_keys(entity_get_info())), + '#default_value' => isset($items[$delta]) ? $items[$delta]['entity_type'] : '', + ); + $element['entity_id'] = array( + '#title' => t('Entity ID'), + '#type' => 'textfield', + '#default_value' => isset($items[$delta]) ? $items[$delta]['entity_id'] : '', + ); + return $element; +} + +/** + * Implements hook_field_formatter_info(). + */ +function relation_field_formatter_info() { + return array( + 'relation_default' => array( + 'label' => t('Default'), + 'field types' => array('relation'), + ), + ); +} + +/** + * Implements hook_field_formatter_view(). + */ +function relation_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $list = array(); + foreach ($items as $item) { + $uri = entity_uri($item['entity_type'], $item['entity']); + $list[] = l($item['entity_type'], $uri['path'], $uri['options']); + } + return array( + '#theme' => 'item_list', + '#items' => $list, + ); +} + +/** + * Implements hook_field_formatter_prepare_view(). + */ +function relation_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { + $entities_to_load = array(); + foreach ($items as $key => $item) { + foreach ($item as $delta => $value) { + $entities_to_load[$value['entity_type']][] = $value['entity_id']; + $lookup[$value['entity_type']][$value['entity_id']][] = array($key, $delta); + } + } + foreach ($entities_to_load as $entity_type => $ids) { + $entities = entity_load($entity_type, $ids); + foreach ($entities as $entity_id => $entity) { + foreach ($lookup[$entity_type][$entity_id] as $data) { + $items[$data[0]][$data[1]]['entity'] = $entity; + } + } + } +} diff --git a/relation.info b/relation.info index 67fda9f8ff70fbedd6f1ba997a9c486cf325cea4..e41edbdc7e39be2c3eb7df30fe58cde57d1ae2f9 100644 --- a/relation.info +++ b/relation.info @@ -1,8 +1,6 @@ ; $Id$ name = Relation description = Describes relationships between entities. -;package = Multilingual core = 7.x -;dependencies[] = locale files[] = relation.module files[] = tests/relation.test diff --git a/relation.install b/relation.install index 1af235c90b0d93ffba8cf86ae5ebef5c32953992..93375263ef424edb8ce6c0591c75c25eb90cc73d 100644 --- a/relation.install +++ b/relation.install @@ -6,3 +6,58 @@ * Installation functions for Relation module. */ +/** + * Implements hook_schema(). + */ +function relation_schema() { + $schema['relation'] = array( + 'description' => 'Stores predicates of relations.', + 'fields' => array( + 'relation_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'predicate' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('relation_id'), + 'indexes' => array( + 'predicates' => array('predicate', 'relation_id'), + ), + ); + $schema['relation_data'] = array( + 'description' => 'Stores relations between entities.', + 'fields' => array( + 'relation_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('relation_id', 'entity_type', 'entity_id'), + 'indexes' => array( + 'relation_id' => array('relation_id'), + 'entity' => array('entity_type', 'entity_id'), + ), + ); + return $schema; +} + diff --git a/relation.module b/relation.module index 5d389670ad49f0bcc69b0f4cd70377dc967e19fc..566f402238b6919a59a008eddda2031b04cb517f 100644 --- a/relation.module +++ b/relation.module @@ -6,3 +6,128 @@ * Describes relationships between entities. */ +// Load field hook implementations. +// @see http://drupal.org/node/977052 +require_once dirname(__FILE__) . '/relation.field.inc'; + +/** + * Implements hook_entity_info(). + */ +function relation_entity_info() { + $entities['relation'] = array( + 'label' => t('Relation'), + 'base table' => 'relation', + 'fieldable' => FALSE, + 'controller class' => 'RelationEntityController', + 'entity keys' => array( + 'id' => 'relation_id', + 'bundle' => 'predicate', + ), + 'bundle keys' => array( + 'bundle' => 'predicate', + ), + 'bundles' => array( + // Describe Drupal's built-in relationships. + 'creator' => array( + 'label' => t('Author'), + ), + ), + 'view modes' => array(), + ); + return $entities; +} + +/** + * Implements hook_entity_info_alter(). + */ +function relation_entity_info_alter(&$entity_info) { + $entity_info['node']['relationships']['user'] = array( + // a handler -- this can be reusable or specific to the entity type + // it will do things like list 'user' entities related to one or more nodes + 'handler' => 'RelationEntityRelation', + ); +} + +/** + * Implements hook_field_info_alter(). + */ +function relation_field_info_alter(&$field_info) { + $field_info['file']['relationships']['files'] = array( + 'handler' => 'RelationFieldRelation', + ); +} + +/** + * Controller class for entity relations. + * + * This extends the DrupalDefaultEntityController class. The buildQuery method + * is overriden to add the self join and to exclude rows where the left and + * right entities are identical. + */ +class RelationEntityController extends DrupalDefaultEntityController { + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + $query = parent::buildQuery($ids, $conditions, $revision_id); + _relation_entity_query_helper($query); + return $query; + } +} + +function _relation_entity_query_helper($query) { + // Add the machine name field from the {taxonomy_vocabulary} table. + $query->innerJoin('relation_data', 'l', 'base.relation_id = l.relation_id'); + $query->innerJoin('relation_data', 'r', 'base.relation_id = r.relation_id AND NOT (l.entity_type = r.entity_type AND l.entity_id = r.entity_id)'); + $query->addField('base', 'relation_id'); + $query->addField('base', 'predicate'); + $query->addField('l', 'entity_type', 'left_entity_type'); + $query->addField('l', 'entity_id', 'left_entity_id'); + $query->addField('r', 'entity_type', 'right_entity_type'); + $query->addField('r', 'entity_id', 'right_entity_id'); +} + +/** + * Interface for relationship handlers. + */ +interface RelationInterface { + // bangpound + public function getRelated($entity, $type); + + // becw + function init($left, $right); // sets types + function set_left($entity_ids = array()); // sets left objects + function set_right($entity_ids = array()); // sets right objects + function get_left(); // returns left + function get_right(); // returns right +} + +/** + * Handler class for entity relations. + */ +class RelationHandler implements RelationInterface { + + function __construct() { + } + + /** + * Entity is a fully loaded entity (node, user, term, etc.) + * Type is the predicate. + */ + public function getRelated($entity, $type) { + return NULL; + } + + function init($left, $right) { + } + + function set_left($entity_ids = array()) { + } + + function set_right($entity_ids = array()) { + } + + function get_left() { + } + + function get_right() { + } +} +