Merge branch '7.x' of git.drupal.org:project/drupal into 7.x

merge-requests/26/head
Dries Buytaert 2011-05-22 09:08:19 -04:00
commit 5dbe8aa92b
19 changed files with 295 additions and 149 deletions

View File

@ -7496,8 +7496,15 @@ function entity_get_controller($entity_type) {
* The type of entity, i.e. 'node', 'user'.
* @param $entities
* The entity objects which are being prepared for view, keyed by object ID.
* @param $langcode
* (optional) A language code to be used for rendering. Defaults to the global
* content language of the current request.
*/
function entity_prepare_view($entity_type, $entities) {
function entity_prepare_view($entity_type, $entities, $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $GLOBALS['language_content']->language;
}
// To ensure hooks are only run once per entity, check for an
// entity_view_prepared flag and only process items without it.
// @todo: resolve this more generally for both entity and field level hooks.
@ -7513,7 +7520,7 @@ function entity_prepare_view($entity_type, $entities) {
}
if (!empty($prepare)) {
module_invoke_all('entity_prepare_view', $prepare, $entity_type);
module_invoke_all('entity_prepare_view', $prepare, $entity_type, $langcode);
}
}
@ -7588,7 +7595,7 @@ function entity_label($entity_type, $entity) {
$label = FALSE;
$info = entity_get_info($entity_type);
if (isset($info['label callback']) && function_exists($info['label callback'])) {
$label = $info['label callback']($entity);
$label = $info['label callback']($entity, $entity_type);
}
elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) {
$label = $entity->{$info['entity keys']['label']};

View File

@ -540,6 +540,63 @@ abstract class DatabaseConnection extends PDO {
return $this->prefixTables('{' . $table . '}_' . $field . '_seq');
}
/**
* Flatten an array of query comments into a single comment string.
*
* The comment string will be sanitized to avoid SQL injection attacks.
*
* @param $comments
* An array of query comment strings.
*
* @return
* A sanitized comment string.
*/
public function makeComment($comments) {
if (empty($comments))
return '';
// Flatten the array of comments.
$comment = implode('; ', $comments);
// Sanitize the comment string so as to avoid SQL injection attacks.
return '/* ' . $this->filterComment($comment) . ' */ ';
}
/**
* Sanitize a query comment string.
*
* Ensure a query comment does not include strings such as "* /" that might
* terminate the comment early. This avoids SQL injection attacks via the
* query comment. The comment strings in this example are separated by a
* space to avoid PHP parse errors.
*
* For example, the comment:
* @code
* db_update('example')
* ->condition('id', $id)
* ->fields(array('field2' => 10))
* ->comment('Exploit * / DROP TABLE node; --')
* ->execute()
* @endcode
*
* Would result in the following SQL statement being generated:
* @code
* "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..."
* @endcode
*
* Unless the comment is sanitised first, the SQL server would drop the
* node table and ignore the rest of the SQL statement.
*
* @param $comment
* A query comment string.
*
* @return
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
}
/**
* Executes a query string against the database.
*

View File

@ -42,8 +42,8 @@ class InsertQuery_mysql extends InsertQuery {
}
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
@ -92,8 +92,8 @@ class TruncateQuery_mysql extends TruncateQuery {
// not transactional, and result in an implicit COMMIT. When we are in a
// transaction, fallback to the slower, but transactional, DELETE.
if ($this->connection->inTransaction()) {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}';
}
else {

View File

@ -103,8 +103,8 @@ class InsertQuery_pgsql extends InsertQuery {
}
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);

View File

@ -361,6 +361,9 @@ abstract class Query implements QueryPlaceholderInterface {
* for easier debugging and allows you to more easily find where a query
* with a performance problem is being generated.
*
* The comment string will be sanitized to remove * / and other characters
* that may terminate the string early so as to avoid SQL injection attacks.
*
* @param $comment
* The comment string to be inserted into the query.
*
@ -623,9 +626,8 @@ class InsertQuery extends Query {
* The prepared statement.
*/
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
@ -815,9 +817,8 @@ class DeleteQuery extends Query implements QueryConditionInterface {
* The prepared statement.
*/
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
$query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
@ -884,8 +885,8 @@ class TruncateQuery extends Query {
* The prepared statement.
*/
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} ';
}
@ -1111,9 +1112,8 @@ class UpdateQuery extends Query implements QueryConditionInterface {
* The prepared statement.
*/
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Expressions take priority over literal fields, so we process those first
// and remove any literal fields that conflict.

View File

@ -1439,9 +1439,8 @@ class SelectQuery extends Query implements SelectQueryInterface {
}
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// SELECT
$query = $comments . 'SELECT ';

View File

@ -32,8 +32,8 @@ class InsertQuery_sqlite extends InsertQuery {
}
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Produce as many generic placeholders as necessary.
$placeholders = array_fill(0, count($this->insertFields), '?');
@ -148,8 +148,8 @@ class DeleteQuery_sqlite extends DeleteQuery {
*/
class TruncateQuery_sqlite extends TruncateQuery {
public function __toString() {
// Create a comments string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
}

View File

@ -1044,7 +1044,7 @@ function menu_tree_output($tree) {
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . $data['link']['menu_name'];
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$element['#attributes']['class'] = $class;
$element['#title'] = $data['link']['title'];
$element['#href'] = $data['link']['href'];

View File

@ -986,8 +986,8 @@ function comment_build_content($comment, $node, $view_mode = 'full', $langcode =
$comment->content = array();
// Build fields content.
field_attach_prepare_view('comment', array($comment->cid => $comment), $view_mode);
entity_prepare_view('comment', array($comment->cid => $comment));
field_attach_prepare_view('comment', array($comment->cid => $comment), $view_mode, $langcode);
entity_prepare_view('comment', array($comment->cid => $comment), $langcode);
$comment->content += field_attach_view('comment', $comment, $view_mode, $langcode);
$comment->content['links'] = array(
@ -1089,8 +1089,8 @@ function comment_links($comment, $node) {
* An array in the format expected by drupal_render().
*/
function comment_view_multiple($comments, $node, $view_mode = 'full', $weight = 0, $langcode = NULL) {
field_attach_prepare_view('comment', $comments, $view_mode);
entity_prepare_view('comment', $comments);
field_attach_prepare_view('comment', $comments, $view_mode, $langcode);
entity_prepare_view('comment', $comments, $langcode);
$build = array(
'#sorted' => TRUE,

View File

@ -257,9 +257,9 @@ function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $opti
* - 'deleted': If TRUE, the function will operate on deleted fields
* as well as non-deleted fields. If unset or FALSE, only
* non-deleted fields are operated on.
* - 'language': A language code or an array of language codes keyed by field
* name. It will be used to narrow down to a single value the available
* languages to act on.
* - 'language': A language code or an array of arrays of language codes keyed
* by entity id and field name. It will be used to narrow down to a single
* value the available languages to act on.
*
* @return
* An array of returned values keyed by entity id.
@ -311,7 +311,8 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
// Unless a language suggestion is provided we iterate on all the
// available languages.
$available_languages = field_available_languages($entity_type, $field);
$languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
$language = !empty($options['language'][$id]) ? $options['language'][$id] : $options['language'];
$languages = _field_language_suggestion($available_languages, $language, $field_name);
foreach ($languages as $langcode) {
$grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
}
@ -1074,8 +1075,13 @@ function field_attach_delete_revision($entity_type, $entity) {
* An array of entities, keyed by entity id.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
* @param $langcode
* (Optional) The language the field values are to be shown in. If no language
* is provided the current language is used.
*/
function field_attach_prepare_view($entity_type, $entities, $view_mode) {
function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) {
$options = array('language' => array());
// To ensure hooks are only run once per entity, only process items without
// the _field_view_prepared flag.
// @todo: resolve this more generally for both entity and field level hooks.
@ -1085,17 +1091,22 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode) {
// Add this entity to the items to be prepared.
$prepare[$id] = $entity;
// Determine the actual language to display for each field, given the
// languages available in the field data.
$options['language'][$id] = field_language($entity_type, $entity, NULL, $langcode);
// Mark this item as prepared.
$entity->_field_view_prepared = TRUE;
}
}
$null = NULL;
// First let the field types do their preparation.
_field_invoke_multiple('prepare_view', $entity_type, $prepare);
_field_invoke_multiple('prepare_view', $entity_type, $prepare, $null, $null, $options);
// Then let the formatters do their own specific massaging.
// field_default_prepare_view() takes care of dispatching to the correct
// formatters according to the display settings for the view mode.
_field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode);
_field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode, $null, $options);
}
/**

View File

@ -469,7 +469,26 @@ function node_update_dependencies() {
* @ingroup update-api-6.x-to-7.x
*/
function _update_7000_node_get_types() {
return db_query('SELECT * FROM {node_type}')->fetchAllAssoc('type', PDO::FETCH_OBJ);
$node_types = db_query('SELECT * FROM {node_type}')->fetchAllAssoc('type', PDO::FETCH_OBJ);
// Create default settings for orphan nodes.
$all_types = db_query('SELECT DISTINCT type FROM {node}')->fetchCol();
$extra_types = array_diff($all_types, array_keys($node_types));
foreach ($extra_types as $type) {
$type_object = new stdClass;
$type_object->type = $type;
// In Drupal 6, whether you have a body field or not is a flag in the node
// type table. If it's enabled, nodes may or may not have an empty string
// for the bodies. As we can't detect what this setting should be in
// Drupal 7 without access to the Drupal 6 node type settings, we assume
// the default, which is to enable the body field.
$type_object->has_body = 1;
$type_object->body_label = 'Body';
$node_types[$type_object->type] = $type_object;
}
return $node_types;
}
/**
@ -600,19 +619,6 @@ function node_update_7006(&$sandbox) {
// Get node type info, specifically the body field settings.
$node_types = _update_7000_node_get_types();
// Create default settings for orphan nodes.
$extra_types = db_query('SELECT DISTINCT type FROM {node} WHERE type NOT IN (:types)', array(':types' => array_keys($node_types)))->fetchCol();
foreach ($extra_types as $type) {
$type_object = new stdClass;
$type_object->type = $type;
// Always create a body. Querying node_revisions for a non-empty body
// would skip creating body fields for types that have a body but
// the nodes of that type so far had empty bodies.
$type_object->has_body = 1;
$type_object->body_label = 'Body';
$node_types[$type_object->type] = $type_object;
}
// Add body field instances for existing node types.
foreach ($node_types as $node_type) {
if ($node_type->has_body) {

View File

@ -1350,15 +1350,15 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
if (node_hook($node, 'view')) {
$node = node_invoke($node, 'view', $view_mode);
$node = node_invoke($node, 'view', $view_mode, $langcode);
}
// Build fields content.
// In case of a multiple view, node_view_multiple() already ran the
// 'prepare_view' step. An internal flag prevents the operation from running
// twice.
field_attach_prepare_view('node', array($node->nid => $node), $view_mode);
entity_prepare_view('node', array($node->nid => $node));
field_attach_prepare_view('node', array($node->nid => $node), $view_mode, $langcode);
entity_prepare_view('node', array($node->nid => $node), $langcode);
$node->content += field_attach_view('node', $node, $view_mode, $langcode);
// Always display a read more link on teasers because we have no way
@ -2513,8 +2513,8 @@ function node_feed($nids = FALSE, $channel = array()) {
* An array in the format expected by drupal_render().
*/
function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
field_attach_prepare_view('node', $nodes, $view_mode);
entity_prepare_view('node', $nodes);
field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
entity_prepare_view('node', $nodes, $langcode);
$build = array();
foreach ($nodes as $node) {
$build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);

View File

@ -341,18 +341,14 @@ function openid_complete($response = array()) {
$response['openid.claimed_id'] = $service['claimed_id'];
}
elseif ($service['version'] == 2) {
// Returned Claimed Identifier could contain unique fragment
// identifier to allow identifier recycling so we need to preserve
// it in the response.
$response_claimed_id = openid_normalize($response['openid.claimed_id']);
$response['openid.claimed_id'] = openid_normalize($response['openid.claimed_id']);
// OpenID Authentication, section 11.2:
// If the returned Claimed Identifier is different from the one sent
// to the OpenID Provider, we need to do discovery on the returned
// identififer to make sure that the provider is authorized to
// respond on behalf of this.
if ($response_claimed_id != $claimed_id) {
$services = openid_discovery($response_claimed_id);
if ($response['openid.claimed_id'] != $claimed_id) {
$services = openid_discovery($response['openid.claimed_id']);
$uris = array();
foreach ($services as $discovered_service) {
if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {

View File

@ -89,12 +89,12 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase {
// Identifier is the URL of an XRDS document containing an OP Identifier
// Element. The Relying Party sends the special value
// "http://specs.openid.net/auth/2.0/identifier_select" as Claimed
// Identifier. The OpenID Provider responds with the actual identifier
// including the fragment.
$identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName()));
// Tell openid_test.module to respond with this identifier. We test if
// openid_complete() processes it right.
variable_set('openid_test_response', array('openid.claimed_id' => $identity));
// Identifier. The OpenID Provider responds with the actual identifier.
$identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE));
// Tell openid_test.module to respond with this identifier. The URL scheme
// is stripped in order to test that the returned identifier is normalized in
// openid_complete().
variable_set('openid_test_response', array('openid.claimed_id' => preg_replace('@^https?://@', '', $identity)));
$this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity);
variable_set('openid_test_response', array());

View File

@ -1324,6 +1324,27 @@ class DatabaseSelectTestCase extends DatabaseTestCase {
$this->assertEqual($query, $expected, t('The flattened query contains the comment string.'));
}
/**
* Test query COMMENT system against vulnerabilities.
*/
function testVulnerableComment() {
$query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$result = $query->execute();
$num_records = 0;
foreach ($result as $record) {
$num_records++;
}
$query = (string)$query;
$expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
$this->assertEqual($query, $expected, t('The flattened query contains the sanitised comment string.'));
}
/**
* Test basic conditionals on SELECT statements.
*/

View File

@ -856,6 +856,64 @@ class MenuTreeDataTestCase extends DrupalUnitTestCase {
}
}
/**
* Menu tree output related tests.
*/
class MenuTreeOutputTestCase extends DrupalWebTestCase {
/**
* Dummy link structure acceptable for menu_tree_output().
*/
var $tree_data = array(
'1'=> array(
'link' => array( 'menu_name' => 'main-menu', 'mlid' => 1, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a', 'localized_options' => array('attributes' => array('title' =>'')) ),
'below' => array(
'2' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 2, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a/b', 'localized_options' => array('attributes' => array('title' =>'')) ),
'below' => array(
'3' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 3, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>'')) ),
'below' => array() ),
'4' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 4, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>'')) ),
'below' => array() )
)
)
)
),
'5' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 5, 'hidden'=>1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access'=>1, 'href' => 'e', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ),
'6' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 6, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access'=>0, 'href' => 'f', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ),
'7' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 7, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access'=>1, 'href' => 'g', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) )
);
public static function getInfo() {
return array(
'name' => 'Menu tree output',
'description' => 'Tests menu tree output functions.',
'group' => 'Menu',
);
}
function setUp() {
parent::setUp();
}
/**
* Validate the generation of a proper menu tree output.
*/
function testMenuTreeData() {
$output = menu_tree_output($this->tree_data);
// Validate that the - in main-menu is changed into an underscore
$this->assertEqual( $output['1']['#theme'], 'menu_link__main_menu', t('Hyphen is changed to a dash on menu_link'));
$this->assertEqual( $output['#theme_wrappers'][0], 'menu_tree__main_menu', t('Hyphen is changed to a dash on menu_tree wrapper'));
// Looking for child items in the data
$this->assertEqual( $output['1']['#below']['2']['#href'], 'a/b', t('Checking the href on a child item'));
$this->assertTrue( in_array('active-trail',$output['1']['#below']['2']['#attributes']['class']) , t('Checking the active trail class'));
// Validate that the hidden and no access items are missing
$this->assertFalse( isset($output['5']), t('Hidden item should be missing'));
$this->assertFalse( isset($output['6']), t('False access should be missing'));
// Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are skipped and 7 still included
$this->assertTrue( isset($output['7']), t('Item after hidden items is present'));
}
}
/**
* Menu breadcrumbs related tests.
*/

