Merge branch '7.x' of git.drupal.org:project/drupal into 7.x
commit
5dbe8aa92b
|
@ -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']};
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ';
|
||||
|
|
|
@ -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) . '} ';
|
||||
}
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'])) {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue