summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsun2010-11-24 20:57:49 (GMT)
committer sun2010-11-24 20:57:49 (GMT)
commitcaefa4c9e6344e02fcb65e5e11d0ceae397cb21a (patch)
treeae53757d2023ad4d6fc0c9a6313655930ea32e10
parent422452ffd350fb6c37cb09adc1a2ecc6e2743a47 (diff)
#977016 by sun: Moved Awesomerelationship into Relation project.
-rw-r--r--CHANGELOG.txt1
-rw-r--r--README.txt86
-rw-r--r--relation.field.inc174
-rw-r--r--relation.info2
-rw-r--r--relation.install55
-rw-r--r--relation.module125
6 files changed, 441 insertions, 2 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 55060e4..179d382 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 165d544..f0f9929 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 0000000..4e2ddd8
--- /dev/null
+++ b/relation.field.inc
@@ -0,0 +1,174 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * A field storing arbitrary relations between entities.
+ */
+
+/**
+ * Implements hook_field_info().
+ */
+function relation_field_info() {
+ return array(
+ 'relation' => 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 67fda9f..e41edbd 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 1af235c..9337526 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 5d38967..566f402 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() {
+ }
+}
+