Index: install.php =================================================================== RCS file: /cvs/drupal/drupal/install.php,v retrieving revision 1.190 diff -u -p -r1.190 install.php --- install.php 30 Jul 2009 19:32:19 -0000 1.190 +++ install.php 11 Aug 2009 00:35:32 -0000 @@ -248,6 +248,7 @@ function install_begin_request(&$install // Load module basics (needed for hook invokes). include_once DRUPAL_ROOT . '/includes/module.inc'; include_once DRUPAL_ROOT . '/includes/session.inc'; + include_once DRUPAL_ROOT . '/includes/entity.inc'; $module_list['system']['filename'] = 'modules/system/system.module'; $module_list['filter']['filename'] = 'modules/filter/filter.module'; module_list(TRUE, FALSE, $module_list); Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.956 diff -u -p -r1.956 common.inc --- includes/common.inc 8 Aug 2009 20:52:32 -0000 1.956 +++ includes/common.inc 11 Aug 2009 00:35:39 -0000 @@ -4917,3 +4917,91 @@ function _drupal_flush_css_js() { } variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19)); } + +/** + * Get the entity info array of an entity type. + * + * @see hook_entity_info() + * @see hook_entity_info_alter() + * + * @param $entity_type + * The entity type, e.g. node, for which the info shall be returned, or NULL + * to return an array with info about all types. + */ +function drupal_get_entity_info($entity_type = NULL) { + // We statically cache the information returned by hook_entity_info(). + $entity_info = &drupal_static(__FUNCTION__, array()); + + if (empty($entity_info)) { + if ($cache = cache_get('entity_info')) { + $entity_info = $cache->data; + } + else { + $entity_info = module_invoke_all('entity_info'); + // Merge in default values. + foreach ($entity_info as $name => $data) { + $entity_info[$name] += array( + 'fieldable' => FALSE, + 'loader class' => 'DrupalDefaultEntityLoader', + 'static cache' => TRUE, + ); + } + // Let other modules alter the entity info. + drupal_alter('entity_info', $entity_info); + cache_set('entity_info', $entity_info); + } + } + + return empty($entity_type) ? $entity_info : $entity_info[$entity_type]; +} + +/** + * Load entities from the database. + * + * This function should be used whenever you need to load more than one entity + * from the database. The entities are loaded into memory and will not require + * database access if loaded again during the same page request. + * + * The actual loading is done through a class that has to implement the + * DrupalEntityLoader interface. By default, DrupalDefaultEntityLoader is used. + * Entity types can specify that a different class should be used by setting + * the 'loader class' key in hook_entity_info(). These classes can either + * implement the DrupalEntityLoader interface, or, most commonly, extend the + * DrupalDefaultEntityLoader class. See node_entity_info() and the class + * NodeLoader in node.module as an example. + * + * @see hook_entity_info() + * @see DrupalEntityLoader + * @see DrupalDefaultEntityLoader + * + * @param $entity_type + * The entity type to load, e.g. node or user. + * @param $ids + * An array of entity IDs, or FALSE to load all entities. + * @param $conditions + * An array of conditions on the entity base table in the form 'field' => $value. + * @param $reset + * Whether to reset the internal cache for the requested entity type. + * + * @return + * An array of entity objects indexed by their ids. + */ +function entity_load_multiple($entity_type, $ids = array(), $conditions = array(), $reset = FALSE) { + if ($reset) { + entity_get_loader($entity_type)->resetCache(); + } + return entity_get_loader($entity_type)->loadMultiple($ids, $conditions); +} + +/** + * Get the entity loader class for an entity type. + */ +function entity_get_loader($entity_type) { + $loaders = &drupal_static(__FUNCTION__, array()); + if (!isset($loaders[$entity_type])) { + $type_info = drupal_get_entity_info($entity_type); + $class = $type_info['loader class']; + $loaders[$entity_type] = new $class($entity_type); + } + return $loaders[$entity_type]; +} Index: includes/entity.inc =================================================================== RCS file: includes/entity.inc diff -N includes/entity.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/entity.inc 11 Aug 2009 00:35:39 -0000 @@ -0,0 +1,281 @@ + $value. + * + * @return + * An array of entity objects indexed by their ids. + */ + public function loadMultiple($ids = array(), $conditions = array()); +} + +/** + * Default implementation of DrupalEntityLoaderInterface. + * + * This class can be used as-is by most simple entity types. Entity types + * requiring special handling can extend the class. + */ +class DrupalDefaultEntityLoader implements DrupalEntityLoaderInterface { + + protected $entityCache; + protected $entityType; + protected $entityInfo; + protected $hookLoadArguments; + protected $idKey; + protected $revisionKey; + protected $revisionTable; + protected $query; + + /** + * Constructor. Set basic variables. + */ + public function __construct($entityType) { + $this->entityType = $entityType; + $this->entityInfo = drupal_get_entity_info($entityType); + $this->entityCache = array(); + $this->hookLoadArguments = array(); + $this->idKey = $this->entityInfo['object keys']['id']; + + // Check if the entity type supports revisions. + if (isset($this->entityInfo['object keys']['revision'])) { + $this->revisionKey = $this->entityInfo['object keys']['revision']; + $this->revisionTable = $this->entityInfo['revision table']; + } + else { + $this->revisionKey = FALSE; + } + + // Check if the entity type supports static caching of loaded entities. + $this->cache = !empty($this->entityInfo['static cache']); + } + + public function resetCache() { + $this->entityCache = array(); + } + + public function loadMultiple($ids = array(), $conditions = array()) { + $this->ids = $ids; + $this->conditions = $conditions; + + $entities = array(); + + // Revisions are not statically cached, and require a different query to + // other conditions, so separate the revision id into its own variable. + if ($this->revisionKey && isset($this->conditions[$this->revisionKey])) { + $this->revisionId = $this->conditions[$this->revisionKey]; + unset($this->conditions[$this->revisionKey]); + } + else { + $this->revisionId = FALSE; + } + + + // Create a new variable which is either a prepared version of the $ids + // array for later comparison with the entity cache, or FALSE if no $ids were + // passed. The $ids array is reduced as items are loaded from cache, and we + // need to know if it's empty for this reason to avoid querying the database + // when all requested entities are loaded from cache. + $passed_ids = !empty($this->ids) ? array_flip($this->ids) : FALSE; + // Try to load entities from the static cache, if the entity type supports + // static caching. + if ($this->cache) { + $entities += $this->cacheGet($this->ids, $this->conditions); + // If any entities were loaded, remove them from the ids still to load. + if ($passed_ids) { + $this->ids = array_keys(array_diff_key($passed_ids, $entities)); + } + } + + // Load any remaining entities from the database. This is the case if $ids + // is set to FALSE (so we load all entities), if there are any ids left to + // load, if loading a revision, or if $conditions was passed without $ids. + if ($this->ids === FALSE || $this->ids || $this->revisionId || ($this->conditions && !$passed_ids)) { + // Build the query. + $this->buildQuery(); + $queried_entities = $this->query + ->execute() + ->fetchAllAssoc($this->idKey); + } + + // Pass all entities loaded from the database through $this->attachLoad(), + // which attaches fields (if supported by the entity type) and calls the + // entity type specific load callback. + if (!empty($queried_entities)) { + $this->attachLoad($queried_entities); + $entities += $queried_entities; + } + + if ($this->cache) { + // Add entities to the cache if we're not loading a revision. + if (!empty($queried_entities) && !$this->revisionId) { + $this->cacheSet($queried_entities); + } + // Ensure that the returned array is ordered the same as the original $ids + // array if this was passed in and remove any invalid ids. + if ($passed_ids) { + // Remove any invalid ids from the array. + $passed_ids = array_intersect_key($passed_ids, $entities); + foreach ($entities as $entity) { + $passed_ids[$entity->{$this->idKey}] = $entity; + } + $entities = $passed_ids; + } + } + + return $entities; + } + + /** + * Build the query to load the entity. + * + * This has full revision support. For entities requiring special queries, + * the class can be extendend, and the default query can be constructed by + * calling parent::buildQuery(). See NodeLoader::buildQuery() for an example. + */ + protected function buildQuery() { + $this->query = db_select($this->entityInfo['base table'], 'base'); + + $this->query->addTag($this->entityType . '_load_multiple'); + + if ($this->revisionId) { + $this->query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $this->revisionId)); + } + elseif ($this->revisionKey) { + $this->query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}"); + } + + // Add fields from the {entity} table. + $entity_fields = drupal_schema_fields_sql($this->entityInfo['base table']); + + if ($this->revisionKey) { + // Add all fields from the {entity_revision} table. + $entity_revision_fields = drupal_schema_fields_sql($this->revisionTable); + // The id field is provided by entity, so remove it. + unset($entity_revision_fields[$this->idKey]); + + // Change timestamp to revision_timestamp before adding it to the query. + // TODO: This is node specific and has to be moved into NodeLoader. + unset($entity_revision_fields['timestamp']); + $this->query->addField('revision', 'timestamp', 'revision_timestamp'); + + // Remove all fields from the base table that are also fields by the same + // name in the revision table. + $entity_field_keys = array_flip($entity_fields); + foreach ($entity_revision_fields as $key => $name) { + if (isset($entity_field_keys[$name])) { + unset($entity_fields[$entity_field_keys[$name]]); + } + } + $this->query->fields('revision', $entity_revision_fields); + } + + $this->query->fields('base', $entity_fields); + + if ($this->ids) { + $this->query->condition("base.{$this->idKey}", $this->ids, 'IN'); + } + if ($this->conditions) { + foreach ($this->conditions as $field => $value) { + $this->query->condition('base.' . $field, $value); + } + } + } + + /** + * Attach data to entities upon loading. + * + * This will attach fields, if the entity is fieldable. It also calls + * hook_entityType_load() on the loaded entities. If your hook_entityType_load() + * expects special parameters apart from the queried entities, you can set + * $this->hookLoadArguments prior to calling the method. + * See NodeLoader::attachLoad() for an example. + */ + protected function attachLoad(&$queried_entities) { + // Attach fields. + if ($this->entityInfo['fieldable']) { + if ($this->revisionId) { + field_attach_load_revision($this->entityType, $queried_entities); + } + else { + field_attach_load($this->entityType, $queried_entities); + } + } + + // Call hook_entityType_load(). The first argument for hook_TYPE_load() are + // always the queried entities, followed by additional arguments set in + // $this->hookLoadArguments. + $args = array_merge(array($queried_entities), $this->hookLoadArguments); + foreach (module_implements($this->entityType . '_load') as $module) { + call_user_func_array($module . '_' . $this->entityType . '_load', $args); + } + } + + /** + * Get entities from the static cache. + * + * @param $ids + * If not empty, return entities that match these IDs. + * @param $conditions + * If set, return entities that match all of these conditions. + */ + protected function cacheGet($ids, $conditions = array()) { + $entities = array(); + // Load any available entities from the internal cache. + if (!empty($this->entityCache) && !$this->revisionId) { + if ($ids) { + $entities += array_intersect_key($this->entityCache, array_flip($ids)); + } + // If loading entities only by conditions, fetch all available entities from + // the cache. Entities which don't match are removed later. + elseif ($conditions) { + $entities = $this->entityCache; + } + } + + // Exclude any entities loaded from cache if they don't match $conditions. + // This ensures the same behavior whether loading from memory or database. + if ($conditions) { + foreach ($entities as $entity) { + $entity_values = (array) $entity; + if (array_diff_assoc($conditions, $entity_values)) { + unset($entities[$entity->{$this->idKey}]); + } + } + } + return $entities; + } + + /** + * Store entities in the static entity cache. + */ + protected function cacheSet($entities) { + $this->entityCache += $entities; + } +} Index: includes/file.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/file.inc,v retrieving revision 1.179 diff -u -p -r1.179 file.inc --- includes/file.inc 2 Aug 2009 05:43:54 -0000 1.179 +++ includes/file.inc 11 Aug 2009 00:35:39 -0000 @@ -490,34 +490,12 @@ function file_check_location($source, $d * @return * An array of file objects, indexed by fid. * + * @see entity_load_multiple() * @see hook_file_load() * @see file_load() */ function file_load_multiple($fids = array(), $conditions = array()) { - $query = db_select('files', 'f')->fields('f'); - - // If the $fids array is populated, add those to the query. - if ($fids) { - $query->condition('f.fid', $fids, 'IN'); - } - - // If the conditions array is populated, add those to the query. - if ($conditions) { - foreach ($conditions as $field => $value) { - $query->condition('f.' . $field, $value); - } - } - $files = $query->execute()->fetchAllAssoc('fid'); - - // Invoke hook_file_load() on the terms loaded from the database - // and add them to the static cache. - if (!empty($files)) { - foreach (module_implements('file_load') as $module) { - $function = $module . '_file_load'; - $function($files); - } - } - return $files; + return entity_load_multiple('file', $fids, $conditions); } /** Index: misc/ajax.js =================================================================== RCS file: misc/ajax.js diff -N misc/ajax.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/ajax.js 11 Aug 2009 00:35:41 -0000 @@ -0,0 +1,391 @@ +// $Id: AJAX.js,v 1.17 2009/07/16 02:46:13 dries Exp $ +(function ($) { + +/** + * Provides AJAX-like page updating via AJAX (Asynchronous HTML and HTTP). + * + * AJAX is a method of making a request via Javascript while viewing an HTML + * page. The request returns an array of commands encoded in JSON, which is + * then executed to make whatever hanges are necessary to the page. + * + * Drupal uses this file to enhance form elements with #ajax[path] and + * #ajax[wrapper] properties. If set, this file will automatically be included + * to provide AJAX capabilities. + */ + +Drupal.AJAX = Drupal.AJAX || {}; + +/** + * Attaches the AJAX behavior to each AJAX form element. + */ +Drupal.behaviors.AJAX = { + attach: function (context, settings) { + for (var base in settings.ajax) { + if (!$('#' + base + '.ajax-processed').size()) { + var element_settings = settings.ajax[base]; + + $(element_settings.selector).each(function () { + Drupal.AJAX[base] = new Drupal.AJAX(base, this, element_settings); + }); + + $('#' + base).addClass('ajax-processed'); + } + } + + // Also bind based on classes. + $('.use-ajax:not(.ajax-processed)').each(function() { + var element_settings = { }; + + // For anchor tags, these will go to the target of the anchor rather + // than the usual location. + if ($(this).attr('href')) { + element_settings.url = $(this).attr('href'); + } + var base = $(this).attr('id'); + Drupal.AJAX[base] = new Drupal.AJAX(base, this, element_settings); + }).addClass('ajax-processed'); + + // This class means to submit the form to the action using ajax. + $('.use-ajax-submit:not(.ajax-processed)').each(function() { + var element_settings = { }; + + element_settings.url = $(this.form).attr('action'); + element_settings.set_click = TRUE; + + var base = $(this).attr('id'); + Drupal.AJAX[base] = new Drupal.AJAX(base, this, element_settings); + }).addClass('ajax-processed'); + } +}; + +/** + * AJAX object. + * + * All AJAX objects on a page are accessible through the global Drupal.AJAX object + * and are keyed by the submit button's ID. You can access them from your module's + * JavaScript file to override properties or functions. + * For example, if your AJAX enabled button has the ID 'edit-submit', you can + * redefine the function that is called to insert the new content like this + * (inside a Drupal.behaviors attach block): + * @code + * Drupal.behaviors.myCustomAJAXStuff = { + * attach: function(context, settings) { + * Drupal.AJAX['edit-submit'].insertNewContent = function(response, status) { + * new_content = $(response.data); + * $('#my-wrapper').append(new_content); + * alert('New content was appended to #my-wrapper'); + * } + * } + * }; + * @endcode + */ +Drupal.AJAX = function (base, element, element_settings) { + var defaults = { + url: "system/ajax", + event: "mousedown", + keypress: true, + selector: "#" + base, + effect: "none", + speed: "slow", + method: "replace", + progress: { + type: "bar", + message: "Please wait..." + }, + button: { }, + }; + + jQuery.extend(this, defaults, element_settings); + + this.element = element; + + // Replacing nojs with ajax allows for an easy method to detect when we + // need to degrade. + this.url = element_settings.url.replace('/nojs/', '/ajax/'); + this.wrapper = '#' + element_settings.wrapper; + + // If there isn't a form, jQuery.ajax will be used instead, allowing us to + // bind ajax to links as well. + if (this.element.form) { + this.form = $(this.element.form); + } + + // Set the options for the ajaxSubmit function. + // The 'this' variable will not persist inside of the options object. + var AJAX = this; + var options = { + url: AJAX.url, + data: AJAX.button, + beforeSubmit: function (form_values, element_settings, options) { + return AJAX.beforeSubmit(form_values, element_settings, options); + }, + success: function (response, status) { + // Sanity check for browser support (object expected). + // When using iFrame uploads, responses must be returned as a string. + if (typeof response == 'string') { + response = Drupal.parseJson(response); + } + return AJAX.success(response, status); + }, + complete: function (response, status) { + if (status == 'error' || status == 'parsererror') { + return AJAX.error(response, AJAX.url); + } + }, + dataType: 'json', + type: 'POST' + }; + + // Bind the ajaxSubmit function to the element event. + $(this.element).bind(element_settings.event, function () { + if (AJAX.form) { + // If setClick is set, we must set this to ensure that the button's + // value is passed. + if (AJAX.setClick) { + // Put our button in. + AJAX.form.clk = this.element; + } + + AJAX.form.ajaxSubmit(options); + } + else { + jQuery.ajax(options); + } + + return false; + }); + // If necessary, enable keyboard submission so that AJAX behaviors + // can be triggered through keyboard input as well as e.g. a mousedown + // action. + if (element_settings.keypress) { + $(element_settings.element).keypress(function (event) { + // Detect enter key. + if (event.keyCode == 13) { + $(element_settings.element).trigger(element_settings.event); + return false; + } + }); + } +}; + +/** + * Handler for the form redirection submission. + */ +Drupal.AJAX.prototype.beforeSubmit = function (form_values, element, options) { + // Disable the element that received the change. + $(this.element).addClass('progress-disabled').attr('disabled', true); + + // Insert progressbar or throbber. + if (this.progress.type == 'bar') { + var progressBar = new Drupal.progressBar('AJAX-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); + if (this.progress.message) { + progressBar.setProgress(-1, this.progress.message); + } + if (this.progress.url) { + progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); + } + this.progress.element = $(progressBar.element).addClass('AJAX-progress AJAX-progress-bar'); + this.progress.object = progressBar; + $(this.element).after(this.progress.element); + } + else if (this.progress.type == 'throbber') { + this.progress.element = $('
node_comment_statistics
table so that the
* comment_count
field is available, a title attribute will
* be added to show the number of comments.
@@ -673,12 +677,14 @@ function node_invoke($node, $hook, $a2 =
}
/**
- * Load node objects from the database.
+ * Load node entities from the database.
*
* This function should be used whenever you need to load more than one node
* from the database. Nodes are loaded into memory and will not require
* database access if loaded again during the same page request.
*
+ * @see entity_load_multiple()
+ *
* @param $nids
* An array of node IDs.
* @param $conditions
@@ -690,144 +696,7 @@ function node_invoke($node, $hook, $a2 =
* An array of node objects indexed by nid.
*/
function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
- $node_cache = &drupal_static(__FUNCTION__, array());
-
- if ($reset) {
- $node_cache = array();
- }
- $nodes = array();
-
- // Create a new variable which is either a prepared version of the $nids
- // array for later comparison with the node cache, or FALSE if no $nids were
- // passed. The $nids array is reduced as items are loaded from cache, and we
- // need to know if it's empty for this reason to avoid querying the database
- // when all requested nodes are loaded from cache.
- $passed_nids = !empty($nids) ? array_flip($nids) : FALSE;
-
- // Revisions are not statically cached, and require a different query to
- // other conditions, so separate vid into its own variable.
- $vid = isset($conditions['vid']) ? $conditions['vid'] : FALSE;
- unset($conditions['vid']);
-
- // Load any available nodes from the internal cache.
- if ($node_cache && !$vid) {
- if ($nids) {
- $nodes += array_intersect_key($node_cache, $passed_nids);
- // If any nodes were loaded, remove them from the $nids still to load.
- $nids = array_keys(array_diff_key($passed_nids, $nodes));
- }
- // If loading nodes only by conditions, fetch all available nodes from
- // the cache. Nodes which don't match are removed later.
- elseif ($conditions) {
- $nodes = $node_cache;
- }
- }
-
- // Exclude any nodes loaded from cache if they don't match $conditions.
- // This ensures the same behavior whether loading from memory or database.
- if ($conditions) {
- foreach ($nodes as $node) {
- $node_values = (array) $node;
- if (array_diff_assoc($conditions, $node_values)) {
- unset($nodes[$node->nid]);
- }
- }
- }
-
- // Load any remaining nodes from the database. This is the case if there are
- // any $nids left to load, if loading a revision, or if $conditions was
- // passed without $nids.
- if ($nids || $vid || ($conditions && !$passed_nids)) {
- $query = db_select('node', 'n');
-
- if ($vid) {
- $query->join('node_revision', 'r', 'r.nid = n.nid AND r.vid = :vid', array(':vid' => $vid));
- }
- else {
- $query->join('node_revision', 'r', 'r.vid = n.vid');
- }
-
- // Add fields from the {node} table.
- $node_fields = drupal_schema_fields_sql('node');
-
- // vid and title are provided by node_revision, so remove them.
- unset($node_fields['vid']);
- unset($node_fields['title']);
- $query->fields('n', $node_fields);
-
- // Add all fields from the {node_revision} table.
- $node_revision_fields = drupal_schema_fields_sql('node_revision');
-
- // nid is provided by node, so remove it.
- unset($node_revision_fields['nid']);
-
- // Change timestamp to revision_timestamp before adding it to the query.
- unset($node_revision_fields['timestamp']);
- $query->addField('r', 'timestamp', 'revision_timestamp');
- $query->fields('r', $node_revision_fields);
-
- if ($nids) {
- $query->condition('n.nid', $nids, 'IN');
- }
- if ($conditions) {
- foreach ($conditions as $field => $value) {
- $query->condition('n.' . $field, $value);
- }
- }
- $queried_nodes = $query->execute()->fetchAllAssoc('nid');
- }
-
- // Pass all nodes loaded from the database through the node type specific
- // callbacks and hook_node_load(), then add them to the internal cache.
- if (!empty($queried_nodes)) {
- // Create an array of nodes for each content type and pass this to the
- // node type specific callback.
- $typed_nodes = array();
- foreach ($queried_nodes as $nid => $node) {
- $typed_nodes[$node->type][$nid] = $node;
- }
-
- // Call node type specific callbacks on each typed array of nodes.
- foreach ($typed_nodes as $type => $nodes_of_type) {
- if (node_hook($type, 'load')) {
- $function = node_type_get_base($type) . '_load';
- $function($nodes_of_type);
- }
- }
-
- // Attach fields.
- if ($vid) {
- field_attach_load_revision('node', $queried_nodes);
- }
- else {
- field_attach_load('node', $queried_nodes);
- }
-
- // Call hook_node_load(), pass the node types so modules can return early
- // if not acting on types in the array.
- foreach (module_implements('node_load') as $module) {
- $function = $module . '_node_load';
- $function($queried_nodes, array_keys($typed_nodes));
- }
- $nodes += $queried_nodes;
- // Add nodes to the cache if we're not loading a revision.
- if (!$vid) {
- $node_cache += $queried_nodes;
- }
- }
-
- // Ensure that the returned array is ordered the same as the original $nids
- // array if this was passed in and remove any invalid nids.
- if ($passed_nids) {
- // Remove any invalid nids from the array.
- $passed_nids = array_intersect_key($passed_nids, $nodes);
- foreach ($nodes as $node) {
- $passed_nids[$node->nid] = $node;
- }
- $nodes = $passed_nids;
- }
-
- return $nodes;
+ return entity_load_multiple('node', $nids, $conditions, $reset);
}
/**
@@ -846,7 +715,6 @@ function node_load_multiple($nids = arra
function node_load($nid, $vid = array(), $reset = FALSE) {
$vid = isset($vid) ? array('vid' => $vid) : NULL;
$node = node_load_multiple(array($nid), $vid, $reset);
-
return $node ? $node[$nid] : FALSE;
}
@@ -3147,3 +3015,30 @@ function node_requirements($phase) {
);
return $requirements;
}
+
+/**
+ * Loader class for nodes.
+ *
+ * This extends the DrupalDefaultEntityLoader class, adding required special
+ * handling for node objects.
+ */
+class NodeLoader extends DrupalDefaultEntityLoader {
+ protected function attachLoad(&$nodes) {
+ // Create an array of nodes for each content type and pass this to the
+ // object type specific callback.
+ $typed_nodes = array();
+ foreach ($nodes as $id => $object) {
+ $typed_nodes[$object->type][$id] = $object;
+ }
+
+ // Call object type specific callbacks on each typed array of nodes.
+ foreach ($typed_nodes as $node_type => $nodes_of_type) {
+ if (node_hook($node_type, 'load')) {
+ $function = node_type_get_base($node_type) . '_load';
+ $function($nodes_of_type);
+ }
+ }
+ $this->hookLoadArguments[] = array_keys($typed_nodes);
+ parent::attachLoad($nodes);
+ }
+}
Index: modules/simpletest/tests/field_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v
retrieving revision 1.13
diff -u -p -r1.13 field_test.module
--- modules/simpletest/tests/field_test.module 10 Jul 2009 05:58:13 -0000 1.13
+++ modules/simpletest/tests/field_test.module 11 Aug 2009 00:35:53 -0000
@@ -60,7 +60,7 @@ function field_test_menu() {
/**
* Define a test fieldable entity.
*/
-function field_test_fieldable_info() {
+function field_test_entity_info() {
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
return array(
'test_entity' => array(
@@ -72,6 +72,7 @@ function field_test_fieldable_info() {
),
'cacheable' => FALSE,
'bundles' => $bundles,
+ 'fieldable' => TRUE,
),
// This entity type doesn't get form handling for now...
'test_cacheable_entity' => array(
@@ -640,4 +641,4 @@ function field_test_memorize($key = NULL
function field_test_field_create_field($field) {
$args = func_get_args();
field_test_memorize(__FUNCTION__, $args);
-}
\ No newline at end of file
+}
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.169
diff -u -p -r1.169 system.admin.inc
--- modules/system/system.admin.inc 5 Aug 2009 19:40:55 -0000 1.169
+++ modules/system/system.admin.inc 11 Aug 2009 00:35:57 -0000
@@ -953,6 +953,7 @@ function system_modules_submit($form, &$
drupal_theme_rebuild();
node_types_rebuild();
cache_clear_all('schema', 'cache');
+ cache_clear_all('entity_info', 'cache');
drupal_clear_css_cache();
drupal_clear_js_cache();
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.59
diff -u -p -r1.59 system.api.php
--- modules/system/system.api.php 8 Aug 2009 22:52:59 -0000 1.59
+++ modules/system/system.api.php 11 Aug 2009 00:35:57 -0000
@@ -12,6 +12,110 @@
*/
/**
+ * Inform the base system and the Field API about one or more entity types.
+ *
+ * Inform the system about one or more entity types (i.e., object types that can
+ * be loaded via entity_load_multiple() and, optionally, to which fields can be
+ * attached).
+ *
+ * @see entity_load_multiple()
+ * @see hook_entity_info_alter()
+ *
+ * @return
+ * An array whose keys are entity type names and whose values identify
+ * properties of those types that the system needs to know about:
+ *
+ * name: The human-readable name of the type.
+ * loader class: The name of the class that is used to load the objects.
+ * The class has to implement the DrupalEntityLoader interface. Leave blank
+ * to use the DefaultDrupalEntityLoader implementation.
+ * base table: (used by DefaultDrupalEntityLoader) The name of the entity
+ * type's base table.
+ * static cache: (used by DefaultDrupalEntityLoader) FALSE to disable static
+ * caching of loaded entities during a page request. Defaults to TRUE.
+ * fieldable: Set to TRUE if you want your entity type to be fieldable.
+ * - object keys: An array describing how the Field API can extract the
+ * information it needs from the objects of the type.
+ * - id: The name of the property that contains the primary id of the
+ * object. Every object passed to the Field API must have this property
+ * and its value must be numeric.
+ * - revision: The name of the property that contains the revision id of
+ * the object. The Field API assumes that all revision ids are unique
+ * across all objects of a type.
+ * This element can be omitted if the objects of this type are not
+ * versionable.
+ * - bundle: The name of the property that contains the bundle name for the
+ * object. The bundle name defines which set of fields are attached to
+ * the object (e.g. what nodes call "content type").
+ * This element can be omitted if this type has no bundles (all objects
+ * have the same fields).
+ * - bundle keys: An array describing how the Field API can extract the
+ * information it needs from the bundle objects for this type (e.g
+ * $vocabulary objects for terms; not applicable for nodes).
+ * This element can be omitted if this type's bundles do not exist as
+ * standalone objects.
+ * - bundle: The name of the property that contains the name of the bundle
+ * object.
+ * cacheable: A boolean indicating whether Field API should cache
+ * loaded fields for each object, reducing the cost of
+ * field_attach_load().
+ * - bundles: An array describing all bundles for this object type.
+ * Keys are bundles machine names, as found in the objects' 'bundle'
+ * property (defined in the 'object keys' entry above).
+ * - label: The human-readable name of the bundle.
+ * - admin: An array of information that allow Field UI pages (currently
+ * implemented in a contributed module) to attach themselves to the
+ * existing administration pages for the bundle.
+ * - path: the path of the bundle's main administration page, as defined
+ * in hook_menu(). If the path includes a placeholder for the bundle,
+ * the 'bundle argument', 'bundle helper' and 'real path' keys below
+ * are required.
+ * - bundle argument: The position of the placeholder in 'path', if any.
+ * - real path: The actual path (no placeholder) of the bundle's main
+ * administration page. This will be used to generate links.
+ * - access callback: As in hook_menu(). 'user_access' will be assumed if
+ * no value is provided.
+ * - access arguments: As in hook_menu().
+ */
+function hook_entity_info() {
+ $return = array(
+ 'node' => array(
+ 'name' => t('Node'),
+ 'loader class' => 'NodeLoader',
+ 'base table' => 'node',
+ 'id key' => 'nid',
+ 'revision key' => 'vid',
+ 'fieldable' => TRUE,
+ 'bundle key' => 'type',
+ // Node.module handles its own caching.
+ // 'cacheable' => FALSE,
+ // Bundles must provide human readable name so
+ // we can create help and error messages about them.
+ 'bundles' => node_type_get_names(),
+ ),
+ );
+ return $return;
+}
+
+/**
+ * Alter the entity info.
+ *
+ * Modules may implement this hook to alter the information that defines an
+ * entity. All properties that are available in hook_entity_info() can be
+ * altered here.
+ *
+ * @see hook_entity_info()
+ *
+ * @param $entity_info
+ * The entity info array, keyed by entity name.
+ */
+function hook_entity_info_alter(&$entity_info) {
+ // Set the loader class for nodes to an alternate implementation of the
+ // DrupalEntityLoader interface.
+ $entity_info['node']['loader class'] = 'MyCustomNodeLoader';
+}
+
+/**
* Perform periodic actions.
*
* Modules that require to schedule some commands to be executed at regular
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.365
diff -u -p -r1.365 system.install
--- modules/system/system.install 8 Aug 2009 20:52:32 -0000 1.365
+++ modules/system/system.install 11 Aug 2009 00:35:59 -0000
@@ -1422,7 +1422,6 @@ function system_update_last_removed() {
return 6049;
}
-
/**
* @defgroup updates-6.x-to-7.x System updates from 6.x to 7.x
* @{
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.737
diff -u -p -r1.737 system.module
--- modules/system/system.module 5 Aug 2009 19:40:55 -0000 1.737
+++ modules/system/system.module 11 Aug 2009 00:36:01 -0000
@@ -255,6 +255,22 @@ function system_rdf_namespaces() {
}
/**
+ * Implement hook_entity_info().
+ */
+function system_entity_info() {
+ return array(
+ 'file' => array(
+ 'label' => t('File'),
+ 'base table' => 'files',
+ 'object keys' => array(
+ 'id' => 'fid',
+ ),
+ 'static cache' => FALSE,
+ ),
+ );
+}
+
+/**
* Implement hook_elements().
*/
function system_elements() {
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.493
diff -u -p -r1.493 taxonomy.module
--- modules/taxonomy/taxonomy.module 4 Aug 2009 06:50:07 -0000 1.493
+++ modules/taxonomy/taxonomy.module 11 Aug 2009 00:36:02 -0000
@@ -19,12 +19,15 @@ function taxonomy_permission() {
}
/**
- * Implement hook_fieldable_info().
+ * Implement hook_entity_info().
*/
-function taxonomy_fieldable_info() {
+function taxonomy_entity_info() {
$return = array(
'taxonomy_term' => array(
'label' => t('Taxonomy term'),
+ 'loader class' => 'TaxonomyTermLoader',
+ 'base table' => 'taxonomy_term_data',
+ 'fieldable' => TRUE,
'object keys' => array(
'id' => 'tid',
'bundle' => 'vocabulary_machine_name',
@@ -35,8 +38,8 @@ function taxonomy_fieldable_info() {
'bundles' => array(),
),
);
- foreach (taxonomy_get_vocabularies() as $vocabulary) {
- $return['taxonomy_term']['bundles'][$vocabulary->machine_name] = array(
+ foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
+ $return['taxonomy_term']['bundles'][$machine_name] = array(
'label' => $vocabulary->name,
'admin' => array(
'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary',
@@ -46,6 +49,16 @@ function taxonomy_fieldable_info() {
),
);
}
+ $return['taxonomy_vocabulary'] = array(
+ 'label' => t('Taxonomy vocabulary'),
+ 'loader class' => 'TaxonomyVocabularyLoader',
+ 'base table' => 'taxonomy_vocabulary',
+ 'object keys' => array(
+ 'id' => 'vid',
+ ),
+ 'fieldable' => FALSE,
+ );
+
return $return;
}
@@ -327,7 +340,7 @@ function taxonomy_vocabulary_save($vocab
}
cache_clear_all();
- drupal_static_reset('taxonomy_vocabulary_load_multiple');
+ entity_get_loader('taxonomy_vocabulary')->resetCache();
return $status;
}
@@ -358,7 +371,7 @@ function taxonomy_vocabulary_delete($vid
module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
cache_clear_all();
- drupal_static_reset('taxonomy_vocabulary_load_multiple');
+ entity_get_loader('taxonomy_vocabulary')->resetCache();
return SAVED_DELETED;
}
@@ -554,7 +567,7 @@ function taxonomy_terms_static_reset() {
drupal_static_reset('taxonomy_term_count_nodes');
drupal_static_reset('taxonomy_get_tree');
drupal_static_reset('taxonomy_get_synonym_root');
- drupal_static_reset('taxonomy_term_load_multiple');
+ entity_get_loader('taxonomy_term')->resetCache();
}
/**
@@ -618,21 +631,17 @@ function taxonomy_form_all($free_tags =
*/
function taxonomy_get_vocabularies($type = NULL) {
$conditions = !empty($type) ? array('type' => $type) : NULL;
- return taxonomy_vocabulary_load_multiple(array(), $conditions);
+ return taxonomy_vocabulary_load_multiple(FALSE, $conditions);
}
/**
* Get names for all taxonomy vocabularies.
*
* @return
- * An array of vocabulary names in the format 'machine_name' => 'name'.
+ * An array of vocabulary ids, names and machine names, keyed by machine name.
*/
function taxonomy_vocabulary_get_names() {
- $names = array();
- $vocabularies = taxonomy_get_vocabularies();
- foreach ($vocabularies as $vocabulary) {
- $names[$vocabulary->machine_name] = $vocabulary->name;
- }
+ $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
return $names;
}
@@ -1176,99 +1185,97 @@ function taxonomy_get_term_by_name($name
}
/**
- * Load multiple taxonomy vocabularies based on certain conditions.
- *
- * This function should be used whenever you need to load more than one
- * vocabulary from the database. Terms are loaded into memory and will not
- * require database access if loaded again during the same page request.
- *
- * @param $vids
- * An array of taxonomy vocabulary IDs.
- * @param $conditions
- * An array of conditions to add to the query.
+ * Return array of tids and join operator.
*
- * @return
- * An array of vocabulary objects, indexed by vid.
+ * This is a wrapper function for taxonomy_terms_parse_string which is called
+ * by the menu system when loading a path with taxonomy terms.
*/
-function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
- $vocabulary_cache = &drupal_static(__FUNCTION__, array());
- // Node type associations are not stored in the vocabulary table, so remove
- // this from conditions into it's own variable.
- if (isset($conditions['type'])) {
- $type = $conditions['type'];
- unset($conditions['type']);
- }
-
- $vocabularies = array();
-
- // Create a new variable which is either a prepared version of the $vids
- // array for later comparison with the term cache, or FALSE if no $vids were
- // passed. The $vids array is reduced as items are loaded from cache, and we
- // need to know if it's empty for this reason to avoid querying the database
- // when all requested items are loaded from cache.
- $passed_vids = !empty($vids) ? array_flip($vids) : FALSE;
+function taxonomy_terms_load($str_tids) {
+ $terms = taxonomy_terms_parse_string($str_tids);
+ return $terms;
+}
- // Load any available items from the internal cache.
- if ($vocabulary_cache) {
- if ($vids) {
- $vocabularies += array_intersect_key($vocabulary_cache, $passed_vids);
- // If any items were loaded, remove them from the $vids still to load.
- $vids = array_keys(array_diff_key($passed_vids, $vocabularies));
+/**
+ * Loader class for taxonomy terms.
+ *
+ * This extends the DrupalDefaultEntityLoader class. Only alteration is that
+ * we match the condition on term name case-independently.
+ */
+class TaxonomyTermLoader extends DrupalDefaultEntityLoader {
+ protected $type;
+ public function loadMultiple($ids = array(), $conditions = array()) {
+ if (isset($conditions['type'])) {
+ $this->type = $conditions['type'];
+ unset($conditions['type']);
+ }
+ return parent::loadMultiple($ids, $conditions);
+ }
+
+ protected function buildQuery() {
+ parent::buildQuery();
+ // When name is passed as a condition use LIKE.
+ if (isset($this->conditions['name'])) {
+ $conditions = &$this->query->conditions();
+ foreach ($conditions as $key => $condition) {
+ if ($condition['field'] == 'base.name') {
+ $conditions[$key]['operator'] = 'LIKE';
+ }
+ }
}
- // If only conditions is passed, load all items from the cache. Items
- // which don't match conditions will be removed later.
- elseif ($conditions) {
- $vocabularies = $vocabulary_cache;
+ // Add the machine name field from the {taxonomy_vocabulary} table.
+ $this->query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
+ $this->query->addField('v', 'machine_name', 'vocabulary_machine_name');
+
+ if (!empty($this->type)) {
+ $this->query->innerJoin('taxonomy_vocabulary_node_type', 'n', 'base.vid = n.vid AND n.type = :type', array(':type' => $this->type));
}
}
- // Remove any loaded terms from the array if they don't match $conditions.
- if ($conditions || isset($type)) {
- foreach ($vocabularies as $vocabulary) {
- $vocabulary_values = (array) $vocabulary;
- if (array_diff_assoc($conditions, $vocabulary_values)) {
- unset($vocabularies[$vocabulary->vid]);
- }
- if (isset($type) && !in_array($type, $vocabulary->nodes)) {
- unset($vocabularies[$vocabulary->vid]);
+ protected function cacheGet($ids) {
+ $terms = parent::cacheGet($ids);
+ // Name matching is case insensitive, note that with some collations
+ // LOWER() and drupal_strtolower() may return different results.
+ foreach ($terms as $term) {
+ $term_values = (array) $term;
+ if (isset($this->conditions['name']) && drupal_strtolower($this->conditions['name'] != drupal_strtolower($term_values['name']))) {
+ unset($terms[$term->tid]);
}
}
+ return $terms;
}
+}
- // Load any remaining vocabularies from the database, this is necessary if
- // we have $vids still to load, or if no $vids were passed.
- if ($vids || !$passed_vids) {
- $query = db_select('taxonomy_vocabulary', 'v');
- $query->addField('n', 'type');
- $query
- ->fields('v')
- ->orderBy('v.weight')
- ->orderBy('v.name')
- ->addTag('vocabulary_access');
-
- if (!empty($type)) {
- $query->join('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid AND n.type = :type', array(':type' => $type));
- }
- else {
- $query->leftJoin('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid');
+/**
+ * Loader class for taxonomy vocabularies.
+ *
+ * This extends the DrupalDefaultEntityLoader class, adding required special
+ * handling for taxonomy vocabulary objects.
+ */
+class TaxonomyVocabularyLoader extends DrupalDefaultEntityLoader {
+ protected $type;
+ public function loadMultiple($ids = array(), $conditions = array()) {
+ if (isset($conditions['type'])) {
+ $this->type = $conditions['type'];
+ unset($conditions['type']);
}
+ return parent::loadMultiple($ids, $conditions);
+ }
- // If the $vids array is populated, add those to the query.
- if ($vids) {
- $query->condition('v.vid', $vids, 'IN');
+ protected function buildQuery() {
+ parent::buildQuery();
+ if (!empty($this->type)) {
+ $this->query->innerJoin('taxonomy_vocabulary_node_type', 'n', 'base.vid = n.vid AND n.type = :type', array(':type' => $this->type));
}
-
- // If the conditions array is populated, add those to the query.
- if ($conditions) {
- foreach ($conditions as $field => $value) {
- $query->condition('v.' . $field, $value);
- }
+ else {
+ $this->query->leftJoin('taxonomy_vocabulary_node_type', 'n', 'base.vid = n.vid');
}
- $result = $query->execute();
+ $this->query->addField('n', 'type');
+ $this->query->orderBy('base.weight');
+ $this->query->orderBy('base.name');
+ }
- $queried_vocabularies = array();
- $node_types = array();
- foreach ($result as $record) {
+ protected function attachLoad(&$records) {
+ foreach ($records as $record) {
// If no node types are associated with a vocabulary, the LEFT JOIN will
// return a NULL value for type.
if (isset($record->type)) {
@@ -1281,45 +1288,9 @@ function taxonomy_vocabulary_load_multip
}
$queried_vocabularies[$record->vid] = $record;
}
-
- // Invoke hook_taxonomy_vocabulary_load() on the vocabularies loaded from
- // the database and add them to the static cache.
- if (!empty($queried_vocabularies)) {
- foreach (module_implements('taxonomy_vocabulary_load') as $module) {
- $function = $module . '_taxonomy_vocabulary_load';
- $function($queried_vocabularies);
- }
- $vocabularies += $queried_vocabularies;
- $vocabulary_cache += $queried_vocabularies;
- }
+ $records = $queried_vocabularies;
+ parent::attachLoad($records);
}
-
- // Ensure that the returned array is ordered the same as the original $vids
- // array if this was passed in and remove any invalid vids.
- if ($passed_vids) {
- // Remove any invalid vids from the array.
- $passed_vids = array_intersect_key($passed_vids, $vocabularies);
- foreach ($vocabularies as $vocabulary) {
- $passed_vids[$vocabulary->vid] = $vocabulary;
- }
- $vocabularies = $passed_vids;
- }
-
- return $vocabularies;
-}
-
-/**
- * Return the vocabulary object matching a vocabulary ID.
- *
- * @param $vid
- * The vocabulary's ID.
- *
- * @return
- * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
- * Results are statically cached.
- */
-function taxonomy_vocabulary_load($vid) {
- return reset(taxonomy_vocabulary_load_multiple(array($vid), array()));
}
/**
@@ -1329,6 +1300,8 @@ function taxonomy_vocabulary_load($vid)
* from the database. Terms are loaded into memory and will not require
* database access if loaded again during the same page request.
*
+ * @see entity_load_multiple()
+ *
* @param $tids
* An array of taxonomy term IDs.
* @param $conditions
@@ -1338,115 +1311,42 @@ function taxonomy_vocabulary_load($vid)
* An array of term objects, indexed by tid.
*/
function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
- $term_cache = &drupal_static(__FUNCTION__, array());
-
- // Node type associations are not stored in the taxonomy_term_data table, so
- // remove this from conditions into it's own variable.
- if (isset($conditions['type'])) {
- $type = $conditions['type'];
- unset($conditions['type']);
- }
-
- $terms = array();
-
- // Create a new variable which is either a prepared version of the $tids
- // array for later comparison with the term cache, or FALSE if no $tids were
- // passed. The $tids array is reduced as items are loaded from cache, and we
- // need to know if it's empty for this reason to avoid querying the database
- // when all requested terms are loaded from cache.
- $passed_tids = !empty($tids) ? array_flip($tids) : FALSE;
-
- // Load any available terms from the internal cache.
- if ($term_cache) {
- if ($tids) {
- $terms += array_intersect_key($term_cache, $passed_tids);
- // If any terms were loaded, remove them from the $tids still to load.
- $tids = array_keys(array_diff_key($passed_tids, $terms));
- }
- // If only conditions is passed, load all terms from the cache. Terms
- // which don't match conditions will be removed later.
- elseif ($conditions) {
- $terms = $term_cache;
- }
- }
-
- // Remove any loaded terms from the array if they don't match $conditions.
- if ($conditions) {
- // Name matching is case insensitive, note that with some collations
- // LOWER() and drupal_strtolower() may return different results.
- foreach ($terms as $term) {
- $term_values = (array) $term;
- if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
- unset($terms[$term->tid]);
- }
- elseif (array_diff_assoc($conditions, $term_values)) {
- unset($terms[$term->tid]);
- }
- }
- }
-
- // Load any remaining terms from the database, this is necessary if we have
- // $tids still to load, or if $conditions was passed without $tids.
- if ($tids || ($conditions && !$passed_tids)) {
- $query = db_select('taxonomy_term_data', 't');
- $query->addTag('term_access');
- $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
- $taxonomy_term_data = drupal_schema_fields_sql('taxonomy_term_data');
- $query->addField('v', 'machine_name', 'vocabulary_machine_name');
- $query
- ->fields('t', $taxonomy_term_data)
- ->addTag('term_access');
-
- // If the $tids array is populated, add those to the query.
- if ($tids) {
- $query->condition('t.tid', $tids, 'IN');
- }
-
- if (!empty($type)) {
- $query->join('taxonomy_vocabulary_node_type', 'n', 't.vid = n.vid AND n.type = :type', array(':type' => $type));
- }
-
- // If the conditions array is populated, add those to the query.
- if ($conditions) {
- // When name is passed as a condition use LIKE.
- if (isset($conditions['name'])) {
- $query->condition('t.name', $conditions['name'], 'LIKE');
- unset($conditions['name']);
- }
- foreach ($conditions as $field => $value) {
- $query->condition('t.' . $field, $value);
- }
- }
- $queried_terms = $query->execute()->fetchAllAssoc('tid');
-
- if (!empty($queried_terms)) {
-
- // Attach fields.
- field_attach_load('taxonomy_term', $queried_terms);
-
- // Invoke hook_taxonomy_term_load() and add the term objects to the
- // static cache.
- foreach (module_implements('taxonomy_term_load') as $module) {
- $function = $module . '_taxonomy_term_load';
- $function($queried_terms);
- }
- $terms += $queried_terms;
- $term_cache += $queried_terms;
- }
- }
+ return entity_load_multiple('taxonomy_term', $tids, $conditions);
+}
- // Ensure that the returned array is ordered the same as the original $tids
- // array if this was passed in and remove any invalid tids.
- if ($passed_tids) {
- // Remove any invalid tids from the array.
- $passed_tids = array_intersect_key($passed_tids, $terms);
- foreach ($terms as $term) {
- $passed_tids[$term->tid] = $term;
- }
- $terms = $passed_tids;
- }
+/**
+ * Load multiple taxonomy vocabularies based on certain conditions.
+ *
+ * This function should be used whenever you need to load more than one
+ * vocabulary from the database. Terms are loaded into memory and will not
+ * require database access if loaded again during the same page request.
+ *
+ * @see entity_load_multiple()
+ *
+ * @param $vids
+ * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies.
+ * @param $conditions
+ * An array of conditions to add to the query.
+ *
+ * @return
+ * An array of vocabulary objects, indexed by vid.
+ */
+function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
+ return entity_load_multiple('taxonomy_vocabulary', $vids, $conditions);
+}
- return $terms;
+/**
+ * Return the vocabulary object matching a vocabulary ID.
+ *
+ * @param $vid
+ * The vocabulary's ID.
+ *
+ * @return
+ * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
+ * Results are statically cached.
+ */
+function taxonomy_vocabulary_load($vid) {
+ return reset(taxonomy_vocabulary_load_multiple(array($vid)));
}
/**
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.43
diff -u -p -r1.43 taxonomy.test
--- modules/taxonomy/taxonomy.test 4 Aug 2009 06:50:07 -0000 1.43
+++ modules/taxonomy/taxonomy.test 11 Aug 2009 00:36:03 -0000
@@ -162,7 +162,7 @@ class TaxonomyVocabularyFunctionalTest e
// Check the created vocabulary.
$vocabularies = taxonomy_get_vocabularies();
$vid = $vocabularies[count($vocabularies)-1]->vid;
- drupal_static_reset('taxonomy_vocabulary_load_multiple');
+ entity_get_loader('taxonomy_vocabulary')->resetCache();
$vocabulary = taxonomy_vocabulary_load($vid);
$this->assertTrue($vocabulary, t('Vocabulary found in database'));
@@ -175,7 +175,7 @@ class TaxonomyVocabularyFunctionalTest e
// Confirm deletion.
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertRaw(t('Deleted vocabulary %name.', array('%name' => $vocabulary->name)), t('Vocabulary deleted'));
- drupal_static_reset('taxonomy_vocabulary_load_multiple');
+ entity_get_loader('taxonomy_vocabulary')->resetCache();
$this->assertFalse(taxonomy_vocabulary_load($vid), t('Vocabulary is not found in the database'));
}
}
@@ -271,8 +271,7 @@ class TaxonomyVocabularyUnitTest extends
// Fetch the names for all vocabularies, confirm that they are keyed by
// machine name.
$names = taxonomy_vocabulary_get_names();
- $this->assertTrue(in_array($vocabulary1->name, $names), t('Vocabulary 1 name found.'));
- $this->assertTrue(isset($names[$vocabulary1->machine_name]), t('Vocabulary names are keyed by machine name.'));
+ $this->assertEqual($names[$vocabulary1->machine_name]->name, $vocabulary1->name, t('Vocabulary 1 name found.'));
// Fetch all of the vocabularies using taxonomy_get_vocabularies().
// Confirm that the vocabularies are ordered by weight.
@@ -295,7 +294,7 @@ class TaxonomyVocabularyUnitTest extends
$this->assertTrue(current(taxonomy_vocabulary_load_multiple(array($vocabulary1->vid), array('name' => $vocabulary1->name))) == $vocabulary1, t('Vocabulary loaded successfully by name and ID.'));
// Fetch vocabulary 1 with specified node type.
- drupal_static_reset('taxonomy_vocabulary_load_multiple');
+ entity_get_loader('taxonomy_vocabulary')->resetCache();
$vocabulary_node_type = current(taxonomy_vocabulary_load_multiple(array($vocabulary1->vid), array('type' => 'article')));
$this->assertEqual($vocabulary_node_type, $vocabulary1, t('Vocabulary with specified node type loaded successfully.'));
}
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1019
diff -u -p -r1.1019 user.module
--- modules/user/user.module 8 Aug 2009 20:52:33 -0000 1.1019
+++ modules/user/user.module 11 Aug 2009 00:36:06 -0000
@@ -84,12 +84,15 @@ function user_theme() {
}
/**
- * Implement hook_fieldable_info().
+ * Implement hook_entity_info().
*/
-function user_fieldable_info() {
+function user_entity_info() {
$return = array(
'user' => array(
'label' => t('User'),
+ 'loader class' => 'UserLoader',
+ 'base table' => 'users',
+ 'fieldable' => TRUE,
'object keys' => array(
'id' => 'uid',
),
@@ -149,68 +152,29 @@ function user_external_load($authname) {
* @return
* An array of user objects, indexed by uid.
*
+ * @see entity_load_multiple()
* @see user_load()
* @see user_load_by_mail()
* @see user_load_by_name()
*/
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
- static $user_cache = array();
- if ($reset) {
- $user_cache = array();
- }
-
- $users = array();
-
- // Create a new variable which is either a prepared version of the $uids
- // array for later comparison with the user cache, or FALSE if no $uids were
- // passed. The $uids array is reduced as items are loaded from cache, and we
- // need to know if it's empty for this reason to avoid querying the database
- // when all requested users are loaded from cache.
- $passed_uids = !empty($uids) ? array_flip($uids) : FALSE;
-
- // Load any available users from the internal cache.
- if ($user_cache) {
- if ($uids && !$conditions) {
- $users += array_intersect_key($user_cache, $passed_uids);
- // If any users were loaded, remove them from the $uids still to load.
- $uids = array_keys(array_diff_key($passed_uids, $users));
- }
- }
-
- // Load any remaining users from the database, this is necessary if we have
- // $uids still to load, or if $conditions was passed without $uids.
- if ($uids || ($conditions && !$passed_uids)) {
- $query = db_select('users', 'u')->fields('u');
-
- // If the $uids array is populated, add those to the query.
- if ($uids) {
- $query->condition('u.uid', $uids, 'IN');
- }
- // If the conditions array is populated, add those to the query.
- if ($conditions) {
- // TODO D7: Using LIKE() to get a case insensitive comparison because Crell
- // and chx promise that dbtng will map it to ILIKE in postgres.
- if (isset($conditions['name'])) {
- $query->condition('u.name', $conditions['name'], 'LIKE');
- unset($conditions['name']);
- }
- if (isset($conditions['mail'])) {
- $query->condition('u.mail', $conditions['mail'], 'LIKE');
- unset($conditions['mail']);
- }
- foreach ($conditions as $field => $value) {
- $query->condition('u.' . $field, $value);
- }
- }
- $result = $query->execute();
+ return entity_load_multiple('user', $uids, $conditions, $reset);
+}
- $queried_users = array();
+/**
+ * Loader class for users.
+ *
+ * This extends the DrupalDefaultEntityLoader class, adding required special
+ * handling for user objects.
+ */
+class UserLoader extends DrupalDefaultEntityLoader {
+ function attachLoad(&$queried_users) {
// Build an array of user picture IDs so that these can be fetched later.
$picture_fids = array();
- foreach ($result as $record) {
+ foreach ($queried_users as $key => $record) {
$picture_fids[] = $record->picture;
- $queried_users[$record->uid] = drupal_unpack($record);
- $queried_users[$record->uid]->roles = array();
+ $queried_users[$key] = drupal_unpack($record);
+ $queried_users[$key]->roles = array();
if ($record->uid) {
$queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
}
@@ -219,57 +183,29 @@ function user_load_multiple($uids = arra
}
}
- if (!empty($queried_users)) {
- // Add any additional roles from the database.
- $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
- foreach ($result as $record) {
- $queried_users[$record->uid]->roles[$record->rid] = $record->name;
- }
+ // Add any additional roles from the database.
+ $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
+ foreach ($result as $record) {
+ $queried_users[$record->uid]->roles[$record->rid] = $record->name;
+ }
- // Add the full file objects for user pictures if enabled.
- if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
- $pictures = file_load_multiple($picture_fids);
- foreach ($queried_users as $account) {
- if (!empty($account->picture) && isset($pictures[$account->picture])) {
- $account->picture = $pictures[$account->picture];
- }
- else {
- $account->picture = NULL;
- }
+ // Add the full file objects for user pictures if enabled.
+ if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
+ $pictures = file_load_multiple($picture_fids);
+ foreach ($queried_users as $account) {
+ if (!empty($account->picture) && isset($pictures[$account->picture])) {
+ $account->picture = $pictures[$account->picture];
+ }
+ else {
+ $account->picture = NULL;
}
}
-
- field_attach_load('user', $queried_users);
-
- // Invoke hook_user_load() on the users loaded from the database
- // and add them to the static cache.
- foreach (module_implements('user_load') as $module) {
- $function = $module . '_user_load';
- $function($queried_users);
- }
-
-
-
- $users = $users + $queried_users;
- $user_cache = $user_cache + $queried_users;
}
+ // Call the default attachLoad() method. This will add fields and call hook_user_load().
+ parent::attachLoad($queried_users);
}
-
- // Ensure that the returned array is ordered the same as the original $uids
- // array if this was passed in and remove any invalid uids.
- if ($passed_uids) {
- // Remove any invalid uids from the array.
- $passed_uids = array_intersect_key($passed_uids, $users);
- foreach ($users as $user) {
- $passed_uids[$user->uid] = $user;
- }
- $users = $passed_uids;
- }
-
- return $users;
}
-
/**
* Fetch a user object.
*