summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--includes/ajax.inc39
-rw-r--r--includes/authorize.inc4
-rw-r--r--includes/bootstrap.inc123
-rw-r--r--includes/common.inc7
-rw-r--r--includes/database/mysql/database.inc6
-rw-r--r--includes/entity.inc105
-rw-r--r--includes/errors.inc2
-rw-r--r--includes/file.inc55
-rw-r--r--includes/form.inc13
-rw-r--r--includes/mail.inc4
-rw-r--r--includes/menu.inc62
-rw-r--r--includes/password.inc2
-rw-r--r--includes/stream_wrappers.inc6
-rw-r--r--misc/ajax.js9
-rw-r--r--modules/block/block.module3
-rw-r--r--modules/book/book.module5
-rw-r--r--modules/comment/comment.module9
-rw-r--r--modules/comment/comment.test65
-rw-r--r--modules/dblog/dblog.admin.inc2
-rw-r--r--modules/dblog/dblog.test2
-rw-r--r--modules/field/field.api.php4
-rw-r--r--modules/field/field.form.inc4
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module72
-rw-r--r--modules/field/modules/list/list.module2
-rw-r--r--modules/field/modules/list/tests/list.test17
-rw-r--r--modules/field/modules/text/text.test6
-rw-r--r--modules/field_ui/field_ui.admin.inc6
-rw-r--r--modules/field_ui/field_ui.module2
-rw-r--r--modules/field_ui/field_ui.test3
-rw-r--r--modules/file/file.module2
-rw-r--r--modules/file/tests/file.test72
-rw-r--r--modules/filter/filter.test8
-rw-r--r--modules/locale/locale-rtl.css12
-rw-r--r--modules/locale/locale.css6
-rw-r--r--modules/locale/locale.test5
-rw-r--r--modules/menu/menu.install52
-rw-r--r--modules/node/node.api.php2
-rw-r--r--modules/node/node.install5
-rw-r--r--modules/node/node.module17
-rw-r--r--modules/path/path.test3
-rw-r--r--modules/poll/poll.module4
-rw-r--r--modules/profile/profile.module1
-rw-r--r--modules/search/search.admin.inc4
-rw-r--r--modules/search/search.api.php4
-rw-r--r--modules/search/tests/search_embedded_form.module3
-rw-r--r--modules/shortcut/shortcut.install7
-rw-r--r--modules/simpletest/tests/entity_query.test127
-rw-r--r--modules/simpletest/tests/file.test139
-rw-r--r--modules/simpletest/tests/file_test.module17
-rw-r--r--modules/simpletest/tests/mail.test353
-rw-r--r--modules/simpletest/tests/menu.test8
-rw-r--r--modules/simpletest/tests/upgrade/drupal-6.menu.database.php119
-rw-r--r--modules/simpletest/tests/upgrade/upgrade.menu.test44
-rw-r--r--modules/simpletest/tests/upgrade/upgrade.node.test5
-rw-r--r--modules/simpletest/tests/upgrade/upgrade.taxonomy.test6
-rw-r--r--modules/system/image.gd.inc2
-rw-r--r--modules/system/system.admin.inc2
-rw-r--r--modules/system/system.api.php55
-rw-r--r--modules/system/system.install3
-rw-r--r--modules/system/system.module1
-rw-r--r--modules/system/system.test1
-rw-r--r--modules/system/system.tokens.inc11
-rw-r--r--modules/taxonomy/taxonomy.admin.inc34
-rw-r--r--modules/taxonomy/taxonomy.install1
-rw-r--r--modules/taxonomy/taxonomy.pages.inc6
-rw-r--r--modules/taxonomy/taxonomy.test48
-rw-r--r--modules/translation/translation.test13
-rw-r--r--modules/user/user-rtl.css6
-rw-r--r--modules/user/user.admin.inc7
-rw-r--r--modules/user/user.css4
-rw-r--r--modules/user/user.module39
-rw-r--r--modules/user/user.permissions.js70
-rw-r--r--profiles/standard/standard.install1
-rw-r--r--scripts/generate-d6-content.sh6
-rwxr-xr-xscripts/run-tests.sh150
75 files changed, 1720 insertions, 404 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index 41c69832a..d70808efe 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -143,6 +143,21 @@
* - #ajax['event']: The JavaScript event to respond to. This is normally
* selected automatically for the type of form widget being used, and
* is only needed if you need to override the default behavior.
+ * - #ajax['prevent']: A JavaScript event to prevent when 'event' is triggered.
+ * Defaults to 'click' for #ajax on #type 'submit', 'button', and
+ * 'image_button'. Multiple events may be specified separated by spaces.
+ * For example, when binding #ajax behaviors to form buttons, pressing the
+ * ENTER key within a textfield triggers the 'click' event of the form's first
+ * submit button. Triggering Ajax in this situation leads to problems, like
+ * breaking autocomplete textfields. Because of that, Ajax behaviors are bound
+ * to the 'mousedown' event on form buttons by default. However, binding to
+ * 'mousedown' rather than 'click' means that it is possible to trigger a
+ * click by pressing the mouse, holding the mouse button down until the Ajax
+ * request is complete and the button is re-enabled, and then releasing the
+ * mouse button. For this case, 'prevent' can be set to 'click', so an
+ * additional event handler is bound to prevent such a click from triggering a
+ * non-Ajax form submission. This also prevents a textfield's ENTER press
+ * triggering a button's non-Ajax form submission behavior.
* - #ajax['method']: The jQuery method to use to place the new HTML.
* Defaults to 'replaceWith'. May be: 'replaceWith', 'append', 'prepend',
* 'before', 'after', or 'html'. See the
@@ -591,6 +606,7 @@ function ajax_process_form($element, &$form_state) {
* An associative array containing the properties of the element.
* Properties used:
* - #ajax['event']
+ * - #ajax['prevent']
* - #ajax['path']
* - #ajax['options']
* - #ajax['wrapper']
@@ -619,13 +635,26 @@ function ajax_pre_render_element($element) {
case 'submit':
case 'button':
case 'image_button':
- // Use the mousedown instead of the click event because form
- // submission via pressing the enter key triggers a click event on
- // submit inputs, inappropriately triggering Ajax behaviors.
+ // Pressing the ENTER key within a textfield triggers the click event of
+ // the form's first submit button. Triggering Ajax in this situation
+ // leads to problems, like breaking autocomplete textfields, so we bind
+ // to mousedown instead of click.
+ // @see http://drupal.org/node/216059
$element['#ajax']['event'] = 'mousedown';
- // Attach an additional event handler so that Ajax behaviors
- // can be triggered still via keyboard input.
+ // Retain keyboard accessibility by setting 'keypress'. This causes
+ // ajax.js to trigger 'event' when SPACE or ENTER are pressed while the
+ // button has focus.
$element['#ajax']['keypress'] = TRUE;
+ // Binding to mousedown rather than click means that it is possible to
+ // trigger a click by pressing the mouse, holding the mouse button down
+ // until the Ajax request is complete and the button is re-enabled, and
+ // then releasing the mouse button. Set 'prevent' so that ajax.js binds
+ // an additional handler to prevent such a click from triggering a
+ // non-Ajax form submission. This also prevents a textfield's ENTER
+ // press triggering this button's non-Ajax form submission behavior.
+ if (!isset($element['#ajax']['prevent'])) {
+ $element['#ajax']['prevent'] = 'click';
+ }
break;
case 'password':
diff --git a/includes/authorize.inc b/includes/authorize.inc
index 3617d7d03..852860413 100644
--- a/includes/authorize.inc
+++ b/includes/authorize.inc
@@ -193,7 +193,7 @@ function _authorize_filetransfer_connection_settings_set_defaults(&$element, $ke
function authorize_filetransfer_form_validate($form, &$form_state) {
// Only validate the form if we have collected all of the user input and are
// ready to proceed with updating or installing.
- if ($form_state['clicked_button']['#name'] != 'process_updates') {
+ if ($form_state['triggering_element']['#name'] != 'process_updates') {
return;
}
@@ -224,7 +224,7 @@ function authorize_filetransfer_form_validate($form, &$form_state) {
*/
function authorize_filetransfer_form_submit($form, &$form_state) {
global $base_url;
- switch ($form_state['clicked_button']['#name']) {
+ switch ($form_state['triggering_element']['#name']) {
case 'process_updates':
// Save the connection settings to the DB.
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index f54ce92a3..e9837da30 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -38,94 +38,70 @@ define('CACHE_PERMANENT', 0);
define('CACHE_TEMPORARY', -1);
/**
- * Log message severity -- Emergency: system is unusable.
+ * @defgroup logging_severity_levels Logging severity levels
+ * @{
+ * Logging severity levels as defined in RFC 3164.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
+ * defined in RFC 3164, section 4.1.1. PHP supplies predefined LOG_* constants
+ * for use in the syslog() function, but their values on Windows builds do not
+ * correspond to RFC 3164. The associated PHP bug report was closed with the
+ * comment, "And it's also not a bug, as Windows just have less log levels,"
+ * and "So the behavior you're seeing is perfectly normal."
+ *
+ * @see http://www.faqs.org/rfcs/rfc3164.html
+ * @see http://bugs.php.net/bug.php?id=18090
+ * @see http://php.net/manual/function.syslog.php
+ * @see http://php.net/manual/network.constants.php
* @see watchdog()
* @see watchdog_severity_levels()
*/
+
+/**
+ * Log message severity -- Emergency: system is unusable.
+ */
define('WATCHDOG_EMERGENCY', 0);
/**
* Log message severity -- Alert: action must be taken immediately.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_ALERT', 1);
/**
* Log message severity -- Critical: critical conditions.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_CRITICAL', 2);
/**
* Log message severity -- Error: error conditions.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_ERROR', 3);
/**
* Log message severity -- Warning: warning conditions.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_WARNING', 4);
/**
* Log message severity -- Notice: normal but significant condition.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_NOTICE', 5);
/**
* Log message severity -- Informational: informational messages.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_INFO', 6);
/**
* Log message severity -- Debug: debug-level messages.
- *
- * The WATCHDOG_* constant definitions correspond to the logging severity levels
- * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
- *
- * @see watchdog()
- * @see watchdog_severity_levels()
*/
define('WATCHDOG_DEBUG', 7);
/**
+ * @} End of "defgroup logging_severity_levels".
+ */
+
+/**
* First bootstrap phase: initialize configuration.
*/
define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
@@ -216,8 +192,11 @@ define('LANGUAGE_RTL', 1);
/**
* For convenience, define a short form of the request time global.
+ *
+ * REQUEST_TIME is a float with microseconds since PHP 5.4.0, but float
+ * timestamps confuses most of the PHP functions (including date_create()).
*/
-define('REQUEST_TIME', $_SERVER['REQUEST_TIME']);
+define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
/**
* Flag for drupal_set_title(); text is not sanitized, so run check_plain().
@@ -311,31 +290,30 @@ function timer_stop($name) {
}
/**
- * Find the appropriate configuration directory.
+ * Finds the appropriate configuration directory.
*
- * Try finding a matching configuration directory by stripping the website's
+ * Finds a matching configuration directory by stripping the website's
* hostname from left to right and pathname from right to left. The first
- * configuration file found will be used; the remaining will ignored. If no
- * configuration file is found, return a default value '$confdir/default'.
+ * configuration file found will be used and the remaining ones will be ignored.
+ * If no configuration file is found, return a default value '$confdir/default'.
*
- * Example for a fictitious site installed at
- * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in
- * the following directories:
+ * With a site located at http://www.example.com:8080/mysite/test/, the file,
+ * settings.php, is searched for in the following directories:
*
- * 1. $confdir/8080.www.drupal.org.mysite.test
- * 2. $confdir/www.drupal.org.mysite.test
- * 3. $confdir/drupal.org.mysite.test
- * 4. $confdir/org.mysite.test
+ * 1. $confdir/8080.www.example.com.mysite.test
+ * 2. $confdir/www.example.com.mysite.test
+ * 3. $confdir/example.com.mysite.test
+ * 4. $confdir/com.mysite.test
*
- * 5. $confdir/8080.www.drupal.org.mysite
- * 6. $confdir/www.drupal.org.mysite
- * 7. $confdir/drupal.org.mysite
- * 8. $confdir/org.mysite
+ * 5. $confdir/8080.www.example.com.mysite
+ * 6. $confdir/www.example.com.mysite
+ * 7. $confdir/example.com.mysite
+ * 8. $confdir/com.mysite
*
- * 9. $confdir/8080.www.drupal.org
- * 10. $confdir/www.drupal.org
- * 11. $confdir/drupal.org
- * 12. $confdir/org
+ * 9. $confdir/8080.www.example.com
+ * 10. $confdir/www.example.com
+ * 11. $confdir/example.com
+ * 12. $confdir/com
*
* 13. $confdir/default
*
@@ -343,18 +321,18 @@ function timer_stop($name) {
* prior to scanning for directories. It should define an associative array
* named $sites, which maps domains to directories. It should be in the form
* of:
- *
+ * @code
* $sites = array(
* 'The url to alias' => 'A directory within the sites directory'
* );
- *
+ * @endcode
* For example:
- *
+ * @code
* $sites = array(
* 'devexample.com' => 'example.com',
* 'localhost.example' => 'example.com',
* );
- *
+ * @endcode
* The above array will cause Drupal to look for a directory named
* "example.com" in the sites directory whenever a request comes from
* "example.com", "devexample.com", or "localhost/example". That is useful
@@ -363,14 +341,15 @@ function timer_stop($name) {
* (files, system table, etc.) this will ensure the paths are correct while
* accessed on development servers.
*
- * @param $require_settings
+ * @param bool $require_settings
* Only configuration directories with an existing settings.php file
* will be recognized. Defaults to TRUE. During initial installation,
* this is set to FALSE so that Drupal can detect a matching directory,
* then create a new settings.php file in it.
- * @param reset
+ * @param bool $reset
* Force a full search for matching directories even if one had been
- * found previously.
+ * found previously. Defaults to FALSE.
+ *
* @return
* The path of the matching directory.
*/
diff --git a/includes/common.inc b/includes/common.inc
index 9b582c446..b8cd55f5c 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2255,7 +2255,7 @@ function drupal_http_header_attributes(array $attributes = array()) {
* An associative array of key-value pairs to be converted to attributes.
*
* @return
- * A string ready for insertion in a tag.
+ * A string ready for insertion in a tag (starts with a space).
*
* @ingroup sanitization
*/
@@ -2291,7 +2291,9 @@ function drupal_attributes(array $attributes = array()) {
* to work in a call to drupal_attributes($options['attributes']).
* - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
* example, to make an image tag into a link, this must be set to TRUE, or
- * you will see the escaped HTML image tag.
+ * you will see the escaped HTML image tag. $text is not sanitized if
+ * 'html' is TRUE. The calling function must ensure that $text is already
+ * safe.
* - 'language': An optional language object. If the path being linked to is
* internal to the site, $options['language'] is used to determine whether
* the link is "active", or pointing to the current page (the language as
@@ -7047,6 +7049,7 @@ function drupal_parse_info_format($data) {
* Array of the possible severity levels for log messages.
*
* @see watchdog()
+ * @ingroup logging_severity_levels
*/
function watchdog_severity_levels() {
return array(
diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc
index bc31feaaf..157cbfa56 100644
--- a/includes/database/mysql/database.inc
+++ b/includes/database/mysql/database.inc
@@ -169,10 +169,8 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// savepoints which no longer exist.
//
// To avoid exceptions when no actual error has occurred, we silently
- // succeed for PDOExceptions with SQLSTATE 42000 ("Syntax error or
- // access rule violation") and MySQL error code 1305 ("SAVEPOINT does
- // not exist").
- if ($e->getCode() == '42000' && $e->errorInfo[1] == '1305') {
+ // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
+ if ($e->errorInfo[1] == '1305') {
// If one SAVEPOINT was released automatically, then all were.
// Therefore, we keep just the topmost transaction.
$this->transactionLayers = array('drupal_transaction');
diff --git a/includes/entity.inc b/includes/entity.inc
index 9ee7889cf..f363c3113 100644
--- a/includes/entity.inc
+++ b/includes/entity.inc
@@ -458,6 +458,21 @@ class EntityFieldQuery {
public $fieldConditions = array();
/**
+ * List of field meta conditions (language and delta).
+ *
+ * Field conditions operate on columns specified by hook_field_schema(),
+ * the meta conditions operate on columns added by the system: delta
+ * and language. These can not be mixed with the field conditions because
+ * field columns can have any name including delta and language.
+ *
+ * @var array
+ *
+ * @see EntityFieldQuery::fieldLanguageCondition()
+ * @see EntityFieldQuery::fielDeltaCondition()
+ */
+ public $fieldMetaConditions = array();
+
+ /**
* List of property conditions.
*
* @var array
@@ -613,6 +628,90 @@ class EntityFieldQuery {
/**
* Adds a condition on field values.
*
+ * @param $type
+ * The condition array the given conditions should be added to.
+ * @param $field
+ * Either a field name or a field array.
+ * @param $column
+ * The column that should hold the value to be matched.
+ * @param $value
+ * The value to test the column value against.
+ * @param $operator
+ * The operator to be used to test the given value.
+ * @param $delta_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $delta_group.
+ * @param $language_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $language_group.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ *
+ * @see EntityFieldQuery::addFieldCondition
+ * @see EntityFieldQuery::deleted
+ */
+ public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+ return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $language_group);
+ }
+
+ /**
+ * Adds a condition on the field language column.
+ *
+ * @param $field
+ * Either a field name or a field array.
+ * @param $value
+ * The value to test the column value against.
+ * @param $operator
+ * The operator to be used to test the given value.
+ * @param $delta_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $delta_group.
+ * @param $language_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $language_group.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ *
+ * @see EntityFieldQuery::addFieldCondition
+ * @see EntityFieldQuery::deleted
+ */
+ public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+ return $this->addFieldCondition($this->fieldMetaConditions, $field, 'language', $value, $operator, $delta_group, $language_group);
+ }
+
+ /**
+ * Adds a condition on the field delta column.
+ *
+ * @param $field
+ * Either a field name or a field array.
+ * @param $value
+ * The value to test the column value against.
+ * @param $operator
+ * The operator to be used to test the given value.
+ * @param $delta_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $delta_group.
+ * @param $language_group
+ * An arbitrary identifier: conditions in the same group must have the same
+ * $language_group.
+ *
+ * @return EntityFieldQuery
+ * The called object.
+ *
+ * @see EntityFieldQuery::addFieldCondition
+ * @see EntityFieldQuery::deleted
+ */
+ public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+ return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $language_group);
+ }
+
+ /**
+ * Adds the given condition to the proper condition array.
+ *
+ * @param $conditions
+ * A reference to an array of conditions.
* @param $field
* Either a field name or a field array.
* @param $column
@@ -649,7 +748,7 @@ class EntityFieldQuery {
* @return EntityFieldQuery
* The called object.
*/
- public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
+ protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
if (is_scalar($field)) {
$field_definition = field_info_field($field);
if (empty($field_definition)) {
@@ -657,11 +756,11 @@ class EntityFieldQuery {
}
$field = $field_definition;
}
- // Ensure the same index is used for fieldConditions as for fields.
+ // Ensure the same index is used for field conditions as for fields.
$index = count($this->fields);
$this->fields[$index] = $field;
if (isset($column)) {
- $this->fieldConditions[$index] = array(
+ $conditions[$index] = array(
'field' => $field,
'column' => $column,
'value' => $value,
diff --git a/includes/errors.inc b/includes/errors.inc
index be7242856..bd31bebed 100644
--- a/includes/errors.inc
+++ b/includes/errors.inc
@@ -24,6 +24,8 @@ define('ERROR_REPORTING_DISPLAY_ALL', 2);
* Map PHP error constants to watchdog severity levels.
* The error constants are documented at
* http://php.net/manual/en/errorfunc.constants.php
+ *
+ * @ingroup logging_severity_levels
*/
function drupal_error_levels() {
$types = array(
diff --git a/includes/file.inc b/includes/file.inc
index 73e75cd4f..19420ca37 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -755,7 +755,12 @@ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $c
*/
function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
if (!file_valid_uri($destination)) {
- watchdog('file', 'File %file (%realpath) could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => drupal_realpath($source->uri), '%destination' => $destination));
+ if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
+ }
+ else {
+ watchdog('file', 'File %file could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
+ }
drupal_set_message(t('The specified file %file could not be copied, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
return FALSE;
}
@@ -847,11 +852,15 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
$original_destination = $destination;
// Assert that the source file actually exists.
- $source = drupal_realpath($source);
if (!file_exists($source)) {
// @todo Replace drupal_set_message() calls with exceptions instead.
drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
- watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => drupal_realpath($original_source)));
+ if (($realpath = drupal_realpath($original_source)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
+ }
+ else {
+ watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
+ }
return FALSE;
}
@@ -871,7 +880,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.
- watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => drupal_realpath($dirname)));
+ watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
return FALSE;
}
@@ -881,12 +890,14 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
$destination = file_destination($destination, $replace);
if ($destination === FALSE) {
drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
- watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => drupal_realpath($destination)));
+ watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination));
return FALSE;
}
// Assert that the source and destination filenames are not the same.
- if (drupal_realpath($source) == drupal_realpath($destination)) {
+ $real_source = drupal_realpath($source);
+ $real_destination = drupal_realpath($destination);
+ if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
return FALSE;
@@ -895,7 +906,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
file_ensure_htaccess();
// Perform the copy operation.
if (!@copy($source, $destination)) {
- watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => drupal_realpath($destination)), WATCHDOG_ERROR);
+ watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERR);
return FALSE;
}
@@ -986,7 +997,12 @@ function file_destination($destination, $replace) {
*/
function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
if (!file_valid_uri($destination)) {
- watchdog('file', 'File %file (%realpath) could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => drupal_realpath($source->uri), '%destination' => $destination));
+ if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
+ }
+ else {
+ watchdog('file', 'File %file could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
+ }
drupal_set_message(t('The specified file %file could not be moved, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
return FALSE;
}
@@ -1212,7 +1228,12 @@ function file_create_filename($basename, $directory) {
*/
function file_delete(stdClass $file, $force = FALSE) {
if (!file_valid_uri($file->uri)) {
- watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => drupal_realpath($file->uri)));
+ if (($realpath = drupal_realpath($file->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => $realpath));
+ }
+ else {
+ watchdog('file', 'File %file could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri));
+ }
drupal_set_message(t('The specified file %file could not be deleted, because it is not a valid URI. More information is available in the system log.', array('%file' => $file->uri)), 'error');
return FALSE;
}
@@ -1256,8 +1277,6 @@ function file_delete(stdClass $file, $force = FALSE) {
* @see file_unmanaged_delete_recursive()
*/
function file_unmanaged_delete($path) {
- // Resolve streamwrapper URI to local path.
- $path = drupal_realpath($path);
if (is_dir($path)) {
watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
return FALSE;
@@ -1299,8 +1318,6 @@ function file_unmanaged_delete($path) {
* @see file_unmanaged_delete()
*/
function file_unmanaged_delete_recursive($path) {
- // Resolve streamwrapper URI to local path.
- $path = drupal_realpath($path);
if (is_dir($path)) {
$dir = dir($path);
while (($entry = $dir->read()) !== FALSE) {
@@ -2331,11 +2348,9 @@ function file_directory_temp() {
if (substr(PHP_OS, 0, 3) == 'WIN') {
$directories[] = 'c:\\windows\\temp';
$directories[] = 'c:\\winnt\\temp';
- $path_delimiter = '\\';
}
else {
$directories[] = '/tmp';
- $path_delimiter = '/';
}
// PHP may be able to find an alternative tmp directory.
// This function exists in PHP 5 >= 5.2.1, but Drupal
@@ -2352,8 +2367,14 @@ function file_directory_temp() {
}
if (empty($temporary_directory)) {
- // If no directory has been found default to 'files/tmp' or 'files\\tmp'.
- $temporary_directory = variable_get('file_public_path', conf_path() . '/files') . $path_delimiter . 'tmp';
+ // If no directory has been found default to 'files/tmp'.
+ $temporary_directory = variable_get('file_public_path', conf_path() . '/files') . '/tmp';
+
+ // Windows accepts paths with either slash (/) or backslash (\), but will
+ // not accept a path which contains both a slash and a backslash. Since
+ // the 'file_public_path' variable may have either format, we sanitize
+ // everything to use slash which is supported on all platforms.
+ $temporary_directory = str_replace('\\', '/', $temporary_directory);
}
// Save the path of the discovered directory.
variable_set('file_temporary_path', $temporary_directory);
diff --git a/includes/form.inc b/includes/form.inc
index 38ef41cf0..8ee0a5a52 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -272,7 +272,8 @@ function drupal_get_form($form_id) {
* form submission may be found in drupal_redirect_form().
*
* @return
- * The rendered form or NULL, depending upon the $form_state flags that were set.
+ * The rendered form. This function may also perform a redirect and hence may
+ * not return at all, depending upon the $form_state flags that were set.
*
* @see drupal_redirect_form()
*/
@@ -995,6 +996,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
$form += array('#tree' => FALSE, '#parents' => array());
if (!isset($form['#validate'])) {
+ // Ensure that modules can rely on #validate being set.
+ $form['#validate'] = array();
// Check for a handler specific to $form_id.
if (function_exists($form_id . '_validate')) {
$form['#validate'][] = $form_id . '_validate';
@@ -1007,6 +1010,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
}
if (!isset($form['#submit'])) {
+ // Ensure that modules can rely on #submit being set.
+ $form['#submit'] = array();
// Check for a handler specific to $form_id.
if (function_exists($form_id . '_submit')) {
$form['#submit'][] = $form_id . '_submit';
@@ -2853,9 +2858,9 @@ function form_process_date($element) {
/**
* Validates the date type to stop dates like February 30, 2006.
*/
-function date_validate($form) {
- if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) {
- form_error($form, t('The specified date is invalid.'));
+function date_validate($element) {
+ if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) {
+ form_error($element, t('The specified date is invalid.'));
}
}
diff --git a/includes/mail.inc b/includes/mail.inc
index d2febed39..7272df972 100644
--- a/includes/mail.inc
+++ b/includes/mail.inc
@@ -430,7 +430,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$indent[] = count($lists) ? ' "' : '>';
break;
case 'li':
- $indent[] = is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
+ $indent[] = isset($lists[0]) && is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
break;
case 'dd':
$indent[] = ' ';
@@ -509,7 +509,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$chunk = $casing($chunk);
}
// Format it and apply the current indentation.
- $output .= drupal_wrap_mail($chunk, implode('', $indent));
+ $output .= drupal_wrap_mail($chunk, implode('', $indent)) . MAIL_LINE_ENDINGS;
// Remove non-quotation markers from indentation.
$indent = array_map('_drupal_html_to_text_clean', $indent);
}
diff --git a/includes/menu.inc b/includes/menu.inc
index cfd35c794..5582c452e 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -431,6 +431,11 @@ function menu_get_item($path = NULL, $router_item = NULL) {
$router_items[$path] = $router_item;
}
if (!isset($router_items[$path])) {
+ // Rebuild if we know it's needed, or if the menu masks are missing which
+ // occurs rarely, likely due to a race condition of multiple rebuilds.
+ if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
+ menu_rebuild();
+ }
$original_map = arg(NULL, $path);
// Since there is no limit to the length of $path, use a hash to keep it
@@ -490,11 +495,6 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
// Only continue if the site status is not set.
if ($page_callback_result == MENU_SITE_ONLINE) {
- // Rebuild if we know it's needed, or if the menu masks are missing which
- // occurs rarely, likely due to a race condition of multiple rebuilds.
- if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
- menu_rebuild();
- }
if ($router_item = menu_get_item($path)) {
if ($router_item['access']) {
if ($router_item['include_file']) {
@@ -2696,19 +2696,15 @@ function _menu_navigation_links_rebuild($menu) {
}
}
if ($menu_links) {
+ // Keep an array of processed menu links, to allow menu_link_save() to
+ // check this for parents instead of querying the database.
+ $parent_candidates = array();
// Make sure no child comes before its parent.
array_multisort($sort, SORT_NUMERIC, $menu_links);
- foreach ($menu_links as $item) {
+ foreach ($menu_links as $key => $item) {
$existing_item = db_select('menu_links')
- ->fields('menu_links', array(
- 'mlid',
- 'menu_name',
- 'plid',
- 'customized',
- 'has_children',
- 'updated',
- ))
+ ->fields('menu_links')
->condition('link_path', $item['path'])
->condition('module', 'system')
->execute()->fetchAssoc();
@@ -2727,9 +2723,14 @@ function _menu_navigation_links_rebuild($menu) {
$item['has_children'] = $existing_item['has_children'];
$item['updated'] = $existing_item['updated'];
}
- if (!$existing_item || !$existing_item['customized']) {
+ if ($existing_item && $existing_item['customized']) {
+ $parent_candidates[$existing_item['mlid']] = $existing_item;
+ }
+ else {
$item = _menu_link_build($item);
- menu_link_save($item);
+ menu_link_save($item, $existing_item, $parent_candidates);
+ $parent_candidates[$item['mlid']] = $item;
+ unset($menu_links[$key]);
}
}
}
@@ -2927,12 +2928,17 @@ function _menu_delete_item($item, $force = FALSE) {
* to insert a new link.
* - plid: (optional) The mlid of the parent.
* - router_path: (optional) The path of the relevant router item.
+ * @param $existing_item
+ * Optional, the current record from the {menu_links} table as an array.
+ * @param $parent_candidates
+ * Optional array of menu links keyed by mlid. Used by
+ * _menu_navigation_links_rebuild() only.
*
* @return
* The mlid of the saved menu link, or FALSE if the menu link could not be
* saved.
*/
-function menu_link_save(&$item) {
+function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
drupal_alter('menu_link', $item);
// This is the easiest way to handle the unique internal path '<front>',
@@ -2951,15 +2957,20 @@ function menu_link_save(&$item) {
'customized' => 0,
'updated' => 0,
);
- $existing_item = FALSE;
if (isset($item['mlid'])) {
- if ($existing_item = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['mlid']))->fetchAssoc()) {
+ if (!$existing_item) {
+ $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
+ }
+ if ($existing_item) {
$existing_item['options'] = unserialize($existing_item['options']);
}
}
+ else {
+ $existing_item = FALSE;
+ }
// Try to find a parent link. If found, assign it and derive its menu.
- $parent = _menu_link_find_parent($item);
+ $parent = _menu_link_find_parent($item, $parent_candidates);
if (!empty($parent['mlid'])) {
$item['plid'] = $parent['mlid'];
$item['menu_name'] = $parent['menu_name'];
@@ -3093,11 +3104,13 @@ function menu_link_save(&$item) {
*
* @param $menu_link
* A menu link.
+ * @param $parent_candidates
+ * An array of menu links keyed by mlid.
* @return
* A menu link structure of the possible parent or FALSE if no valid parent
* has been found.
*/
-function _menu_link_find_parent($menu_link) {
+function _menu_link_find_parent($menu_link, $parent_candidates = array()) {
$parent = FALSE;
// This item is explicitely top-level, skip the rest of the parenting.
@@ -3119,7 +3132,12 @@ function _menu_link_find_parent($menu_link) {
}
foreach ($candidates as $mlid) {
- $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+ if (isset($parent_candidates[$mlid])) {
+ $parent = $parent_candidates[$mlid];
+ }
+ else {
+ $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+ }
if ($parent) {
return $parent;
}
diff --git a/includes/password.inc b/includes/password.inc
index 93d34f81f..c0761da69 100644
--- a/includes/password.inc
+++ b/includes/password.inc
@@ -18,7 +18,7 @@
* increase by 1 every Drupal version in order to counteract increases in the
* speed and power of computers available to crack the hashes.
*/
-define('DRUPAL_HASH_COUNT', 14);
+define('DRUPAL_HASH_COUNT', 15);
/**
* The minimum allowed log2 number of iterations for password stretching.
diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc
index 7df1f9dc6..3c88f3d8f 100644
--- a/includes/stream_wrappers.inc
+++ b/includes/stream_wrappers.inc
@@ -341,7 +341,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* Base implementation of chmod().
*/
function chmod($mode) {
- return @chmod($this->getLocalPath(), $mode);
+ $output = @chmod($this->getLocalPath(), $mode);
+ // We are modifying the underlying file here, so we have to clear the stat
+ // cache so that PHP understands that URI has changed too.
+ clearstatcache();
+ return $output;
}
/**
diff --git a/misc/ajax.js b/misc/ajax.js
index fb03e2b30..830c8aa1c 100644
--- a/misc/ajax.js
+++ b/misc/ajax.js
@@ -182,10 +182,17 @@ Drupal.ajax = function (base, element, element_settings) {
// can be triggered through keyboard input as well as e.g. a mousedown
// action.
if (element_settings.keypress) {
- $(element_settings.element).keypress(function (event) {
+ $(ajax.element).keypress(function (event) {
return ajax.keypressResponse(this, event);
});
}
+
+ // If necessary, prevent the browser default action of an additional event.
+ // For example, prevent the browser default action of a click, even if the
+ // AJAX behavior binds to mousedown.
+ if (element_settings.prevent) {
+ $(ajax.element).bind(element_settings.prevent, false);
+ }
};
/**
diff --git a/modules/block/block.module b/modules/block/block.module
index 6b3b23afb..b2a3f0522 100644
--- a/modules/block/block.module
+++ b/modules/block/block.module
@@ -612,7 +612,8 @@ function block_theme_initialize($theme) {
$has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField();
if (!$has_blocks) {
$default_theme = variable_get('theme_default', 'bartik');
- $regions = system_region_list($theme);
+ // Apply only to new theme's visible regions.
+ $regions = system_region_list($theme, REGIONS_VISIBLE);
$result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $block) {
// If the region isn't supported by the theme, assign the block to the theme's default region.
diff --git a/modules/book/book.module b/modules/book/book.module
index de9561fec..beb17214c 100644
--- a/modules/book/book.module
+++ b/modules/book/book.module
@@ -617,6 +617,8 @@ function _book_update_outline($node) {
'bid' => $node->book['bid'],
))
->execute();
+ // Reset the cache of stored books.
+ drupal_static_reset('book_get_books');
}
else {
if ($node->book['bid'] != db_query("SELECT bid FROM {book} WHERE nid = :nid", array(
@@ -624,6 +626,8 @@ function _book_update_outline($node) {
))->fetchField()) {
// Update the bid for this page and all children.
book_update_bid($node->book);
+ // Reset the cache of stored books.
+ drupal_static_reset('book_get_books');
}
}
@@ -895,6 +899,7 @@ function book_node_delete($node) {
db_delete('book')
->condition('mlid', $node->book['mlid'])
->execute();
+ drupal_static_reset('book_get_books');
}
}
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 9c31f4427..c17c5a6be 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -2161,7 +2161,14 @@ function comment_submit($comment) {
// 1) Filter it into HTML
// 2) Strip out all HTML tags
// 3) Convert entities back to plain-text.
- $comment->subject = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment->comment_body[LANGUAGE_NONE][0]['value'], $comment->comment_body[LANGUAGE_NONE][0]['format'])))), 29, TRUE);
+ $comment_body = $comment->comment_body[LANGUAGE_NONE][0];
+ if (isset($comment_body['format'])) {
+ $comment_text = check_markup($comment_body['value'], $comment_body['format']);
+ }
+ else {
+ $comment_text = check_plain($comment_body['value']);
+ }
+ $comment->subject = truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE);
// Edge cases where the comment body is populated only by HTML tags will
// require a default subject.
if ($comment->subject == '') {
diff --git a/modules/comment/comment.test b/modules/comment/comment.test
index 770e01d4a..c9478f491 100644
--- a/modules/comment/comment.test
+++ b/modules/comment/comment.test
@@ -251,6 +251,56 @@ class CommentHelperCase extends DrupalWebTestCase {
return $match[2];
}
+
+ /**
+ * Tests new comment marker.
+ */
+ public function testCommentNewCommentsIndicator() {
+ // Test if the right links are displayed when no comment is present for the
+ // node.
+ $this->drupalLogin($this->admin_user);
+ $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN));
+ $this->drupalGet('node');
+ $this->assertNoLink(t('@count comments', array('@count' => 0)));
+ $this->assertNoLink(t('@count new comments', array('@count' => 0)));
+ $this->assertLink(t('Read more'));
+ $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper'));
+ $this->assertTrue(count($count) == 1, t('One child found'));
+
+ // Create a new comment. This helper function may be run with different
+ // comment settings so use comment_save() to avoid complex setup.
+ $comment = (object) array(
+ 'cid' => NULL,
+ 'nid' => $this->node->nid,
+ 'node_type' => $this->node->type,
+ 'pid' => 0,
+ 'uid' => $this->loggedInUser->uid,
+ 'status' => COMMENT_PUBLISHED,
+ 'subject' => $this->randomName(),
+ 'hostname' => ip_address(),
+ 'language' => LANGUAGE_NONE,
+ 'comment_body' => array(LANGUAGE_NONE => array($this->randomName())),
+ );
+ comment_save($comment);
+ $this->drupalLogout();
+
+ // Log in with 'web user' and check comment links.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node');
+ $this->assertLink(t('1 new comment'));
+ $this->clickLink(t('1 new comment'));
+ $this->assertRaw('<a id="new"></a>', t('Found "new" marker.'));
+ $this->assertTrue($this->xpath('//a[@id=:new]/following-sibling::a[1][@id=:comment_id]', array(':new' => 'new', ':comment_id' => 'comment-1')), t('The "new" anchor is positioned at the right comment.'));
+
+ // Test if "new comment" link is correctly removed.
+ $this->drupalGet('node');
+ $this->assertLink(t('1 comment'));
+ $this->assertLink(t('Read more'));
+ $this->assertNoLink(t('1 new comment'));
+ $this->assertNoLink(t('@count new comments', array('@count' => 0)));
+ $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper'));
+ $this->assertTrue(count($count) == 2, print_r($count, TRUE));
+ }
}
class CommentInterfaceTest extends CommentHelperCase {
@@ -1912,4 +1962,19 @@ class CommentFieldsTest extends CommentHelperCase {
$this->postComment($book_node, $this->randomName(), $this->randomName());
$this->postComment($poll_node, $this->randomName(), $this->randomName());
}
+
+ /**
+ * Test that comment module works correctly with plain text format.
+ */
+ function testCommentFormat() {
+ // Disable text processing for comments.
+ $this->drupalLogin($this->admin_user);
+ $edit = array('instance[settings][text_processing]' => 0);
+ $this->drupalPost('admin/structure/types/manage/article/comment/fields/comment_body', $edit, t('Save settings'));
+
+ // Post a comment without an explicit subject.
+ $this->drupalLogin($this->web_user);
+ $edit = array('comment_body[und][0][value]' => $this->randomName(8));
+ $this->drupalPost('node/' . $this->node->nid, $edit, t('Save'));
+ }
}
diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc
index 963e6f8eb..0655e7564 100644
--- a/modules/dblog/dblog.admin.inc
+++ b/modules/dblog/dblog.admin.inc
@@ -10,6 +10,8 @@
*
* Messages are truncated at 56 chars. Full-length message could be viewed at
* the message details page.
+ *
+ * @ingroup logging_severity_levels
*/
function dblog_overview() {
$filter = dblog_build_filter_query();
diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test
index 5717455ef..ca844a361 100644
--- a/modules/dblog/dblog.test
+++ b/modules/dblog/dblog.test
@@ -528,6 +528,8 @@ class DBLogTestCase extends DrupalWebTestCase {
* CSS class attribute.
* @return
* The watchdog severity constant or NULL if not found.
+ *
+ * @ingroup logging_severity_levels
*/
protected function getSeverityConstant($class) {
// Reversed array from dblog_overview().
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index ba44c7356..3287dd555 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1132,7 +1132,7 @@ function hook_field_attach_form($entity_type, $entity, &$form, &$form_state, $la
*
* See field_attach_load() for details and arguments.
*/
-function hook_field_attach_load($entity_type, &$entities, $age, $options) {
+function hook_field_attach_load($entity_type, $entities, $age, $options) {
// @todo Needs function body.
}
@@ -1580,7 +1580,7 @@ function hook_field_storage_details_alter(&$details, $field) {
* non-deleted fields. If unset or FALSE, only non-deleted fields should be
* loaded.
*/
-function hook_field_storage_load($entity_type, &$entities, $age, $fields, $options) {
+function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
$field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc
index 845f04109..66d93e963 100644
--- a/modules/field/field.form.inc
+++ b/modules/field/field.form.inc
@@ -373,7 +373,7 @@ function field_default_form_errors($entity_type, $entity, $field, $instance, $la
* to return just the changed part of the form.
*/
function field_add_more_submit($form, &$form_state) {
- $button = $form_state['clicked_button'];
+ $button = $form_state['triggering_element'];
// Go one level up in the form, to the widgets container.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
@@ -398,7 +398,7 @@ function field_add_more_submit($form, &$form_state) {
* @see field_add_more_submit()
*/
function field_add_more_js($form, $form_state) {
- $button = $form_state['clicked_button'];
+ $button = $form_state['triggering_element'];
// Go one level up in the form, to the widgets container.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index 6f49167ec..10bae64b5 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -468,7 +468,6 @@ function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $i
* Implements hook_field_storage_query().
*/
function field_sql_storage_field_storage_query(EntityFieldQuery $query) {
- $groups = array();
if ($query->age == FIELD_LOAD_CURRENT) {
$tablename_function = '_field_sql_storage_tablename';
$id_key = 'entity_id';
@@ -499,26 +498,12 @@ function field_sql_storage_field_storage_query(EntityFieldQuery $query) {
}
}
- // Add field conditions.
- foreach ($query->fieldConditions as $key => $condition) {
- $table_alias = $table_aliases[$key];
- $field = $condition['field'];
- // Add the specified condition.
- $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $condition['column']);
- $query->addCondition($select_query, $sql_field, $condition);
- // Add delta / language group conditions.
- foreach (array('delta', 'language') as $column) {
- if (isset($condition[$column . '_group'])) {
- $group_name = $condition[$column . '_group'];
- if (!isset($groups[$column][$group_name])) {
- $groups[$column][$group_name] = $table_alias;
- }
- else {
- $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column");
- }
- }
- }
- }
+ // Add field conditions. We need a fresh grouping cache.
+ drupal_static_reset('_field_sql_storage_query_field_conditions');
+ _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldConditions, $table_aliases, '_field_sql_storage_columnname');
+
+ // Add field meta conditions.
+ _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldMetaConditions, $table_aliases, '_field_sql_storage_query_columnname');
if (isset($query->deleted)) {
$select_query->condition("$field_base_table.deleted", (int) $query->deleted);
@@ -592,6 +577,51 @@ function _field_sql_storage_query_join_entity(SelectQuery $select_query, $entity
}
/**
+ * Adds field (meta) conditions to the given query objects respecting groupings.
+ *
+ * @param EntityFieldQuery $query
+ * The field query object to be processed.
+ * @param SelectQuery $select_query
+ * The SelectQuery that should get grouping conditions.
+ * @param condtions
+ * The conditions to be added.
+ * @param $table_aliases
+ * An associative array of table aliases keyed by field index.
+ * @param $column_callback
+ * A callback that should return the column name to be used for the field
+ * conditions. Accepts a field name and a field column name as parameters.
+ */
+function _field_sql_storage_query_field_conditions(EntityFieldQuery $query, SelectQuery $select_query, $conditions, $table_aliases, $column_callback) {
+ $groups = &drupal_static(__FUNCTION__, array());
+ foreach ($conditions as $key => $condition) {
+ $table_alias = $table_aliases[$key];
+ $field = $condition['field'];
+ // Add the specified condition.
+ $sql_field = "$table_alias." . $column_callback($field['field_name'], $condition['column']);
+ $query->addCondition($select_query, $sql_field, $condition);
+ // Add delta / language group conditions.
+ foreach (array('delta', 'language') as $column) {
+ if (isset($condition[$column . '_group'])) {
+ $group_name = $condition[$column . '_group'];
+ if (!isset($groups[$column][$group_name])) {
+ $groups[$column][$group_name] = $table_alias;
+ }
+ else {
+ $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column");
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Field meta condition column callback.
+ */
+function _field_sql_storage_query_columnname($field_name, $column) {
+ return $column;
+}
+
+/**
* Implements hook_field_storage_delete_revision().
*
* This function actually deletes the data from the database.
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index 608679bbb..2518ebcfc 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -343,7 +343,7 @@ function list_allowed_values_string($values) {
function list_field_update_forbid($field, $prior_field, $has_data) {
if ($field['module'] == 'list' && $has_data) {
// Forbid any update that removes allowed values with actual data.
- $lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($prior_field['settings']['allowed_values']));
+ $lost_keys = array_diff(array_keys($prior_field['settings']['allowed_values']), array_keys($field['settings']['allowed_values']));
if (_list_values_in_use($field, $lost_keys)) {
throw new FieldUpdateForbiddenException(t('Cannot update a list field to not include keys with existing data.'));
}
diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test
index 941d2b4cb..dec09560f 100644
--- a/modules/field/modules/list/tests/list.test
+++ b/modules/field/modules/list/tests/list.test
@@ -55,6 +55,23 @@ class ListFieldTestCase extends FieldTestCase {
$this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists'));
$this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists'));
+ // Use one of the values in an actual entity, and check that this value
+ // cannot be removed from the list.
+ $entity = field_test_create_stub_entity();
+ $entity->{$this->field_name}[$langcode][0] = array('value' => 1);
+ field_test_entity_save($entity);
+ $this->field['settings']['allowed_values'] = array(2 => 'Two');
+ try {
+ field_update_field($this->field);
+ $this->fail(t('Cannot update a list field to not include keys with existing data.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot update a list field to not include keys with existing data.'));
+ }
+ // Empty the value, so that we can actually remove the option.
+ $entity->{$this->field_name}[$langcode] = array();
+ field_test_entity_save($entity);
+
// Removed options do not appear.
$this->field['settings']['allowed_values'] = array(2 => 'Two');
field_update_field($this->field);
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index b42fed7e0..59369370e 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -464,7 +464,7 @@ class TextTranslationTestCase extends DrupalWebTestCase {
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->drupalGet("node/$node->nid/translate");
$this->clickLink(t('add translation'));
- $this->assertFieldByXPath("//textarea[@name='body[fr][0][value]']", $body, t('The textfield widget is populated.'));
+ $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, t('The textfield widget is populated.'));
}
/**
@@ -484,17 +484,17 @@ class TextTranslationTestCase extends DrupalWebTestCase {
);
// Create an article with the first body input format set to "Full HTML".
- $langcode = 'en';
$title = $this->randomName();
$edit = array(
'title' => $title,
- 'language' => $langcode,
+ 'language' => 'en',
);
$this->drupalPost('node/add/article', $edit, t('Save'));
// Populate the body field: the first item gets the "Full HTML" input
// format, the second one "Filtered HTML".
$formats = array('full_html', 'filtered_html');
+ $langcode = LANGUAGE_NONE;
foreach ($body as $delta => $value) {
$edit = array(
"body[$langcode][$delta][value]" => $value,
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index 96beb1334..b594faca3 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -509,6 +509,10 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle
'#cell_attributes' => array('colspan' => 3),
'#prefix' => '<div class="add-new-placeholder">&nbsp;</div>',
),
+ 'translatable' => array(
+ '#type' => 'value',
+ '#value' => FALSE,
+ ),
);
}
@@ -753,7 +757,7 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
$field = array(
'field_name' => $values['field_name'],
'type' => $values['type'],
- 'translatable' => TRUE,
+ 'translatable' => $values['translatable'],
);
$instance = array(
'field_name' => $field['field_name'],
diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module
index 7355e879b..27ef3c2d7 100644
--- a/modules/field_ui/field_ui.module
+++ b/modules/field_ui/field_ui.module
@@ -358,7 +358,7 @@ function field_ui_form_node_type_form_alter(&$form, $form_state) {
* Redirect to manage fields form.
*/
function field_ui_form_node_type_form_submit($form, &$form_state) {
- if ($form_state['clicked_button']['#parents'][0] === 'save_continue') {
+ if ($form_state['triggering_element']['#parents'][0] === 'save_continue') {
$form_state['redirect'] = _field_ui_bundle_admin_path('node', $form_state['values']['type']) .'/fields';
}
}
diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test
index 5d2ff9bfa..9ff6c1720 100644
--- a/modules/field_ui/field_ui.test
+++ b/modules/field_ui/field_ui.test
@@ -613,7 +613,8 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase {
// Render a cloned node, so that we do not alter the original.
$clone = clone $node;
- $output = drupal_render(node_view($clone, $view_mode));
+ $element = node_view($clone, $view_mode);
+ $output = drupal_render($element);
$this->verbose(t('Rendered node - view mode: @view_mode', array('@view_mode' => $view_mode)) . '<hr />'. $output);
// Assign content so that DrupalWebTestCase functions can be used.
diff --git a/modules/file/file.module b/modules/file/file.module
index 3e4525119..83de0f622 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -533,7 +533,7 @@ function file_managed_file_validate(&$element, &$form_state) {
// If referencing an existing file, only allow if there are existing
// references. This prevents unmanaged files from being deleted if this
// item were to be deleted.
- $clicked_button = end($form_state['clicked_button']['#parents']);
+ $clicked_button = end($form_state['triggering_element']['#parents']);
if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
if ($file = file_load($element['fid']['#value'])) {
if ($file->status == FILE_STATUS_PERMANENT) {
diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test
index 044c6f698..0e5f97d84 100644
--- a/modules/file/tests/file.test
+++ b/modules/file/tests/file.test
@@ -12,7 +12,16 @@ class FileFieldTestCase extends DrupalWebTestCase {
protected $admin_user;
function setUp() {
- parent::setUp('file', 'file_module_test');
+ // Since this is a base class for many test cases, support the same
+ // flexibility that DrupalWebTestCase::setUp() has for the modules to be
+ // passed in as either an array or a variable number of string arguments.
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'file';
+ $modules[] = 'file_module_test';
+ parent::setUp($modules);
$this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access'));
$this->drupalLogin($this->admin_user);
}
@@ -112,7 +121,7 @@ class FileFieldTestCase extends DrupalWebTestCase {
/**
* Upload a file to a node.
*/
- function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE) {
+ function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) {
$langcode = LANGUAGE_NONE;
$edit = array(
"title" => $this->randomName(),
@@ -124,7 +133,8 @@ class FileFieldTestCase extends DrupalWebTestCase {
}
else {
// Add a new node.
- $node = $this->drupalCreateNode(array('type' => $nid_or_type));
+ $extras['type'] = $nid_or_type;
+ $node = $this->drupalCreateNode($extras);
$nid = $node->nid;
// Save at least one revision to better simulate a real site.
$this->drupalCreateNode(get_object_vars($node));
@@ -1019,21 +1029,19 @@ class FileTokenReplaceTestCase extends FileFieldTestCase {
// Load the node and the file.
$node = node_load($nid, NULL, TRUE);
- $file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
- $file->description = 'File description.';
+ $file = file_load($node->{$field_name}[LANGUAGE_NONE][0]['fid']);
// Generate and test sanitized tokens.
$tests = array();
$tests['[file:fid]'] = $file->fid;
$tests['[file:name]'] = check_plain($file->filename);
- $tests['[file:description]'] = filter_xss($file->description);
- $tests['[file:path]'] = filter_xss($file->uri);
- $tests['[file:mime]'] = filter_xss($file->filemime);
+ $tests['[file:path]'] = check_plain($file->uri);
+ $tests['[file:mime]'] = check_plain($file->filemime);
$tests['[file:size]'] = format_size($file->filesize);
- $tests['[file:url]'] = url(file_create_url($file->uri), $url_options);
+ $tests['[file:url]'] = check_plain(file_create_url($file->uri));
$tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language);
$tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language);
- $tests['[file:owner]'] = $this->admin_user->name;
+ $tests['[file:owner]'] = check_plain(format_username($this->admin_user));
$tests['[file:owner:uid]'] = $file->uid;
// Test to make sure that we generated something for each token.
@@ -1046,7 +1054,6 @@ class FileTokenReplaceTestCase extends FileFieldTestCase {
// Generate and test unsanitized tokens.
$tests['[file:name]'] = $file->filename;
- $tests['[file:description]'] = $file->description;
$tests['[file:path]'] = $file->uri;
$tests['[file:mime]'] = $file->filemime;
$tests['[file:size]'] = format_size($file->filesize);
@@ -1057,3 +1064,46 @@ class FileTokenReplaceTestCase extends FileFieldTestCase {
}
}
}
+
+/**
+ * Test class to test file access on private nodes.
+ */
+class FilePrivateTestCase extends FileFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Private file test',
+ 'description' => 'Uploads a test to a private node and checks access.',
+ 'group' => 'File',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('node_access_test');
+ node_access_rebuild();
+ variable_set('node_access_test_private', TRUE);
+ }
+
+ /**
+ * Uploads a file to a private node, then tests that access is allowed and denied when appropriate.
+ */
+ function testPrivateFile() {
+ // Use 'page' instead of 'article', so that the 'article' image field does
+ // not conflict with this test. If in the future the 'page' type gets its
+ // own default file or image field, this test can be made more robust by
+ // using a custom node type.
+ $type_name = 'page';
+ $field_name = strtolower($this->randomName());
+ $this->createFileField($field_name, $type_name, array('uri_scheme' => 'private'));
+
+ $test_file = $this->getTestFile('text');
+ $nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, array('private' => TRUE));
+ $node = node_load($nid, NULL, TRUE);
+ $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0];
+ // Ensure the file can be downloaded.
+ $this->drupalGet(file_create_url($node_file->uri));
+ $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the shipped file.'));
+ $this->drupalLogOut();
+ $this->drupalGet(file_create_url($node_file->uri));
+ $this->assertNoResponse(200, t('Confirmed that access is denied for the file without the needed permission.'));
+ }
+}
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index a3d1bde40..67d08333d 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -792,7 +792,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
*/
function testLineBreakFilter() {
// Setup dummy filter object.
- $filter = new stdClass;
+ $filter = new stdClass();
$filter->callback = '_filter_autop';
// Since the line break filter naturally needs plenty of newlines in test
@@ -1156,7 +1156,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
*/
function testHtmlEscapeFilter() {
// Setup dummy filter object.
- $filter = new stdClass;
+ $filter = new stdClass();
$filter->callback = '_filter_html_escape';
$tests = array(
@@ -1174,7 +1174,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
*/
function testUrlFilter() {
// Setup dummy filter object.
- $filter = new stdClass;
+ $filter = new stdClass();
$filter->callback = '_filter_url';
$filter->settings = array(
'filter_url_length' => 496,
@@ -1509,7 +1509,7 @@ www.example.com with a newline in comments -->
*/
function testUrlFilterContent() {
// Setup dummy filter object.
- $filter = new stdClass;
+ $filter = new stdClass();
$filter->settings = array(
'filter_url_length' => 496,
);
diff --git a/modules/locale/locale-rtl.css b/modules/locale/locale-rtl.css
new file mode 100644
index 000000000..aaf1988dd
--- /dev/null
+++ b/modules/locale/locale-rtl.css
@@ -0,0 +1,12 @@
+
+#locale-translation-filter-form .form-item-language,
+#locale-translation-filter-form .form-item-translation,
+#locale-translation-filter-form .form-item-group {
+ float: right;
+ padding-left: .8em;
+ padding-right: 0;
+}
+#locale-translation-filter-form .form-actions {
+ float: right;
+ padding: 3ex 1em 0 0;
+}
diff --git a/modules/locale/locale.css b/modules/locale/locale.css
index 1d875a2bf..38112b506 100644
--- a/modules/locale/locale.css
+++ b/modules/locale/locale.css
@@ -21,14 +21,12 @@
width: 100%;
}
#locale-translation-filter-form .form-actions {
- float: left;
- padding: 3ex 0 0 1em;
+ float: left; /* LTR */
+ padding: 3ex 0 0 1em; /* LTR */
}
-
.language-switcher-locale-session a.active {
color: #0062A0;
}
-
.language-switcher-locale-session a.session-active {
color: #000000;
}
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index 42a6dbc48..3ae6d91a5 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -2137,6 +2137,11 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase {
);
$this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
+
+ // Make node body translatable.
+ $field = field_info_field('body');
+ $field['translatable'] = TRUE;
+ field_update_field($field);
}
/**
diff --git a/modules/menu/menu.install b/modules/menu/menu.install
index 717c5e712..13cb3cb50 100644
--- a/modules/menu/menu.install
+++ b/modules/menu/menu.install
@@ -109,6 +109,58 @@ function menu_update_7000() {
}
/**
+ * Rename "Primary Links" and "Secondary Links" to their Drupal 7 equivalents.
+ */
+function menu_update_7001() {
+ // Migrate D6 menu_primary_links_source to D7 menu_main_links_source (without
+ // renaming).
+ if (variable_get('menu_primary_links_source') !== NULL) {
+ variable_set('menu_main_links_source', variable_get('menu_primary_links_source'));
+ variable_del('menu_primary_links_source');
+ }
+
+ // Rename each menu, and any settings that refer to the old menu name.
+ $rename = array(
+ 'primary-links' => array('main-menu', 'Main menu'),
+ 'secondary-links' => array('secondary-menu', 'Secondary menu'),
+ );
+ foreach ($rename as $from_menu => $to) {
+ list($to_menu, $to_title) = $to;
+ // Rename the menu, and links in the menu.
+ db_update('menu_custom')
+ ->fields(array('menu_name' => $to_menu, 'title' => $to_title))
+ ->condition('menu_name', $from_menu)
+ ->execute();
+ db_update('menu_links')
+ ->fields(array('menu_name' => $to_menu))
+ ->condition('menu_name', $from_menu)
+ ->execute();
+
+ // Update any content type that used this menu as a default menu.
+ // Note: these variables may be unset/default, in which case we leave them
+ // alone. See menu_update_7000()
+ foreach (_update_7000_node_get_types() as $type => $type_object) {
+ $menu_options = variable_get('menu_options_' . $type);
+ if ($menu_options !== NULL) {
+ variable_set('menu_options_' . $type, str_replace($from_menu, $to_menu, $menu_options));
+ if (variable_get('menu_parent_' . $type) == $from_menu . ':0') {
+ variable_set('menu_parent_' . $type, $to_menu . ':0');
+ }
+ }
+ }
+
+ // Update the "source for primary links" and "source for secondary links" to
+ // follow.
+ if (variable_get('menu_main_links_source') == $from_menu) {
+ variable_set('menu_main_links_source', $to_menu);
+ }
+ if (variable_get('menu_secondary_links_source') == $from_menu) {
+ variable_set('menu_secondary_links_source', $to_menu);
+ }
+ }
+}
+
+/**
* @} End of "defgroup updates-7.x-extra"
* The next series of updates should start at 8000.
*/
diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 90ffd8b7f..bc2eb65b1 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -1022,7 +1022,7 @@ function hook_node_type_delete($info) {
*/
function hook_delete($node) {
db_delete('mytable')
- ->condition('nid', $nid->nid)
+ ->condition('nid', $node->nid)
->execute();
}
diff --git a/modules/node/node.install b/modules/node/node.install
index 852c1117b..2498091fc 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -476,7 +476,7 @@ function _update_7000_node_get_types() {
$extra_types = array_diff($all_types, array_keys($node_types));
foreach ($extra_types as $type) {
- $type_object = new stdClass;
+ $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
@@ -610,7 +610,6 @@ function node_update_7006(&$sandbox) {
'module' => 'text',
'cardinality' => 1,
'entity_types' => array('node'),
- 'translatable' => TRUE,
);
_update_7000_field_create_field($body_field);
@@ -628,6 +627,8 @@ function node_update_7006(&$sandbox) {
'entity_type' => 'node',
'bundle' => $node_type->type,
'label' => $node_type->body_label,
+ 'description' => isset($node_type->description) ? $node_type->description : '',
+ 'required' => (isset($node_type->min_word_count) && $node_type->min_word_count > 0) ? 1 : 0,
'widget' => array(
'type' => 'text_textarea_with_summary',
'settings' => array(
diff --git a/modules/node/node.module b/modules/node/node.module
index 66e93c737..6c32a2799 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -558,7 +558,6 @@ function node_add_body_field($type, $label = 'Body') {
'field_name' => 'body',
'type' => 'text_with_summary',
'entity_types' => array('node'),
- 'translatable' => TRUE,
);
$field = field_create_field($field);
}
@@ -1404,11 +1403,13 @@ function node_show($node, $message = FALSE) {
drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
}
+ // For markup consistency with other pages, use node_view_multiple() rather than node_view().
+ $nodes = node_view_multiple(array($node->nid => $node), 'full');
+
// Update the history table, stating that this user viewed this node.
node_tag_new($node);
- // For markup consistency with other pages, use node_view_multiple() rather than node_view().
- return node_view_multiple(array($node->nid => $node), 'full');
+ return $nodes;
}
/**
@@ -2159,7 +2160,7 @@ function node_block_save($delta = '', $edit = array()) {
}
/**
- * Find the most recent nodes that are available to the current user.
+ * Finds the most recently changed nodes that are available to the current user.
*
* @param $number
* (optional) The maximum number of nodes to find. Defaults to 10.
@@ -2415,7 +2416,10 @@ function node_block_list_alter(&$blocks) {
}
/**
- * A generic function for generating RSS feeds from a set of nodes.
+ * Generates and prints an RSS feed.
+ *
+ * Generates an RSS feed from an array of node IDs, and prints it with an HTTP
+ * header, with Content Type set to RSS/XML.
*
* @param $nids
* An array of node IDs (nid). Defaults to FALSE so empty feeds can be
@@ -3337,6 +3341,9 @@ function node_access_acquire_grants($node, $delete = TRUE) {
* node_access can use this function when doing mass updates due to widespread
* permission changes.
*
+ * Note: Don't call this function directly from a contributed module. Call
+ * node_access_acquire_grants() instead.
+ *
* @param $node
* The $node being written to. All that is necessary is that it contains a
* nid.
diff --git a/modules/path/path.test b/modules/path/path.test
index 4112e5f6b..f42ec81be 100644
--- a/modules/path/path.test
+++ b/modules/path/path.test
@@ -279,8 +279,9 @@ class PathLanguageTestCase extends DrupalWebTestCase {
$this->drupalGet('node/' . $english_node->nid . '/translate');
$this->clickLink(t('add translation'));
$edit = array();
+ $langcode = LANGUAGE_NONE;
$edit["title"] = $this->randomName();
- $edit["body[fr][0][value]"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
$french_alias = $this->randomName();
$edit['path[alias]'] = $french_alias;
$this->drupalPost(NULL, $edit, t('Save'));
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index f45b8bda7..861969573 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -485,6 +485,10 @@ function poll_load($nodes) {
foreach ($nodes as $node) {
$poll = db_query("SELECT runtime, active FROM {poll} WHERE nid = :nid", array(':nid' => $node->nid))->fetchObject();
+ if (empty($poll)) {
+ $poll = new stdClass();
+ }
+
// Load the appropriate choices into the $poll object.
$poll->choice = db_select('poll_choice', 'c')
->addTag('translatable')
diff --git a/modules/profile/profile.module b/modules/profile/profile.module
index 2374fe8ea..8cac6d797 100644
--- a/modules/profile/profile.module
+++ b/modules/profile/profile.module
@@ -544,6 +544,7 @@ function template_preprocess_profile_block(&$variables) {
// Supply filtered version of $fields that have values.
foreach ($variables['fields'] as $field) {
if ($field->value) {
+ $variables['profile'][$field->name] = new stdClass();
$variables['profile'][$field->name]->title = check_plain($field->title);
$variables['profile'][$field->name]->value = $field->value;
$variables['profile'][$field->name]->type = $field->type;
diff --git a/modules/search/search.admin.inc b/modules/search/search.admin.inc
index afa02defb..d93c85288 100644
--- a/modules/search/search.admin.inc
+++ b/modules/search/search.admin.inc
@@ -143,7 +143,7 @@ function search_admin_settings($form) {
*/
function search_admin_settings_validate($form, &$form_state) {
// Check whether we selected a valid default.
- if ($form_state['clicked_button']['#value'] != t('Reset to defaults')) {
+ if ($form_state['triggering_element']['#value'] != t('Reset to defaults')) {
$new_modules = array_filter($form_state['values']['search_active_modules']);
$default = $form_state['values']['search_default_module'];
if (!in_array($default, $new_modules, TRUE)) {
@@ -164,7 +164,7 @@ function search_admin_settings_submit($form, &$form_state) {
}
$current_modules = variable_get('search_active_modules', array('node', 'user'));
// Check whether we are resetting the values.
- if ($form_state['clicked_button']['#value'] == t('Reset to defaults')) {
+ if ($form_state['triggering_element']['#value'] == t('Reset to defaults')) {
$new_modules = array('node', 'user');
}
else {
diff --git a/modules/search/search.api.php b/modules/search/search.api.php
index 557a56e51..8d6e2399f 100644
--- a/modules/search/search.api.php
+++ b/modules/search/search.api.php
@@ -107,6 +107,10 @@ function hook_search_reset() {
/**
* Report the status of indexing.
*
+ * The core search module only invokes this hook on active modules.
+ * Implementing modules do not need to check whether they are active when
+ * calculating their return values.
+ *
* @return
* An associative array with the key-value pairs:
* - 'remaining': The number of items left to index.
diff --git a/modules/search/tests/search_embedded_form.module b/modules/search/tests/search_embedded_form.module
index c0058f74d..484579674 100644
--- a/modules/search/tests/search_embedded_form.module
+++ b/modules/search/tests/search_embedded_form.module
@@ -65,5 +65,6 @@ function search_embedded_form_form_submit($form, &$form_state) {
* Adds the test form to search results.
*/
function search_embedded_form_preprocess_search_result(&$variables) {
- $variables['snippet'] .= drupal_render(drupal_get_form('search_embedded_form_form'));
+ $form = drupal_get_form('search_embedded_form_form');
+ $variables['snippet'] .= drupal_render($form);
}
diff --git a/modules/shortcut/shortcut.install b/modules/shortcut/shortcut.install
index 9dbab806d..60ee6be8d 100644
--- a/modules/shortcut/shortcut.install
+++ b/modules/shortcut/shortcut.install
@@ -25,6 +25,13 @@ function shortcut_install() {
'weight' => -19,
),
);
+ // If Drupal is being installed, rebuild the menu before saving the shortcut
+ // set, to make sure the links defined above can be correctly saved. (During
+ // installation, the menu might not have been built at all yet, or it might
+ // have been built but without the node module's links in it.)
+ if (drupal_installation_attempted()) {
+ menu_rebuild();
+ }
shortcut_set_save($shortcut_set);
}
diff --git a/modules/simpletest/tests/entity_query.test b/modules/simpletest/tests/entity_query.test
index d28d5a35c..0fe8106ef 100644
--- a/modules/simpletest/tests/entity_query.test
+++ b/modules/simpletest/tests/entity_query.test
@@ -1050,6 +1050,133 @@ class EntityFieldQueryTestCase extends DrupalWebTestCase {
}
/**
+ * Tests field meta conditions.
+ */
+ function testEntityFieldQueryMetaConditions() {
+ // Make a test field translatable.
+ $this->fields[0]['translatable'] = TRUE;
+ field_update_field($this->fields[0]);
+ field_test_entity_info_translatable('test_entity', TRUE);
+ drupal_static_reset('field_available_languages');
+
+ // Create more items with different languages.
+ $entity = new stdClass();
+ $entity->ftid = 1;
+ $entity->ftvid = 1;
+ $entity->fttype = 'test_bundle';
+ $j = 0;
+
+ foreach (array(LANGUAGE_NONE, 'en') as $langcode) {
+ for ($i = 0; $i < 4; $i++) {
+ $entity->{$this->field_names[0]}[$langcode][$i]['value'] = $i + $j;
+ }
+ $j += 4;
+ }
+
+ field_attach_update('test_entity', $entity);
+
+ // Test delta field meta condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldDeltaCondition($this->fields[0], 0, '>');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ ), t('Test with a delta meta condition.'));
+
+ // Test language field meta condition.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ ), t('Test with a language meta condition.'));
+
+ // Test delta grouping.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'group')
+ ->fieldDeltaCondition($this->fields[0], 1, '<', 'group');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ ), t('Test with a grouped delta meta condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'group')
+ ->fieldDeltaCondition($this->fields[0], 1, '>=', 'group');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta meta condition (empty result set).'));
+
+ // Test language grouping.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group')
+ ->fieldLanguageCondition($this->fields[0], 'en', '!=', NULL, 'group');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ ), t('Test with a grouped language meta condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group')
+ ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', NULL, 'group');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a grouped language meta condition (empty result set).'));
+
+ // Test delta and language grouping.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language')
+ ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language')
+ ->fieldLanguageCondition($this->fields[0], 'en', '!=', 'delta', 'language');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity', 1),
+ ), t('Test with a grouped delta + language meta condition.'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language')
+ ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language')
+ ->fieldLanguageCondition($this->fields[0], 'en', '!=', 'delta', 'language');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, delta condition unsatisifed).'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language')
+ ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language')
+ ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', 'delta', 'language');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, language condition unsatisifed).'));
+
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity', '=')
+ ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language')
+ ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language')
+ ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '!=', 'delta', 'language');
+ $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, both conditions unsatisifed).'));
+
+ // Test grouping with another field to ensure that grouping cache is reset
+ // properly.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', 'test_entity_bundle', '=')
+ ->fieldCondition($this->fields[1], 'shape', 'circle', '=', 'delta', 'language')
+ ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'delta', 'language')
+ ->fieldDeltaCondition($this->fields[1], 1, '=', 'delta', 'language')
+ ->fieldLanguageCondition($this->fields[1], LANGUAGE_NONE, '=', 'delta', 'language');
+ $this->assertEntityFieldQuery($query, array(
+ array('test_entity_bundle', 5),
+ ), t('Test grouping cache.'));
+ }
+
+ /**
* Tests the routing feature of EntityFieldQuery.
*/
function testEntityFieldQueryRouting() {
diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test
index dc12b1b73..9dbe5464f 100644
--- a/modules/simpletest/tests/file.test
+++ b/modules/simpletest/tests/file.test
@@ -196,10 +196,13 @@ class FileTestCase extends DrupalWebTestCase {
* @return
* File object.
*/
- function createFile($filepath = NULL, $contents = NULL, $scheme = 'public') {
+ function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
if (!isset($filepath)) {
$filepath = $this->randomName();
}
+ if (!isset($scheme)) {
+ $scheme = file_default_scheme();
+ }
$filepath = $scheme . '://' . $filepath;
if (!isset($contents)) {
@@ -427,7 +430,7 @@ class FileValidatorTest extends DrupalWebTestCase {
// Maximum size.
if (image_get_toolkit()) {
// Copy the image so that the original doesn't get resized.
- copy(drupal_realpath('misc/druplicon.png'), 'temporary://druplicon.png');
+ copy('misc/druplicon.png', 'temporary://druplicon.png');
$this->image->uri = 'temporary://druplicon.png';
$errors = file_validate_image_resolution($this->image, '10x5');
@@ -437,7 +440,7 @@ class FileValidatorTest extends DrupalWebTestCase {
$this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File');
$this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File');
- drupal_unlink(drupal_realpath('temporary://druplicon.png'));
+ drupal_unlink('temporary://druplicon.png');
}
else {
// TODO: should check that the error is returned if no toolkit is available.
@@ -531,18 +534,34 @@ class FileUnmanagedSaveDataTest extends FileTestCase {
$filepath = file_unmanaged_save_data($contents);
$this->assertTrue($filepath, t('Unnamed file saved correctly.'));
$this->assertEqual(file_uri_scheme($filepath), file_default_scheme(), t("File was placed in Drupal's files directory."));
- $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.'));
+ $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
// Provide a filename.
$filepath = file_unmanaged_save_data($contents, 'public://asdf.txt', FILE_EXISTS_REPLACE);
$this->assertTrue($filepath, t('Unnamed file saved correctly.'));
$this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.'));
- $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.'));
+ $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
$this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664));
}
}
/**
+ * Tests the file_unmanaged_save_data() function on remote filesystems.
+ */
+class RemoteFileUnmanagedSaveDataTest extends FileUnmanagedSaveDataTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
+
+/**
* Test the file_save_upload() function.
*/
class FileSaveUploadTest extends FileHookTestCase {
@@ -863,6 +882,22 @@ class FileSaveUploadTest extends FileHookTestCase {
}
/**
+ * Test the file_save_upload() function on remote filesystems.
+ */
+class RemoteFileSaveUploadTest extends FileSaveUploadTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
+
+/**
* Directory related tests.
*/
class FileDirectoryTest extends FileTestCase {
@@ -879,7 +914,7 @@ class FileDirectoryTest extends FileTestCase {
*/
function testFileCheckDirectoryHandling() {
// A directory to operate on.
- $directory = file_stream_wrapper_get_instance_by_scheme(file_default_scheme())->getDirectoryPath() . '/' . $this->randomName() . '/' . $this->randomName();
+ $directory = file_default_scheme() . '://' . $this->randomName() . '/' . $this->randomName();
$this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.'));
// Non-existent directory.
@@ -986,6 +1021,22 @@ class FileDirectoryTest extends FileTestCase {
}
/**
+ * Directory related tests.
+ */
+class RemoteFileDirectoryTest extends FileDirectoryTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
+
+/**
* Tests the file_scan_directory() function.
*/
class FileScanDirectoryTest extends FileTestCase {
@@ -1114,6 +1165,21 @@ class FileScanDirectoryTest extends FileTestCase {
}
}
+/**
+ * Tests the file_scan_directory() function on remote filesystems.
+ */
+class RemoteFileScanDirectoryTest extends FileScanDirectoryTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
/**
* Deletion related tests.
@@ -1160,6 +1226,21 @@ class FileUnmanagedDeleteTest extends FileTestCase {
}
}
+/**
+ * Deletion related tests on remote filesystems.
+ */
+class RemoteFileUnmanagedDeleteTest extends FileUnmanagedDeleteTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
/**
* Deletion related tests.
@@ -1237,6 +1318,21 @@ class FileUnmanagedDeleteRecursiveTest extends FileTestCase {
}
}
+/**
+ * Deletion related tests on remote filesystems.
+ */
+class RemoteFileUnmanagedDeleteRecursiveTest extends FileUnmanagedDeleteRecursiveTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
/**
* Unmanaged move related tests.
@@ -1310,6 +1406,21 @@ class FileUnmanagedMoveTest extends FileTestCase {
}
}
+/**
+ * Unmanaged move related tests on remote filesystems.
+ */
+class RemoteFileUnmanagedMoveTest extends FileUnmanagedMoveTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
/**
* Unmanaged copy related tests.
@@ -1400,6 +1511,22 @@ class FileUnmanagedCopyTest extends FileTestCase {
}
/**
+ * Unmanaged copy related tests on remote filesystems.
+ */
+class RemoteFileUnmanagedCopyTest extends FileUnmanagedCopyTest {
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['group'] = 'File API (remote)';
+ return $info;
+ }
+
+ function setUp() {
+ parent::setUp('file_test');
+ variable_set('file_default_scheme', 'dummy-remote');
+ }
+}
+
+/**
* Deletion related tests.
*/
class FileDeleteTest extends FileHookTestCase {
diff --git a/modules/simpletest/tests/file_test.module b/modules/simpletest/tests/file_test.module
index 2865a1fa3..b3c43e071 100644
--- a/modules/simpletest/tests/file_test.module
+++ b/modules/simpletest/tests/file_test.module
@@ -37,6 +37,11 @@ function file_test_stream_wrappers() {
'class' => 'DrupalDummyStreamWrapper',
'description' => t('Dummy wrapper for simpletest.'),
),
+ 'dummy-remote' => array(
+ 'name' => t('Dummy files (remote)'),
+ 'class' => 'DrupalDummyRemoteStreamWrapper',
+ 'description' => t('Dummy wrapper for simpletest (remote).'),
+ ),
);
}
@@ -442,3 +447,15 @@ class DrupalDummyStreamWrapper extends DrupalLocalStreamWrapper {
}
}
+/**
+ * Helper class for testing the stream wrapper registry.
+ *
+ * Dummy remote stream wrapper implementation (dummy-remote://).
+ *
+ * Basically just the public scheme but not returning a local file for realpath.
+ */
+class DrupalDummyRemoteStreamWrapper extends DrupalPublicStreamWrapper {
+ function realpath() {
+ return FALSE;
+ }
+}
diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test
index 8a7b152d9..a6c7b40e5 100644
--- a/modules/simpletest/tests/mail.test
+++ b/modules/simpletest/tests/mail.test
@@ -1,6 +1,7 @@
<?php
/**
+ * @file
* Test the Drupal mailing system.
*/
class MailTestCase extends DrupalWebTestCase implements MailSystemInterface {
@@ -63,3 +64,355 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface {
}
}
+/**
+ * Unit tests for drupal_html_to_text().
+ */
+class DrupalHtmlToTextTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'HTML to text conversion',
+ 'description' => 'Tests drupal_html_to_text().',
+ 'group' => 'Mail',
+ );
+ }
+
+ /**
+ * Converts a string to its PHP source equivalent for display in test messages.
+ *
+ * @param $text
+ * The text string to convert.
+ *
+ * @return
+ * An HTML representation of the text string that, when displayed in a
+ * browser, represents the PHP source code equivalent of $text.
+ */
+ function stringToHtml($text) {
+ return '"' .
+ str_replace(
+ array("\n", ' '),
+ array('\n', '&nbsp;'),
+ check_plain($text)
+ ) . '"';
+ }
+
+ /**
+ * Helper function for testing drupal_html_to_text().
+ *
+ * @param $html
+ * The source HTML string to be converted.
+ * @param $text
+ * The expected result of converting $html to text.
+ * @param $message
+ * A text message to display in the assertion message.
+ * @param $allowed_tags
+ * (optional) An array of allowed tags, or NULL to default to the full
+ * set of tags supported by drupal_html_to_text().
+ */
+ function assertHtmlToText($html, $text, $message, $allowed_tags = NULL) {
+ preg_match_all('/<([a-z0-6]+)/', drupal_strtolower($html), $matches);
+ $tested_tags = implode(', ', array_unique($matches[1]));
+ $message .= ' (' . $tested_tags . ')';
+ $result = drupal_html_to_text($html, $allowed_tags);
+ $pass = $this->assertEqual($result, $text, check_plain($message));
+ $verbose = 'html = <pre>' . $this->stringToHtml($html)
+ . '</pre><br />' . 'result = <pre>' . $this->stringToHtml($result)
+ . '</pre><br />' . 'expected = <pre>' . $this->stringToHtml($text)
+ . '</pre>';
+ $this->verbose($verbose);
+ if (!$pass) {
+ $this->pass("Previous test verbose info:<br />$verbose");
+ }
+ }
+
+ /**
+ * Test all supported tags of drupal_html_to_text().
+ */
+ function testTags() {
+ global $base_path, $base_url;
+ $tests = array(
+ // @todo Trailing linefeeds should be trimmed.
+ '<a href = "http://drupal.org">Drupal.org</a>' => "Drupal.org [1]\n\n[1] http://drupal.org\n",
+ // @todo Footer urls should be absolute.
+ "<a href = \"$base_path\">Homepage</a>" => "Homepage [1]\n\n[1] $base_url/\n",
+ '<address>Drupal</address>' => "Drupal\n",
+ // @todo The <address> tag is currently not supported.
+ '<address>Drupal</address><address>Drupal</address>' => "DrupalDrupal\n",
+ '<b>Drupal</b>' => "*Drupal*\n",
+ // @todo There should be a space between the '>' and the text.
+ '<blockquote>Drupal</blockquote>' => ">Drupal\n",
+ '<blockquote>Drupal</blockquote><blockquote>Drupal</blockquote>' => ">Drupal\n>Drupal\n",
+ '<br />Drupal<br />Drupal<br /><br />Drupal' => "Drupal\nDrupal\nDrupal\n",
+ '<br/>Drupal<br/>Drupal<br/><br/>Drupal' => "Drupal\nDrupal\nDrupal\n",
+ // @todo There should be two line breaks before the paragraph.
+ '<br/>Drupal<br/>Drupal<br/><br/>Drupal<p>Drupal</p>' => "Drupal\nDrupal\nDrupal\nDrupal\n\n",
+ '<div>Drupal</div>' => "Drupal\n",
+ // @todo The <div> tag is currently not supported.
+ '<div>Drupal</div><div>Drupal</div>' => "DrupalDrupal\n",
+ '<em>Drupal</em>' => "/Drupal/\n",
+ '<h1>Drupal</h1>' => "======== DRUPAL ==============================================================\n\n",
+ '<h1>Drupal</h1><p>Drupal</p>' => "======== DRUPAL ==============================================================\n\nDrupal\n\n",
+ '<h2>Drupal</h2>' => "-------- DRUPAL --------------------------------------------------------------\n\n",
+ '<h2>Drupal</h2><p>Drupal</p>' => "-------- DRUPAL --------------------------------------------------------------\n\nDrupal\n\n",
+ '<h3>Drupal</h3>' => ".... Drupal\n\n",
+ '<h3>Drupal</h3><p>Drupal</p>' => ".... Drupal\n\nDrupal\n\n",
+ '<h4>Drupal</h4>' => ".. Drupal\n\n",
+ '<h4>Drupal</h4><p>Drupal</p>' => ".. Drupal\n\nDrupal\n\n",
+ '<h5>Drupal</h5>' => "Drupal\n\n",
+ '<h5>Drupal</h5><p>Drupal</p>' => "Drupal\n\nDrupal\n\n",
+ '<h6>Drupal</h6>' => "Drupal\n\n",
+ '<h6>Drupal</h6><p>Drupal</p>' => "Drupal\n\nDrupal\n\n",
+ '<hr />Drupal<hr />' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\n",
+ '<hr/>Drupal<hr/>' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\n",
+ '<hr/>Drupal<hr/><p>Drupal</p>' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\nDrupal\n\n",
+ '<i>Drupal</i>' => "/Drupal/\n",
+ '<p>Drupal</p>' => "Drupal\n\n",
+ '<p>Drupal</p><p>Drupal</p>' => "Drupal\n\nDrupal\n\n",
+ '<strong>Drupal</strong>' => "*Drupal*\n",
+ // @todo Tables are currently not supported.
+ '<table><tr><td>Drupal</td><td>Drupal</td></tr><tr><td>Drupal</td><td>Drupal</td></tr></table>' => "DrupalDrupalDrupalDrupal\n",
+ '<table><tr><td>Drupal</td></tr></table><p>Drupal</p>' => "Drupal\nDrupal\n\n",
+ // @todo The <u> tag is currently not supported.
+ '<u>Drupal</u>' => "Drupal\n",
+ '<ul><li>Drupal</li></ul>' => " * Drupal\n\n",
+ '<ul><li>Drupal <em>Drupal</em> Drupal</li></ul>' => " * Drupal /Drupal/ Drupal\n\n",
+ // @todo Lines containing nothing but spaces should be trimmed.
+ '<ul><li>Drupal</li><li><ol><li>Drupal</li><li>Drupal</li></ol></li></ul>' => " * Drupal\n * 1) Drupal\n 2) Drupal\n \n\n",
+ '<ul><li>Drupal</li><li><ol><li>Drupal</li></ol></li><li>Drupal</li></ul>' => " * Drupal\n * 1) Drupal\n \n * Drupal\n\n",
+ '<ul><li>Drupal</li><li>Drupal</li></ul>' => " * Drupal\n * Drupal\n\n",
+ '<ul><li>Drupal</li></ul><p>Drupal</p>' => " * Drupal\n\nDrupal\n\n",
+ '<ol><li>Drupal</li></ol>' => " 1) Drupal\n\n",
+ '<ol><li>Drupal</li><li><ul><li>Drupal</li><li>Drupal</li></ul></li></ol>' => " 1) Drupal\n 2) * Drupal\n * Drupal\n \n\n",
+ '<ol><li>Drupal</li><li>Drupal</li></ol>' => " 1) Drupal\n 2) Drupal\n\n",
+ '<ol>Drupal</ol>' => "Drupal\n\n",
+ '<ol><li>Drupal</li></ol><p>Drupal</p>' => " 1) Drupal\n\nDrupal\n\n",
+ '<dl><dt>Drupal</dt></dl>' => "Drupal\n\n",
+ '<dl><dt>Drupal</dt><dd>Drupal</dd></dl>' => "Drupal\n Drupal\n\n",
+ '<dl><dt>Drupal</dt><dd>Drupal</dd><dt>Drupal</dt><dd>Drupal</dd></dl>' => "Drupal\n Drupal\nDrupal\n Drupal\n\n",
+ '<dl><dt>Drupal</dt><dd>Drupal</dd></dl><p>Drupal</p>' => "Drupal\n Drupal\n\nDrupal\n\n",
+ '<dl><dt>Drupal<dd>Drupal</dl>' => "Drupal\n Drupal\n\n",
+ '<dl><dt>Drupal</dt></dl><p>Drupal</p>' => "Drupal\n\nDrupal\n\n",
+ // @todo Again, lines containing only spaces should be trimmed.
+ '<ul><li>Drupal</li><li><dl><dt>Drupal</dt><dd>Drupal</dd><dt>Drupal</dt><dd>Drupal</dd></dl></li><li>Drupal</li></ul>' => " * Drupal\n * Drupal\n Drupal\n Drupal\n Drupal\n \n * Drupal\n\n",
+ // Tests malformed HTML tags.
+ '<br>Drupal<br>Drupal' => "Drupal\nDrupal\n",
+ '<hr>Drupal<hr>Drupal' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\nDrupal\n",
+ '<ol><li>Drupal<li>Drupal</ol>' => " 1) Drupal\n 2) Drupal\n\n",
+ '<ul><li>Drupal <em>Drupal</em> Drupal</ul></ul>' => " * Drupal /Drupal/ Drupal\n\n",
+ '<ul><li>Drupal<li>Drupal</ol>' => " * Drupal\n * Drupal\n\n",
+ '<ul><li>Drupal<li>Drupal</ul>' => " * Drupal\n * Drupal\n\n",
+ '<ul>Drupal</ul>' => "Drupal\n\n",
+ 'Drupal</ul></ol></dl><li>Drupal' => "Drupal\n * Drupal\n",
+ '<dl>Drupal</dl>' => "Drupal\n\n",
+ '<dl>Drupal</dl><p>Drupal</p>' => "Drupal\n\nDrupal\n\n",
+ '<dt>Drupal</dt>' => "Drupal\n",
+ // Tests some unsupported HTML tags.
+ '<html>Drupal</html>' => "Drupal\n",
+ // @todo Perhaps the contents of <script> tags should be dropped.
+ '<script type="text/javascript">Drupal</script>' => "Drupal\n",
+ );
+
+ foreach ($tests as $html => $text) {
+ $this->assertHtmlToText($html, $text, 'Supported tags');
+ }
+ }
+
+ /**
+ * Test $allowed_tags argument of drupal_html_to_text().
+ */
+ function testDrupalHtmlToTextArgs() {
+ // The second parameter of drupal_html_to_text() overrules the allowed tags.
+ $this->assertHtmlToText(
+ 'Drupal <b>Drupal</b> Drupal',
+ "Drupal *Drupal* Drupal\n",
+ 'Allowed <b> tag found',
+ array('b')
+ );
+ $this->assertHtmlToText(
+ 'Drupal <h1>Drupal</h1> Drupal',
+ "Drupal Drupal Drupal\n",
+ 'Disallowed <h1> tag not found',
+ array('b')
+ );
+
+ $this->assertHtmlToText(
+ 'Drupal <p><em><b>Drupal</b></em><p> Drupal',
+ "Drupal Drupal Drupal\n",
+ 'Disallowed <p>, <em>, and <b> tags not found',
+ array('a', 'br', 'h1')
+ );
+
+ $this->assertHtmlToText(
+ '<html><body>Drupal</body></html>',
+ "Drupal\n",
+ 'Unsupported <html> and <body> tags not found',
+ array('html', 'body')
+ );
+ }
+
+ /**
+ * Test that whitespace is collapsed.
+ */
+ function testDrupalHtmltoTextCollapsesWhitespace() {
+ $input = "<p>Drupal Drupal\n\nDrupal<pre>Drupal Drupal\n\nDrupal</pre>Drupal Drupal\n\nDrupal</p>";
+ // @todo The whitespace should be collapsed.
+ $collapsed = "Drupal Drupal\n\nDrupalDrupal Drupal\n\nDrupalDrupal Drupal\n\nDrupal\n\n";
+ $this->assertHtmlToText(
+ $input,
+ $collapsed,
+ 'Whitespace is collapsed',
+ array('p')
+ );
+ }
+
+ /**
+ * Test that text separated by block-level tags in HTML get separated by
+ * (at least) a newline in the plaintext version.
+ */
+ function testDrupalHtmlToTextBlockTagToNewline() {
+ $input = '[text]'
+ . '<blockquote>[blockquote]</blockquote>'
+ . '<br />[br]'
+ . '<dl><dt>[dl-dt]</dt>'
+ . '<dt>[dt]</dt>'
+ . '<dd>[dd]</dd>'
+ . '<dd>[dd-dl]</dd></dl>'
+ . '<h1>[h1]</h1>'
+ . '<h2>[h2]</h2>'
+ . '<h3>[h3]</h3>'
+ . '<h4>[h4]</h4>'
+ . '<h5>[h5]</h5>'
+ . '<h6>[h6]</h6>'
+ . '<hr />[hr]'
+ . '<ol><li>[ol-li]</li>'
+ . '<li>[li]</li>'
+ . '<li>[li-ol]</li></ol>'
+ . '<p>[p]</p>'
+ . '<ul><li>[ul-li]</li>'
+ . '<li>[li-ul]</li></ul>'
+ . '[text]';
+ $output = drupal_html_to_text($input);
+ $pass = $this->assertFalse(
+ preg_match('/\][^\n]*\[/s', $output),
+ 'Block-level HTML tags should force newlines'
+ );
+ if (!$pass) {
+ $this->verbose($this->stringToHtml($output));
+ }
+ $output_upper = drupal_strtoupper($output);
+ $upper_input = drupal_strtoupper($input);
+ $upper_output = drupal_html_to_text($upper_input);
+ $pass = $this->assertEqual(
+ $upper_output,
+ $output_upper,
+ 'Tag recognition should be case-insensitive'
+ );
+ if (!$pass) {
+ $this->verbose(
+ $upper_output
+ . '<br />should be equal to <br />'
+ . $output_upper
+ );
+ }
+ }
+
+ /**
+ * Test that headers are properly separated from surrounding text.
+ */
+ function testHeaderSeparation() {
+ $html = 'Drupal<h1>Drupal</h1>Drupal';
+ // @todo There should be more space above the header than below it.
+ $text = "Drupal\n======== DRUPAL ==============================================================\n\nDrupal\n";
+ $this->assertHtmlToText($html, $text,
+ 'Text before and after <h1> tag');
+ $html = '<p>Drupal</p><h1>Drupal</h1>Drupal';
+ // @todo There should be more space above the header than below it.
+ $text = "Drupal\n\n======== DRUPAL ==============================================================\n\nDrupal\n";
+ $this->assertHtmlToText($html, $text,
+ 'Paragraph before and text after <h1> tag');
+ $html = 'Drupal<h1>Drupal</h1><p>Drupal</p>';
+ // @todo There should be more space above the header than below it.
+ $text = "Drupal\n======== DRUPAL ==============================================================\n\nDrupal\n\n";
+ $this->assertHtmlToText($html, $text,
+ 'Text before and paragraph after <h1> tag');
+ $html = '<p>Drupal</p><h1>Drupal</h1><p>Drupal</p>';
+ $text = "Drupal\n\n======== DRUPAL ==============================================================\n\nDrupal\n\n";
+ $this->assertHtmlToText($html, $text,
+ 'Paragraph before and after <h1> tag');
+ }
+
+ /**
+ * Test that footnote references are properly generated.
+ */
+ function testFootnoteReferences() {
+ global $base_path, $base_url;
+ $source = '<a href="http://www.example.com/node/1">Host and path</a>'
+ . '<br /><a href="http://www.example.com">Host, no path</a>'
+ . '<br /><a href="' . $base_path . 'node/1">Path, no host</a>'
+ . '<br /><a href="node/1">Relative path</a>';
+ // @todo Footnote urls should be absolute.
+ $tt = "Host and path [1]"
+ . "\nHost, no path [2]"
+ // @todo The following two references should be combined.
+ . "\nPath, no host [3]"
+ . "\nRelative path [4]"
+ . "\n"
+ . "\n[1] http://www.example.com/node/1"
+ . "\n[2] http://www.example.com"
+ // @todo The following two references should be combined.
+ . "\n[3] $base_url/node/1"
+ . "\n[4] node/1\n";
+ $this->assertHtmlToText($source, $tt, 'Footnotes');
+ }
+
+ /**
+ * Test that combinations of paragraph breaks, line breaks, linefeeds,
+ * and spaces are properly handled.
+ */
+ function testDrupalHtmlToTextParagraphs() {
+ $tests = array();
+ $tests[] = array(
+ 'html' => "<p>line 1<br />\nline 2<br />line 3\n<br />line 4</p><p>paragraph</p>",
+ // @todo Trailing line breaks should be trimmed.
+ 'text' => "line 1\nline 2\nline 3\nline 4\n\nparagraph\n\n",
+ );
+ $tests[] = array(
+ 'html' => "<p>line 1<br /> line 2</p> <p>line 4<br /> line 5</p> <p>0</p>",
+ // @todo Trailing line breaks should be trimmed.
+ 'text' => "line 1\nline 2\n\nline 4\nline 5\n\n0\n\n",
+ );
+ foreach ($tests as $test) {
+ $this->assertHtmlToText($test['html'], $test['text'], 'Paragraph breaks');
+ }
+ }
+
+ /**
+ * Tests that drupal_html_to_text() wraps before 1000 characters.
+ *
+ * RFC 3676 says, "The Text/Plain media type is the lowest common
+ * denominator of Internet email, with lines of no more than 998 characters."
+ *
+ * RFC 2046 says, "SMTP [RFC-821] allows a maximum of 998 octets before the
+ * next CRLF sequence."
+ *
+ * RFC 821 says, "The maximum total length of a text line including the
+ * <CRLF> is 1000 characters."
+ */
+ function testVeryLongLineWrap() {
+ $input = 'Drupal<br /><p>' . str_repeat('x', 2100) . '</><br />Drupal';
+ $output = drupal_html_to_text($input);
+ // This awkward construct comes from includes/mail.inc lines 8-13.
+ $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
+ // We must use strlen() rather than drupal_strlen() in order to count
+ // octets rather than characters.
+ $line_length_limit = 1000 - drupal_strlen($eol);
+ $maximum_line_length = 0;
+ foreach (explode($eol, $output) as $line) {
+ // We must use strlen() rather than drupal_strlen() in order to count
+ // octets rather than characters.
+ $maximum_line_length = max($maximum_line_length, strlen($line . $eol));
+ }
+ $verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.';
+ // @todo This should assert that $maximum_line_length <= 1000.
+ $this->pass($verbose);
+ }
+}
diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test
index 5642fcee0..c0a79d4c2 100644
--- a/modules/simpletest/tests/menu.test
+++ b/modules/simpletest/tests/menu.test
@@ -395,6 +395,14 @@ class MenuRouterTestCase extends DrupalWebTestCase {
}
/**
+ * Test menu_get_item() with empty ancestors.
+ */
+ function testMenuGetItemNoAncestors() {
+ variable_set('menu_masks', array());
+ $this->drupalGet('');
+ }
+
+ /**
* Test menu_set_item().
*/
function testMenuSetItem() {
diff --git a/modules/simpletest/tests/upgrade/drupal-6.menu.database.php b/modules/simpletest/tests/upgrade/drupal-6.menu.database.php
index d10c4eec4..f5c588af7 100644
--- a/modules/simpletest/tests/upgrade/drupal-6.menu.database.php
+++ b/modules/simpletest/tests/upgrade/drupal-6.menu.database.php
@@ -7,4 +7,123 @@ db_insert('variable')->fields(array(
'name' => 'menu_default_node_menu',
'value' => 's:15:"secondary-links";',
))
+->values(array(
+ 'name' => 'menu_primary_links_source',
+ 'value' => 's:15:"secondary-links";',
+))
+->values(array(
+ 'name' => 'menu_secondary_links_source',
+ 'value' => 's:13:"primary-links";',
+))
+->execute();
+
+// Add some links to the menus.
+db_insert('menu_links')->fields(array(
+ 'menu_name',
+ 'mlid',
+ 'plid',
+ 'link_path',
+ 'router_path',
+ 'link_title',
+ 'options',
+ 'module',
+ 'hidden',
+ 'external',
+ 'has_children',
+ 'expanded',
+ 'weight',
+ 'depth',
+ 'customized',
+ 'p1',
+ 'p2',
+ 'p3',
+ 'p4',
+ 'p5',
+ 'p6',
+ 'p7',
+ 'p8',
+ 'p9',
+ 'updated',
+))
+->values(array(
+ 'menu_name' => 'navigation',
+ 'mlid' => '201',
+ 'plid' => '0',
+ 'link_path' => 'node/add',
+ 'router_path' => 'node/add',
+ 'link_title' => 'nodeadd-navigation',
+ 'options' => 'a:0:{}',
+ 'module' => 'menu',
+ 'hidden' => '0',
+ 'external' => '0',
+ 'has_children' => '1',
+ 'expanded' => '0',
+ 'weight' => '1',
+ 'depth' => '1',
+ 'customized' => '0',
+ 'p1' => '201',
+ 'p2' => '0',
+ 'p3' => '0',
+ 'p4' => '0',
+ 'p5' => '0',
+ 'p6' => '0',
+ 'p7' => '0',
+ 'p8' => '0',
+ 'p9' => '0',
+ 'updated' => '0',
+))
+->values(array(
+ 'menu_name' => 'primary-links',
+ 'mlid' => '204',
+ 'plid' => '0',
+ 'link_path' => 'node/add',
+ 'router_path' => 'node/add',
+ 'link_title' => 'nodeadd-primary',
+ 'options' => 'a:0:{}',
+ 'module' => 'menu',
+ 'hidden' => '0',
+ 'external' => '0',
+ 'has_children' => '1',
+ 'expanded' => '0',
+ 'weight' => '1',
+ 'depth' => '1',
+ 'customized' => '0',
+ 'p1' => '204',
+ 'p2' => '0',
+ 'p3' => '0',
+ 'p4' => '0',
+ 'p5' => '0',
+ 'p6' => '0',
+ 'p7' => '0',
+ 'p8' => '0',
+ 'p9' => '0',
+ 'updated' => '0',
+))
+->values(array(
+ 'menu_name' => 'secondary-links',
+ 'mlid' => '205',
+ 'plid' => '0',
+ 'link_path' => 'node/add',
+ 'router_path' => 'node/add',
+ 'link_title' => 'nodeadd-secondary',
+ 'options' => 'a:0:{}',
+ 'module' => 'menu',
+ 'hidden' => '0',
+ 'external' => '0',
+ 'has_children' => '1',
+ 'expanded' => '0',
+ 'weight' => '1',
+ 'depth' => '1',
+ 'customized' => '0',
+ 'p1' => '205',
+ 'p2' => '0',
+ 'p3' => '0',
+ 'p4' => '0',
+ 'p5' => '0',
+ 'p6' => '0',
+ 'p7' => '0',
+ 'p8' => '0',
+ 'p9' => '0',
+ 'updated' => '0',
+))
->execute();
diff --git a/modules/simpletest/tests/upgrade/upgrade.menu.test b/modules/simpletest/tests/upgrade/upgrade.menu.test
index beb20277a..5a17a1947 100644
--- a/modules/simpletest/tests/upgrade/upgrade.menu.test
+++ b/modules/simpletest/tests/upgrade/upgrade.menu.test
@@ -29,16 +29,50 @@ class MenuUpgradePathTestCase extends UpgradePathTestCase {
public function testMenuUpgrade() {
$this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
- // Test the migration of "Default menu for content" setting to individual node types.
- $this->drupalGet("admin/structure/types/manage/page/edit");
+ // Test the migration of "Default menu for content" setting to individual
+ // node types.
+ $this->drupalGet('admin/structure/types/manage/page/edit');
$this->assertNoFieldChecked('edit-menu-options-management', 'Management menu is not selected as available menu');
$this->assertNoFieldChecked('edit-menu-options-navigation', 'Navigation menu is not selected as available menu');
- $this->assertNoFieldChecked('edit-menu-options-primary-links', 'Primary Links menu is not selected as available menu');
- $this->assertFieldChecked('edit-menu-options-secondary-links', 'Secondary Links menu is selected as available menu');
+ $this->assertNoFieldChecked('edit-menu-options-main-menu', 'Main menu is not selected as available menu');
+ $this->assertFieldChecked('edit-menu-options-secondary-menu', 'Secondary menu is selected as available menu');
$this->assertNoFieldChecked('edit-menu-options-user-menu', 'User menu is not selected as available menu');
- $this->assertOptionSelected('edit-menu-parent', 'secondary-links:0', 'Secondary links is selected as default parent item');
+ $this->assertOptionSelected('edit-menu-parent', 'secondary-menu:0', 'Secondary menu is selected as default parent item');
$this->assertEqual(variable_get('menu_default_node_menu'), NULL, 'Redundant variable menu_default_node_menu has been removed');
+ // Verify Primary/Secondary Links have been renamed.
+ $this->drupalGet('admin/structure/menu');
+ $this->assertNoLinkByHref('admin/structure/menu/manage/primary-links');
+ $this->assertLinkByHref('admin/structure/menu/manage/main-menu');
+ $this->assertNoLinkByHref('admin/structure/menu/manage/secondary-links');
+ $this->assertLinkByHref('admin/structure/menu/manage/secondary-menu');
+
+ // Verify the existence of all system-defined (default) menus.
+ foreach (menu_list_system_menus() as $menu_name => $title) {
+ $this->assertLinkByHref('admin/structure/menu/manage/' . $menu_name, 0, 'Found default menu: ' . $title);
+ }
+
+ // Verify a few known links are still present, plus the ones created here.
+ $test_menus = array(
+ 'navigation' => array('Add content', 'nodeadd-navigation'),
+ 'management' => array('Administration', 'Account settings'),
+ 'user-menu' => array('My account', 'Log out'),
+ 'main-menu' => array('nodeadd-primary'),
+ 'secondary-menu' => array('nodeadd-secondary'),
+ );
+
+ foreach ($test_menus as $menu_name => $links) {
+ $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
+ $this->assertResponse(200, 'Access menu management for ' . $menu_name);
+ foreach ($links as $link_text) {
+ $this->assertLink(t($link_text));
+ }
+ }
+
+ // Check the "source for primary/secondary links" setting.
+ $this->drupalGet('admin/structure/menu/settings');
+ $this->assertOptionSelected('edit-menu-main-links-source', 'secondary-menu');
+ $this->assertOptionSelected('edit-menu-secondary-links-source', 'main-menu');
}
}
diff --git a/modules/simpletest/tests/upgrade/upgrade.node.test b/modules/simpletest/tests/upgrade/upgrade.node.test
index 163dbef5e..cd44790c7 100644
--- a/modules/simpletest/tests/upgrade/upgrade.node.test
+++ b/modules/simpletest/tests/upgrade/upgrade.node.test
@@ -27,6 +27,11 @@ class NodeBodyUpgradePathTestCase extends UpgradePathTestCase {
*/
public function testNodeBodyUpgrade() {
$this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
+
+ $instance = field_info_instance('node', 'body', 'story');
+ $this->assertIdentical($instance['required'], 0, 'The required setting was preserved during the upgrade path.');
+ $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path');
+
$this->drupalGet("content/1263769200");
$this->assertText('node body (broken) - 37');
diff --git a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
index dadb98e5a..37de08775 100644
--- a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
+++ b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
@@ -68,6 +68,12 @@ class UpgradePathTaxonomyTestCase extends UpgradePathTestCase {
sort($inst_keys);
$this->assertEqual($voc_keys, $inst_keys, t('Node type page has instances for every vocabulary.'));
+ // Ensure instance variables are getting through.
+ foreach ($instances as $instance) {
+ $this->assertTrue(isset($instance['required']), 'The required setting was preserved during the upgrade path.');
+ $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path');
+ }
+
// Node type 'story' was not explicitly in $vocabulary->nodes but
// each node of type 'story' was associated to one or more terms.
// Check that the node type 'story' has been associated only to
diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc
index a3f76d4a6..39f86dc30 100644
--- a/modules/system/image.gd.inc
+++ b/modules/system/image.gd.inc
@@ -346,7 +346,7 @@ function image_gd_create_tmp(stdClass $image, $width, $height) {
*/
function image_gd_get_info(stdClass $image) {
$details = FALSE;
- $data = getimagesize(drupal_realpath($image->source));
+ $data = getimagesize($image->source);
if (isset($data) && is_array($data)) {
$extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 9e7d69dd3..0d3a8d7a8 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1757,7 +1757,7 @@ function system_file_system_settings() {
'#title' => t('Private file system path'),
'#default_value' => variable_get('file_private_path', ''),
'#maxlength' => 255,
- '#description' => t('A local file system path where private files will be stored. This directory must exist and be writable by Drupal. This directory should not be accessible over the web.'),
+ '#description' => t('An existing local file system path for storing private files. It should be writable by Drupal and not accessible over the web. See the online handbook for <a href="@handbook">more information about securing private files</a>.', array('@handbook' => 'http://drupal.org/handbook/modules/file')),
'#after_build' => array('system_check_directory'),
);
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 400538900..179f12311 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -1972,8 +1972,8 @@ function hook_permission() {
* containing information about the hook. Each array may contain the
* following elements:
* - variables: (required if "render element" not present) An array of
- * variables that this theme hook uses. This value allows the theme layer to
- * properly utilize templates. Each array key represents the name of the
+ * variables that this theme hook uses. This value allows the theme layer
+ * to properly utilize templates. Each array key represents the name of the
* variable and the value will be used as the default value if it is not
* given when theme() is called. Template implementations receive these
* arguments as variables in the template file. Function implementations
@@ -1989,20 +1989,20 @@ function hook_permission() {
* preprocess function (as needed) is actually loaded; this makes it
* possible to split theme functions out into separate files quite easily.
* - path: Override the path of the file to be used. Ordinarily the module or
- * theme path will be used, but if the file will not be in the default path,
- * include it here. This path should be relative to the Drupal root
+ * theme path will be used, but if the file will not be in the default
+ * path, include it here. This path should be relative to the Drupal root
* directory.
- * - template: If specified, this theme implementation is a template, and this
- * is the template file without an extension. Do not put .tpl.php on this
- * file; that extension will be added automatically by the default rendering
- * engine (which is PHPTemplate). If 'path', above, is specified, the
- * template should also be in this path.
- * - function: If specified, this will be the function name to invoke for this
- * implementation. If neither file nor function is specified, a default
- * function name will be assumed. For example, if a module registers
- * the 'node' theme hook, 'theme_node' will be assigned to its function.
- * If the chameleon theme registers the node hook, it will be assigned
- * 'chameleon_node' as its function.
+ * - template: If specified, this theme implementation is a template, and
+ * this is the template file without an extension. Do not put .tpl.php on
+ * this file; that extension will be added automatically by the default
+ * rendering engine (which is PHPTemplate). If 'path', above, is specified,
+ * the template should also be in this path.
+ * - function: If specified, this will be the function name to invoke for
+ * this implementation. If neither 'template' nor 'function' is specified,
+ * a default function name will be assumed. For example, if a module
+ * registers the 'node' theme hook, 'theme_node' will be assigned to its
+ * function. If the chameleon theme registers the node hook, it will be
+ * assigned 'chameleon_node' as its function.
* - pattern: A regular expression pattern to be used to allow this theme
* implementation to have a dynamic name. The convention is to use __ to
* differentiate the dynamic portion of the theme. For example, to allow
@@ -2017,11 +2017,11 @@ function hook_permission() {
* a theme this will be filled in as phptemplate_preprocess and
* phptemplate_preprocess_HOOK as well as themename_preprocess and
* themename_preprocess_HOOK.
- * - override preprocess functions: Set to TRUE when a theme does NOT want the
- * standard preprocess functions to run. This can be used to give a theme
- * FULL control over how variables are set. For example, if a theme wants
- * total control over how certain variables in the page.tpl.php are set,
- * this can be set to true. Please keep in mind that when this is used
+ * - override preprocess functions: Set to TRUE when a theme does NOT want
+ * the standard preprocess functions to run. This can be used to give a
+ * theme FULL control over how variables are set. For example, if a theme
+ * wants total control over how certain variables in the page.tpl.php are
+ * set, this can be set to true. Please keep in mind that when this is used
* by a theme, that theme becomes responsible for making sure necessary
* variables are set.
* - type: (automatically derived) Where the theme hook is defined:
@@ -2687,22 +2687,21 @@ function hook_file_delete($file) {
* NULL.
*
* @see file_download()
- * @see upload_file_download()
*/
function hook_file_download($uri) {
// Check if the file is controlled by the current module.
if (!file_prepare_directory($uri)) {
$uri = FALSE;
}
- $result = db_query("SELECT f.* FROM {file_managed} f INNER JOIN {upload} u ON f.fid = u.fid WHERE uri = :uri", array('uri' => $uri));
- foreach ($result as $file) {
- if (!user_access('view uploaded files')) {
+ if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
+ if (!user_access('access user profiles')) {
+ // Access to the file is denied.
return -1;
}
- return array(
- 'Content-Type' => $file->filemime,
- 'Content-Length' => $file->filesize,
- );
+ else {
+ $info = image_get_info($uri);
+ return array('Content-Type' => $info['mime_type']);
+ }
}
}
diff --git a/modules/system/system.install b/modules/system/system.install
index e06744460..e55c7cf3a 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -2263,6 +2263,9 @@ function system_update_7042() {
// provided any meaningful unique constraint ('pid' is a primary key).
db_add_index('url_alias', 'source_language_pid', array('source', 'language', 'pid'));
db_add_index('url_alias', 'alias_language_pid', array('alias', 'language', 'pid'));
+
+ // Now that the URL aliases are correct, we can rebuild the whitelist.
+ drupal_path_alias_whitelist_rebuild();
}
/**
diff --git a/modules/system/system.module b/modules/system/system.module
index c3b4a1e39..8fc0ffa43 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -3018,6 +3018,7 @@ function system_cron() {
->fields(array(
'expire' => 0,
))
+ ->condition('expire', 0, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
diff --git a/modules/system/system.test b/modules/system/system.test
index 0fe0bc057..8a29ce531 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -1824,7 +1824,6 @@ class TokenReplaceTestCase extends DrupalWebTestCase {
// Set a few site variables.
variable_set('site_name', '<strong>Drupal<strong>');
variable_set('site_slogan', '<blink>Slogan</blink>');
- variable_set('site_mission', '<em>Mission</em>');
// Generate and test sanitized tokens.
$tests = array();
diff --git a/modules/system/system.tokens.inc b/modules/system/system.tokens.inc
index 27d7dfdb0..56ddf2988 100644
--- a/modules/system/system.tokens.inc
+++ b/modules/system/system.tokens.inc
@@ -88,10 +88,6 @@ function system_token_info() {
'name' => t("File name"),
'description' => t("The name of the file on disk."),
);
- $file['description'] = array(
- 'name' => t("Description"),
- 'description' => t("An optional human-readable description of the file."),
- );
$file['path'] = array(
'name' => t("Path"),
'description' => t("The location of the file relative to Drupal root."),
@@ -228,10 +224,6 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
$replacements[$original] = $sanitize ? check_plain($file->filename) : $file->filename;
break;
- case 'description':
- $replacements[$original] = $sanitize ? check_plain($file->description) : $file->description;
- break;
-
case 'path':
$replacements[$original] = $sanitize ? check_plain($file->uri) : $file->uri;
break;
@@ -255,7 +247,8 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
case 'owner':
$account = user_load($file->uid);
- $replacements[$original] = $sanitize ? check_plain($account->name) : $account->name;
+ $name = format_username($account);
+ $replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
}
}
diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc
index d83f5d367..a236cfed1 100644
--- a/modules/taxonomy/taxonomy.admin.inc
+++ b/modules/taxonomy/taxonomy.admin.inc
@@ -209,7 +209,7 @@ function taxonomy_form_vocabulary_validate($form, &$form_state) {
* @see taxonomy_form_vocabulary_validate()
*/
function taxonomy_form_vocabulary_submit($form, &$form_state) {
- if ($form_state['clicked_button']['#value'] == t('Delete')) {
+ if ($form_state['triggering_element']['#value'] == t('Delete')) {
// Rebuild the form to confirm vocabulary deletion.
$form_state['rebuild'] = TRUE;
$form_state['confirm_delete'] = TRUE;
@@ -434,7 +434,7 @@ function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
* @see taxonomy_overview_terms()
*/
function taxonomy_overview_terms_submit($form, &$form_state) {
- if ($form_state['clicked_button']['#value'] == t('Reset to alphabetical')) {
+ if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) {
// Execute the reset action.
if ($form_state['values']['reset_alphabetical'] === TRUE) {
return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
@@ -674,9 +674,6 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary =
if (isset($form_state['confirm_delete'])) {
return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $term->tid));
}
- elseif (isset($form_state['confirm_parents'])) {
- return array_merge($form, taxonomy_term_confirm_parents($form, $form_state, $vocabulary));
- }
$form['name'] = array(
'#type' => 'textfield',
@@ -801,7 +798,7 @@ function taxonomy_form_term_validate($form, &$form_state) {
* @see taxonomy_form_term()
*/
function taxonomy_form_term_submit($form, &$form_state) {
- if ($form_state['clicked_button']['#value'] == t('Delete')) {
+ if ($form_state['triggering_element']['#value'] == t('Delete')) {
// Execute the term deletion.
if ($form_state['values']['delete'] === TRUE) {
return taxonomy_term_confirm_delete_submit($form, $form_state);
@@ -811,12 +808,6 @@ function taxonomy_form_term_submit($form, &$form_state) {
$form_state['confirm_delete'] = TRUE;
return;
}
- // Rebuild the form to confirm enabling multiple parents.
- elseif ($form_state['clicked_button']['#value'] == t('Save') && count($form_state['values']['parent']) > 1 && $form['#vocabulary']->hierarchy < 2) {
- $form_state['rebuild'] = TRUE;
- $form_state['confirm_parents'] = TRUE;
- return;
- }
$term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state);
@@ -873,25 +864,6 @@ function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {
}
/**
- * Form builder for the confirmation of multiple term parents.
- *
- * @ingroup forms
- * @see taxonomy_form_term()
- */
-function taxonomy_term_confirm_parents($form, &$form_state, $vocabulary) {
- foreach (element_children($form_state['values']) as $key) {
- $form[$key] = array(
- '#type' => 'value',
- '#value' => $form_state['values'][$key],
- );
- }
- $question = t('Set multiple term parents?');
- $description = '<p>' . t("Adding multiple parents to a term will cause the %vocabulary vocabulary to look for multiple parents on every term. Because multiple parents are not supported when using the drag and drop outline interface, drag and drop will be disabled if you enable this option. If you choose to have multiple parents, you will only be able to set parents by using the term edit form.", array('%vocabulary' => $vocabulary->name)) . '</p>';
- $description .= '<p>' . t("You may re-enable the drag and drop interface at any time by reducing multiple parents to a single parent for the terms in this vocabulary.") . '</p>';
- return confirm_form($form, $question, drupal_get_destination(), $description, t('Set multiple parents'));
-}
-
-/**
* Form builder for the term delete form.
*
* @ingroup forms
diff --git a/modules/taxonomy/taxonomy.install b/modules/taxonomy/taxonomy.install
index 56b7e01c6..68fbc7804 100644
--- a/modules/taxonomy/taxonomy.install
+++ b/modules/taxonomy/taxonomy.install
@@ -438,6 +438,7 @@ function taxonomy_update_7004() {
'entity_type' => 'node',
'settings' => array(),
'description' => $vocabulary->help,
+ 'required' => $vocabulary->required,
'widget' => array(),
'display' => array(
'default' => array(
diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc
index 3aed29011..0cca252d7 100644
--- a/modules/taxonomy/taxonomy.pages.inc
+++ b/modules/taxonomy/taxonomy.pages.inc
@@ -110,7 +110,7 @@ function taxonomy_autocomplete($field_name, $tags_typed = '') {
->execute()
->fetchAllKeyed();
- $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
+ $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
$term_matches = array();
foreach ($tags_return as $tid => $name) {
@@ -119,9 +119,7 @@ function taxonomy_autocomplete($field_name, $tags_typed = '') {
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $name) . '"';
}
- else {
- $term_matches[$prefix . $n] = check_plain($name);
- }
+ $term_matches[$prefix . $n] = check_plain($name);
}
}
diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test
index 97cfe448f..aa7cc2e44 100644
--- a/modules/taxonomy/taxonomy.test
+++ b/modules/taxonomy/taxonomy.test
@@ -580,7 +580,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
field_create_instance($instance);
$terms = array(
$this->randomName(),
- $this->randomName(),
+ $this->randomName() . ', ' . $this->randomName(),
$this->randomName(),
);
@@ -590,7 +590,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
$edit["body[$langcode][0][value]"] = $this->randomName();
// Insert the terms in a comma separated list. Vocabulary 1 is a
// free-tagging field created by the default profile.
- $edit[$instance['field_name'] . "[$langcode]"] = implode(', ', $terms);
+ $edit[$instance['field_name'] . "[$langcode]"] = drupal_implode_tags($terms);
// Preview and verify the terms appear but are not created.
$this->drupalPost('node/add/page', $edit, t('Preview'));
@@ -611,9 +611,9 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
// Get the created terms.
- list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid);
+ list($term1, $term2, $term3) = array_values(taxonomy_term_load_multiple(FALSE));
- // Delete one term.
+ // Delete term 1.
$this->drupalPost('taxonomy/term/' . $term1->tid . '/edit', array(), t('Delete'));
$this->drupalPost(NULL, NULL, t('Delete'));
$term_names = array($term2->name, $term3->name);
@@ -627,10 +627,17 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
$this->assertNoText($term1->name, t('The deleted term %name does not appear on the node page.', array('%name' => $term1->name)));
- // Test autocomplete on term 2.
+ // Test autocomplete on term 2 - it contains a comma, so expect the key to
+ // be quoted.
$input = substr($term2->name, 0, 3);
$this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
- $this->assertRaw('{"' . $term2->name . '":"' . $term2->name . '"}', t('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term2->name)));
+ $this->assertRaw('{"\"' . $term2->name . '\"":"' . $term2->name . '"}', t('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term2->name)));
+
+ // Test autocomplete on term 3 - it is alphanumeric only, so no extra
+ // quoting.
+ $input = substr($term3->name, 0, 3);
+ $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
+ $this->assertRaw('{"' . $term3->name . '":"' . $term3->name . '"}', t('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term3->name)));
}
/**
@@ -761,6 +768,35 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
/**
+ * Test saving a term with multiple parents through the UI.
+ */
+ function testTermMultipleParentsInterface() {
+ // Add a new term to the vocabulary so that we can have multiple parents.
+ $parent = $this->createTerm($this->vocabulary);
+
+ // Add a new term with multiple parents.
+ $edit = array(
+ 'name' => $this->randomName(12),
+ 'description[value]' => $this->randomName(100),
+ 'parent[]' => array(0, $parent->tid),
+ );
+ // Save the new term.
+ $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
+
+ // Check that the term was successfully created.
+ $terms = taxonomy_get_term_by_name($edit['name']);
+ $term = reset($terms);
+ $this->assertNotNull($term, t('Term found in database'));
+ $this->assertEqual($edit['name'], $term->name, t('Term name was successfully saved.'));
+ $this->assertEqual($edit['description[value]'], $term->description, t('Term description was successfully saved.'));
+ // Check that the parent tid is still there. The other parent (<root>) is
+ // not added by taxonomy_get_parents().
+ $parents = taxonomy_get_parents($term->tid);
+ $parent = reset($parents);
+ $this->assertEqual($edit['parent[]'][1], $parent->tid, t('Term parents were successfully saved.'));
+ }
+
+ /**
* Test taxonomy_get_term_by_name().
*/
function testTaxonomyGetTermByName() {
diff --git a/modules/translation/translation.test b/modules/translation/translation.test
index 54b53d9fd..fe320a935 100644
--- a/modules/translation/translation.test
+++ b/modules/translation/translation.test
@@ -108,9 +108,9 @@ class TranslationTestCase extends DrupalWebTestCase {
// Update original and mark translation as outdated.
$node_body = $this->randomName();
- $node->body[$node->language][0]['value'] = $node_body;
+ $node->body[LANGUAGE_NONE][0]['value'] = $node_body;
$edit = array();
- $edit["body[$node->language][0][value]"] = $node_body;
+ $edit["body[$langcode][0][value]"] = $node_body;
$edit['translation[retranslate]'] = TRUE;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_title)), t('Original node updated.'));
@@ -121,7 +121,7 @@ class TranslationTestCase extends DrupalWebTestCase {
// Update translation and mark as updated.
$edit = array();
- $edit["body[$node_translation->language][0][value]"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
$edit['translation[status]'] = FALSE;
$this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.'));
@@ -131,7 +131,7 @@ class TranslationTestCase extends DrupalWebTestCase {
$this->drupalGet('node/add/page');
$this->assertFieldByXPath('//select[@name="language"]//option', 'it', t('Italian (disabled) is available in language selection.'));
$translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it');
- $this->assertRaw($translation_it->body['it'][0]['value'], t('Content created in Italian (disabled).'));
+ $this->assertRaw($translation_it->body[LANGUAGE_NONE][0]['value'], t('Content created in Italian (disabled).'));
// Confirm that language neutral is an option for translators when there are
// disabled languages.
@@ -349,9 +349,10 @@ class TranslationTestCase extends DrupalWebTestCase {
function createTranslation($node, $title, $body, $language) {
$this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => $language)));
- $body_key = "body[$language][0][value]";
+ $langcode = LANGUAGE_NONE;
+ $body_key = "body[$langcode][0][value]";
$this->assertFieldByXPath('//input[@id="edit-title"]', $node->title, "Original title value correctly populated.");
- $this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[$node->language][0]['value'], "Original body value correctly populated.");
+ $this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[LANGUAGE_NONE][0]['value'], "Original body value correctly populated.");
$edit = array();
$edit["title"] = $title;
diff --git a/modules/user/user-rtl.css b/modules/user/user-rtl.css
index 5a1442c1c..642c94347 100644
--- a/modules/user/user-rtl.css
+++ b/modules/user/user-rtl.css
@@ -4,6 +4,12 @@
padding-right: 1.5em;
}
+#user-admin-roles .form-item-name {
+ float: right;
+ margin-left: 1em;
+ margin-right: 0;
+}
+
/**
* Password strength indicator.
*/
diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc
index 0596bde4c..0044b18a4 100644
--- a/modules/user/user.admin.inc
+++ b/modules/user/user.admin.inc
@@ -712,7 +712,12 @@ function user_admin_permissions($form, $form_state, $rid = NULL) {
// Have to build checkboxes here after checkbox arrays are built
foreach ($role_names as $rid => $name) {
- $form['checkboxes'][$rid] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => isset($status[$rid]) ? $status[$rid] : array());
+ $form['checkboxes'][$rid] = array(
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => isset($status[$rid]) ? $status[$rid] : array(),
+ '#attributes' => array('class' => array('rid-' . $rid)),
+ );
$form['role_names'][$rid] = array('#markup' => check_plain($name), '#tree' => TRUE);
}
diff --git a/modules/user/user.css b/modules/user/user.css
index d6ed7c366..079ec38ab 100644
--- a/modules/user/user.css
+++ b/modules/user/user.css
@@ -22,8 +22,8 @@
clear: both;
}
#user-admin-roles .form-item-name {
- float: left;
- margin-right: 1em;
+ float: left; /* LTR */
+ margin-right: 1em; /* LTR */
}
/**
diff --git a/modules/user/user.module b/modules/user/user.module
index 84430b2f7..044ad4698 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -74,10 +74,26 @@ function user_help($path, $arg) {
}
/**
- * Invokes hook_user() in every module.
+ * Invokes a user hook in every module.
*
* We cannot use module_invoke() for this, because the arguments need to
* be passed by reference.
+ *
+ * @param $type
+ * A text string that controls which user hook to invoke. Valid choices are:
+ * - cancel: Invokes hook_user_cancel().
+ * - insert: Invokes hook_user_insert().
+ * - login: Invokes hook_user_login().
+ * - presave: Invokes hook_user_presave().
+ * - update: Invokes hook_user_update().
+ * @param $edit
+ * An associative array variable containing form values to be passed
+ * as the first parameter of the hook function.
+ * @param $account
+ * The user account object to be passed as the second parameter of the hook
+ * function.
+ * @param $category
+ * The category of user information being acted upon.
*/
function user_module_invoke($type, &$edit, $account, $category = NULL) {
foreach (module_implements('user_' . $type) as $module) {
@@ -2287,6 +2303,27 @@ function user_cancel_url($account) {
return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
}
+/**
+ * Creates a unique hash value for use in time-dependent per-user URLs.
+ *
+ * This hash is normally used to build a unique and secure URL that is sent to
+ * the user by email for purposes such as resetting the user's password. In
+ * order to validate the URL, the same hash can be generated again, from the
+ * same information, and compared to the hash value from the URL. The URL
+ * normally contains both the time stamp and the numeric user ID. The login
+ * name and hashed password are retrieved from the database as necessary. For a
+ * usage example, see user_cancel_url() and user_cancel_confirm().
+ *
+ * @param $password
+ * The hashed user account password value.
+ * @param $timestamp
+ * A unix timestamp.
+ * @param $login
+ * The user account login name.
+ *
+ * @return
+ * A string that is safe for use in URLs and SQL statements.
+ */
function user_pass_rehash($password, $timestamp, $login) {
return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password);
}
diff --git a/modules/user/user.permissions.js b/modules/user/user.permissions.js
index 4ef576cd4..988820e12 100644
--- a/modules/user/user.permissions.js
+++ b/modules/user/user.permissions.js
@@ -5,34 +5,64 @@
*/
Drupal.behaviors.permissions = {
attach: function (context) {
- $('table#permissions:not(.permissions-processed)').each(function () {
+ var self = this;
+ $('table#permissions').once('permissions', function () {
+ // On a site with many roles and permissions, this behavior initially has
+ // to perform thousands of DOM manipulations to inject checkboxes and hide
+ // them. By detaching the table from the DOM, all operations can be
+ // performed without triggering internal layout and re-rendering processes
+ // in the browser.
+ var $table = $(this);
+ if ($table.prev().length) {
+ var $ancestor = $table.prev(), method = 'after';
+ }
+ else {
+ var $ancestor = $table.parent(), method = 'append';
+ }
+ $table.detach();
+
// Create dummy checkboxes. We use dummy checkboxes instead of reusing
// the existing checkboxes here because new checkboxes don't alter the
// submitted form. If we'd automatically check existing checkboxes, the
// permission table would be polluted with redundant entries. This
// is deliberate, but desirable when we automatically check them.
- $(':checkbox', this).not('[name^="2["]').not('[name^="1["]').each(function () {
- $(this).addClass('real-checkbox');
- $('<input type="checkbox" class="dummy-checkbox" disabled="disabled" checked="checked" />')
- .attr('title', Drupal.t("This permission is inherited from the authenticated user role."))
- .insertAfter(this)
- .hide();
- });
+ var $dummy = $('<input type="checkbox" class="dummy-checkbox" disabled="disabled" checked="checked" />')
+ .attr('title', Drupal.t("This permission is inherited from the authenticated user role."))
+ .hide();
- // Helper function toggles all dummy checkboxes based on the checkboxes'
- // state. If the "authenticated user" checkbox is checked, the checked
- // and disabled checkboxes are shown, the real checkboxes otherwise.
- var toggle = function () {
- $(this).closest('tr')
- .find('.real-checkbox')[this.checked ? 'hide' : 'show']().end()
- .find('.dummy-checkbox')[this.checked ? 'show' : 'hide']();
- };
+ $('input[type=checkbox]', this).not('.rid-2, .rid-1').addClass('real-checkbox').each(function () {
+ $dummy.clone().insertAfter(this);
+ });
// Initialize the authenticated user checkbox.
- $(':checkbox[name^="2["]', this)
- .click(toggle)
- .each(function () { toggle.call(this); });
- }).addClass('permissions-processed');
+ $('input[type=checkbox].rid-2', this)
+ .bind('click.permissions', self.toggle)
+ // .triggerHandler() cannot be used here, as it only affects the first
+ // element.
+ .each(self.toggle);
+
+ // Re-insert the table into the DOM.
+ $ancestor[method]($table);
+ });
+ },
+
+ /**
+ * Toggles all dummy checkboxes based on the checkboxes' state.
+ *
+ * If the "authenticated user" checkbox is checked, the checked and disabled
+ * checkboxes are shown, the real checkboxes otherwise.
+ */
+ toggle: function () {
+ var authCheckbox = this, $row = $(this).closest('tr');
+ // jQuery performs too many layout calculations for .hide() and .show(),
+ // leading to a major page rendering lag on sites with many roles and
+ // permissions. Therefore, we toggle visibility directly.
+ $row.find('.real-checkbox').each(function () {
+ this.style.display = (authCheckbox.checked ? 'none' : '');
+ });
+ $row.find('.dummy-checkbox').each(function () {
+ this.style.display = (authCheckbox.checked ? '' : 'none');
+ });
}
};
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index 70829064d..5d447177f 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -327,7 +327,6 @@ function standard_install() {
'field_name' => 'field_image',
'type' => 'image',
'cardinality' => 1,
- 'translatable' => TRUE,
'locked' => FALSE,
'indexes' => array('fid' => array('fid')),
'settings' => array(
diff --git a/scripts/generate-d6-content.sh b/scripts/generate-d6-content.sh
index bba61caee..5079c2338 100644
--- a/scripts/generate-d6-content.sh
+++ b/scripts/generate-d6-content.sh
@@ -100,7 +100,7 @@ module_load_include('inc', 'node', 'node.pages');
for ($i = 0; $i < 24; $i++) {
$uid = intval($i / 8) + 3;
$user = user_load($uid);
- $node = new stdClass;
+ $node = new stdClass();
$node->uid = $uid;
$node->type = $i < 12 ? 'page' : 'story';
$node->sticky = 0;
@@ -148,7 +148,7 @@ for ($i = 0; $i < 24; $i++) {
for ($i = 0; $i < 12; $i++) {
$uid = intval($i / 4) + 3;
$user = user_load($uid);
- $node = new stdClass;
+ $node = new stdClass();
$node->uid = $uid;
$node->type = 'poll';
$node->sticky = 0;
@@ -187,7 +187,7 @@ for ($i = 0; $i < 12; $i++) {
$uid = 6;
$user = user_load($uid);
-$node = new stdClass;
+$node = new stdClass();
$node->uid = $uid;
$node->type = 'broken';
$node->sticky = 0;
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index db34924c9..02e897e41 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -16,10 +16,10 @@ if ($args['help'] || $count == 0) {
exit;
}
-if ($args['execute-batch']) {
+if ($args['execute-test']) {
// Masquerade as Apache for running tests.
simpletest_script_init("Apache");
- simpletest_script_execute_batch();
+ simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
}
else {
// Run administrative functions as CLI.
@@ -67,8 +67,6 @@ if ($args['list']) {
exit;
}
-$test_list = simpletest_script_get_test_list();
-
// Try to allocate unlimited time to run the tests.
drupal_set_time_limit(0);
@@ -78,7 +76,7 @@ simpletest_script_reporter_init();
$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
// Execute tests.
-simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
+simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
// Retrieve the last database prefix used for testing and the last test class
// that was run from. Use the information to read the lgo file in case any
@@ -99,6 +97,9 @@ if ($args['xml']) {
// Cleanup our test results.
simpletest_clean_results_table($test_id);
+// Test complete, exit.
+exit;
+
/**
* Print help text.
*/
@@ -130,9 +131,7 @@ All arguments are long options.
--concurrency [num]
- Run tests in parallel, up to [num] tests at a time. This requires
- the Process Control Extension (PCNTL) to be compiled in PHP, not
- supported under Windows.
+ Run tests in parallel, up to [num] tests at a time.
--all Run all available tests.
@@ -193,8 +192,8 @@ function simpletest_script_parse_args() {
'verbose' => FALSE,
'test_names' => array(),
// Used internally.
- 'test-id' => NULL,
- 'execute-batch' => FALSE,
+ 'test-id' => 0,
+ 'execute-test' => '',
'xml' => '',
);
@@ -236,10 +235,6 @@ function simpletest_script_parse_args() {
simpletest_script_print_error("--concurrency must be a strictly positive integer.");
exit;
}
- elseif ($args['concurrency'] > 1 && !function_exists('pcntl_fork')) {
- simpletest_script_print_error("Parallel test execution requires the Process Control extension to be compiled in PHP. See http://php.net/manual/en/intro.pcntl.php for more information.");
- exit;
- }
return array($args, $count);
}
@@ -310,93 +305,96 @@ function simpletest_script_init($server_software) {
/**
* Execute a batch of tests.
*/
-function simpletest_script_execute_batch() {
+function simpletest_script_execute_batch($test_id, $test_classes) {
global $args;
- if (!isset($args['test-id'])) {
- simpletest_script_print_error("--execute-batch should not be called interactively.");
- exit;
- }
- if ($args['concurrency'] == 1) {
- // Fallback to mono-threaded execution.
- if (count($args['test_names']) > 1) {
- foreach ($args['test_names'] as $test_class) {
- // Execute each test in its separate Drupal environment.
- simpletest_script_command(1, $args['test-id'], $test_class);
+ // Multi-process execution.
+ $children = array();
+ while (!empty($test_classes) || !empty($children)) {
+ while (count($children) < $args['concurrency']) {
+ if (empty($test_classes)) {
+ break;
}
- exit;
- }
- else {
- // Execute an individual test.
- $test_class = array_shift($args['test_names']);
- drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
- simpletest_script_run_one_test($args['test-id'], $test_class);
- exit;
- }
- }
- else {
- // Multi-threaded execution.
- $children = array();
- while (!empty($args['test_names']) || !empty($children)) {
- // Fork children safely since Drupal is not bootstrapped yet.
- while (count($children) < $args['concurrency']) {
- if (empty($args['test_names'])) break;
-
- $child = array();
- $child['test_class'] = $test_class = array_shift($args['test_names']);
- $child['pid'] = pcntl_fork();
- if (!$child['pid']) {
- // This is the child process, bootstrap and execute the test.
- drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
- simpletest_script_run_one_test($args['test-id'], $test_class);
- exit;
- }
- else {
- // Register our new child.
- $children[] = $child;
- }
+
+ // Fork a child process.
+ $test_class = array_shift($test_classes);
+ $command = simpletest_script_command($test_id, $test_class);
+ $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
+
+ if (!is_resource($process)) {
+ echo "Unable to fork test process. Aborting.\n";
+ exit;
}
- // Wait for children every 200ms.
- usleep(200000);
+ // Register our new child.
+ $children[] = array(
+ 'process' => $process,
+ 'class' => $test_class,
+ 'pipes' => $pipes,
+ );
+ }
- // Check if some children finished.
- foreach ($children as $cid => $child) {
- if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) {
- // This particular child exited.
- unset($children[$cid]);
+ // Wait for children every 200ms.
+ usleep(200000);
+
+ // Check if some children finished.
+ foreach ($children as $cid => $child) {
+ $status = proc_get_status($child['process']);
+ if (empty($status['running'])) {
+ // The child exited, unregister it.
+ proc_close($child['process']);
+ if ($status['exitcode']) {
+ echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
}
+ unset($children[$cid]);
}
}
- exit;
}
}
/**
- * Run a single test (assume a Drupal bootstrapped environment).
+ * Bootstrap Drupal and run a single test.
*/
function simpletest_script_run_one_test($test_id, $test_class) {
- $test = new $test_class($test_id);
- $test->run();
- $info = $test->getInfo();
+ try {
+ // Bootstrap Drupal.
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+
+ $test = new $test_class($test_id);
+ $test->run();
+ $info = $test->getInfo();
+
+ $had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0);
+ $had_exceptions = (isset($test->results['#exception']) && $test->results['#exception'] > 0);
+ $status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
+ simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
- $status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
- || (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'fail' : 'pass');
- simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
+ // Finished, kill this runner.
+ exit(0);
+ }
+ catch (Exception $e) {
+ echo (string) $e;
+ exit(1);
+ }
}
/**
- * Execute a command to run batch of tests in separate process.
+ * Return a command used to run a test in a separate process.
+ *
+ * @param $test_id
+ * The current test ID.
+ * @param $test_class
+ * The name of the test class to run.
*/
-function simpletest_script_command($concurrency, $test_id, $tests) {
+function simpletest_script_command($test_id, $test_class) {
global $args, $php;
- $command = "$php ./scripts/{$args['script']} --url {$args['url']}";
+ $command = escapeshellarg($php) . ' ' . escapeshellarg('./scripts/' . $args['script']) . ' --url ' . escapeshellarg($args['url']);
if ($args['color']) {
$command .= ' --color';
}
- $command .= " --php " . escapeshellarg($php) . " --concurrency $concurrency --test-id $test_id --execute-batch $tests";
- passthru($command);
+ $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class";
+ return $command;
}
/**