View File

@ -87,16 +87,16 @@ function hook_hook_info_alter(&$hooks) {
* - uri callback: A function taking an entity as argument and returning the
* uri elements of the entity, e.g. 'path' and 'options'. The actual entity
* uri can be constructed by passing these elements to url().
* - label callback: (optional) A function taking an entity as argument and
* returning the label of the entity. The entity label is the main string
* associated with an entity; for example, the title of a node or the
* subject of a comment. If there is an entity object property that defines
* the label, use the 'label' element of the 'entity keys' return
* value component to provide this information (see below). If more complex
* logic is needed to determine the label of an entity, you can instead
* specify a callback function here, which will be called to determine the
* entity label. See also the entity_label() function, which implements this
* logic.
* - label callback: (optional) A function taking an entity and an entity type
* as arguments and returning the label of the entity. The entity label is
* the main string associated with an entity; for example, the title of a
* node or the subject of a comment. If there is an entity object property
* that defines the label, use the 'label' element of the 'entity keys'
* return value component to provide this information (see below). If more
* complex logic is needed to determine the label of an entity, you can
* instead specify a callback function here, which will be called to
* determine the entity label. See also the entity_label() function, which
* implements this logic.
* - fieldable: Set to TRUE if you want your entity type to accept fields
* being attached to it.
* - translation: An associative array of modules registered as field
@ -502,8 +502,10 @@ function hook_admin_paths_alter(&$paths) {
* The entities keyed by entity ID.
* @param $type
* The type of entities being loaded (i.e. node, user, comment).
* @param $langcode
* The language to display the entity in.
*/
function hook_entity_prepare_view($entities, $type) {
function hook_entity_prepare_view($entities, $type, $langcode) {
// Load a specific node into the user object for later theming.
if ($type == 'user') {
$nodes = mymodule_get_user_nodes(array_keys($entities));

View File

@ -682,8 +682,8 @@ function taxonomy_term_view($term, $view_mode = 'full', $langcode = NULL) {
$langcode = $GLOBALS['language_content']->language;
}
field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode);
entity_prepare_view('taxonomy_term', array($term->tid => $term));
field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
$build = array(
'#theme' => 'taxonomy_term',

View File

@ -442,27 +442,18 @@ function user_save($account, $edit = array(), $category = 'account') {
user_module_invoke('presave', $edit, $account, $category);
// Invoke presave operations of Field Attach API and Entity API. Those APIs
// require a fully-fledged (and updated) entity object, so $edit is not
// necessarily sufficient, as it technically contains submitted form values
// only. Therefore, we need to clone $account into a new object and copy any
// new property values of $edit into it.
$account_updated = clone $account;
// require a fully-fledged and updated entity object. Therefore, we need to
// copy any new property values of $edit into it.
foreach ($edit as $key => $value) {
$account_updated->$key = $value;
}
field_attach_presave('user', $account_updated);
module_invoke_all('entity_presave', $account_updated, 'user');
// Update $edit with any changes modules might have applied to the account.
foreach ($account_updated as $key => $value) {
if (!property_exists($account, $key) || $value !== $account->$key) {
$edit[$key] = $value;
}
$account->$key = $value;
}
field_attach_presave('user', $account);
module_invoke_all('entity_presave', $account, 'user');
if (is_object($account) && !$account->is_new) {
// Process picture uploads.
if (!$delete_previous_picture = empty($edit['picture']->fid)) {
$picture = $edit['picture'];
if (!empty($account->picture->fid) && (!isset($account->original->picture->fid) || $account->picture->fid != $account->original->picture->fid)) {
$picture = $account->picture;
// If the picture is a temporary file move it to its final location and
// make it permanent.
if (!$picture->status) {
@ -475,26 +466,23 @@ function user_save($account, $edit = array(), $category = 'account') {
// Move the temporary file into the final location.
if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
$delete_previous_picture = TRUE;
$picture->status = FILE_STATUS_PERMANENT;
$edit['picture'] = file_save($picture);
$account->picture = file_save($picture);
file_usage_add($picture, 'user', 'user', $account->uid);
}
}
// Delete the previous picture if it was deleted or replaced.
if (!empty($account->original->picture->fid)) {
file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
file_delete($account->original->picture);
}
}
// Delete the previous picture if it was deleted or replaced.
if ($delete_previous_picture && !empty($account->picture->fid)) {
file_usage_delete($account->picture, 'user', 'user', $account->uid);
file_delete($account->picture);
}
$edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;
$account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid;
// Do not allow 'uid' to be changed.
$edit['uid'] = $account->uid;
$account->uid = $account->original->uid;
// Save changes to the user table.
$success = drupal_write_record('users', $edit, 'uid');
$success = drupal_write_record('users', $account, 'uid');
if ($success === FALSE) {
// The query failed - better to abort the save than risk further
// data loss.
@ -502,13 +490,13 @@ function user_save($account, $edit = array(), $category = 'account') {
}
// Reload user roles if provided.
if (isset($edit['roles']) && is_array($edit['roles'])) {
if ($account->roles != $account->original->roles) {
db_delete('users_roles')
->condition('uid', $account->uid)
->execute();
$query = db_insert('users_roles')->fields(array('uid', 'rid'));
foreach (array_keys($edit['roles']) as $rid) {
foreach (array_keys($account->roles) as $rid) {
if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
$query->values(array(
'uid' => $account->uid,
@ -520,13 +508,13 @@ function user_save($account, $edit = array(), $category = 'account') {
}
// Delete a blocked user's sessions to kick them if they are online.
if (isset($edit['status']) && $edit['status'] == 0) {
if ($account->original->status != $account->status && $account->status == 0) {
drupal_session_destroy_uid($account->uid);
}
// If the password changed, delete all open sessions and recreate
// the current one.
if (!empty($edit['pass'])) {
if ($account->pass != $account->original->pass) {
drupal_session_destroy_uid($account->uid);
if ($account->uid == $GLOBALS['user']->uid) {
drupal_session_regenerate();
@ -534,60 +522,56 @@ function user_save($account, $edit = array(), $category = 'account') {
}
// Save Field data.
$entity = (object) $edit;
field_attach_update('user', $entity);
// Refresh user object.
$user = user_load($account->uid, TRUE);
// Make the original, unchanged user account available to update hooks.
if (isset($account->original)) {
$user->original = $account->original;
}
field_attach_update('user', $account);
// Send emails after we have the new user object.
if (isset($edit['status']) && $edit['status'] != $account->status) {
if ($account->status != $account->original->status) {
// The user's status is changing; conditionally send notification email.
$op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
_user_mail_notify($op, $user);
$op = $account->status == 1 ? 'status_activated' : 'status_blocked';
_user_mail_notify($op, $account);
}
user_module_invoke('update', $edit, $user, $category);
module_invoke_all('entity_update', $user, 'user');
unset($user->original);
// Update $edit with any interim changes to $account.
foreach ($account as $key => $value) {
if (!property_exists($account->original, $key) || $value !== $account->original->$key) {
$edit[$key] = $value;
}
}
user_module_invoke('update', $edit, $account, $category);
module_invoke_all('entity_update', $account, 'user');
}
else {
// Allow 'uid' to be set by the caller. There is no danger of writing an
// existing user as drupal_write_record will do an INSERT.
if (empty($edit['uid'])) {
$edit['uid'] = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
if (empty($account->uid)) {
$account->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
}
// Allow 'created' to be set by the caller.
if (!isset($edit['created'])) {
$edit['created'] = REQUEST_TIME;
if (!isset($account->created)) {
$account->created = REQUEST_TIME;
}
$success = drupal_write_record('users', $edit);
$success = drupal_write_record('users', $account);
if ($success === FALSE) {
// On a failed INSERT some other existing user's uid may be returned.
// We must abort to avoid overwriting their account.
return FALSE;
}
// Build a stub user object.
$user = (object) $edit;
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
// Make sure $account is properly initialized.
$account->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
field_attach_insert('user', $user);
user_module_invoke('insert', $edit, $user, $category);
module_invoke_all('entity_insert', $user, 'user');
field_attach_insert('user', $account);
$edit = (array) $account;
user_module_invoke('insert', $edit, $account, $category);
module_invoke_all('entity_insert', $account, 'user');
// Save user roles.
if (isset($edit['roles']) && is_array($edit['roles'])) {
if (count($account->roles) > 1) {
$query = db_insert('users_roles')->fields(array('uid', 'rid'));
foreach (array_keys($edit['roles']) as $rid) {
foreach (array_keys($account->roles) as $rid) {
if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
$query->values(array(
'uid' => $edit['uid'],
'uid' => $account->uid,
'rid' => $rid,
));
}
@ -595,8 +579,13 @@ function user_save($account, $edit = array(), $category = 'account') {
$query->execute();
}
}
// Clear internal properties.
unset($account->is_new);
unset($account->original);
// Clear the static loading cache.
entity_get_controller('user')->resetCache(array($account->uid));
return $user;
return $account;
}
catch (Exception $e) {
$transaction->rollback();
@ -2523,8 +2512,8 @@ function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
$account->content = array();
// Build fields content.
field_attach_prepare_view('user', array($account->uid => $account), $view_mode);
entity_prepare_view('user', array($account->uid => $account));
field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
entity_prepare_view('user', array($account->uid => $account), $langcode);
$account->content += field_attach_view('user', $account, $view_mode, $langcode);
// Populate $account->content with a render() array.