diff --git a/.htaccess b/.htaccess index a69bdd4..725897e 100644 --- a/.htaccess +++ b/.htaccess @@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] + RewriteRule ^(.*)$ index.php [L] # Rules to correctly serve gzip compressed CSS and JS files. # Requires both mod_rewrite and mod_headers to be enabled. diff --git a/core/authorize.php b/core/authorize.php index c6ba51d..d703b33 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -103,17 +103,6 @@ if (authorize_access_allowed()) { // Load the code that drives the authorize process. require_once DRUPAL_ROOT . '/core/includes/authorize.inc'; - // For the sake of Batch API and a few other low-level functions, we need to - // initialize the URL path into $_GET['q']. However, we do not want to raise - // our bootstrap level, nor do we want to call drupal_initialize_path(), - // since that is assuming that modules are loaded and invoking hooks. - // However, all we really care is if we're in the middle of a batch, in which - // case $_GET['q'] will already be set, we just initialize it to an empty - // string if it's not already defined. - if (!isset($_GET['q'])) { - $_GET['q'] = ''; - } - if (isset($_SESSION['authorize_operation']['page_title'])) { drupal_set_title($_SESSION['authorize_operation']['page_title']); } diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 83ddd30..0b07d8e 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -14,6 +14,8 @@ * @see batch_get() */ +use \Symfony\Component\HttpFoundation\JsonResponse; + /** * Loads a batch from the database. * @@ -77,7 +79,7 @@ function _batch_page() { case 'do': // JavaScript-based progress page callback. - _batch_do(); + $output = _batch_do(); break; case 'do_nojs': @@ -160,7 +162,7 @@ function _batch_do() { // Perform actual processing. list($percentage, $message) = _batch_process(); - drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); + return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); } /** diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 6abe08a..e36d35f 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -4,6 +4,7 @@ use Drupal\Core\Database\Database; use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpFoundation\Request; /** * @file @@ -73,32 +74,32 @@ const WATCHDOG_EMERGENCY = 0; const WATCHDOG_ALERT = 1; /** - * Log message severity -- Critical conditions. + * Log message severity -- Critical: critical conditions. */ const WATCHDOG_CRITICAL = 2; /** - * Log message severity -- Error conditions. + * Log message severity -- Error: error conditions. */ const WATCHDOG_ERROR = 3; /** - * Log message severity -- Warning conditions. + * Log message severity -- Warning: warning conditions. */ const WATCHDOG_WARNING = 4; /** - * Log message severity -- Normal but significant conditions. + * Log message severity -- Notice: normal but significant condition. */ const WATCHDOG_NOTICE = 5; /** - * Log message severity -- Informational messages. + * Log message severity -- Informational: informational messages. */ const WATCHDOG_INFO = 6; /** - * Log message severity -- Debug-level messages. + * Log message severity -- Debug: debug-level messages. */ const WATCHDOG_DEBUG = 7; @@ -551,12 +552,8 @@ function drupal_environment_initialize() { $_SERVER['HTTP_HOST'] = ''; } - // When clean URLs are enabled, emulate ?q=foo/bar using REQUEST_URI. It is - // not possible to append the query string using mod_rewrite without the B - // flag (this was added in Apache 2.2.8), because mod_rewrite unescapes the - // path before passing it on to PHP. This is a problem when the path contains - // e.g. "&" or "%" that have special meanings in URLs and must be encoded. - $_GET['q'] = request_path(); + // @todo Refactor with the Symfony Request object. + _current_path(request_path()); // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. error_reporting(E_STRICT | E_ALL | error_reporting()); @@ -597,7 +594,7 @@ function drupal_valid_http_host($host) { * Sets the base URL, cookie domain, and session name from configuration. */ function drupal_settings_initialize() { - global $base_url, $base_path, $base_root; + global $base_url, $base_path, $base_root, $script_path; // Export the following settings.php variables to the global namespace global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directory_name, $config_signature_key; @@ -626,8 +623,8 @@ function drupal_settings_initialize() { $base_url = $base_root; - // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not - // be modified by a visitor. + // For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is + // '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'. if ($dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')) { // Remove "core" directory if present, allowing install.php, update.php, // cron.php and others to auto-detect a base path. @@ -648,6 +645,32 @@ function drupal_settings_initialize() { $base_secure_url = str_replace('http://', 'https://', $base_url); $base_insecure_url = str_replace('https://', 'http://', $base_url); + // Determine the path of the script relative to the base path, and add a + // trailing slash. This is needed for creating URLs to Drupal pages. + if (!isset($script_path)) { + $script_path = ''; + // We don't expect scripts outside of the base path, but sanity check + // anyway. + if (strpos($_SERVER['SCRIPT_NAME'], $base_path) === 0) { + $script_path = substr($_SERVER['SCRIPT_NAME'], strlen($base_path)) . '/'; + // If the request URI does not contain the script name, then clean URLs + // are in effect and the script path can be similarly dropped from URL + // generation. For servers that don't provide $_SERVER['REQUEST_URI'], we + // do not know the actual URI requested by the client, and request_uri() + // returns a URI with the script name, resulting in non-clean URLs unless + // there's other code that intervenes. + if (strpos(request_uri(TRUE) . '/', $base_path . $script_path) !== 0) { + $script_path = ''; + } + // @todo Temporary BC for install.php, update.php, and other scripts. + // - http://drupal.org/node/1547184 + // - http://drupal.org/node/1546082 + if ($script_path !== 'index.php/') { + $script_path = ''; + } + } + } + if ($cookie_domain) { // If the user specifies the cookie domain, also use it for session name. $session_name = $cookie_domain; @@ -842,7 +865,7 @@ function variable_initialize($conf = array()) { * The default value to use if this variable has never been set. * * @return - * The value of the variable. Unserialization is taken care of as necessary. + * The value of the variable. * * @see variable_del() * @see variable_set() @@ -1488,13 +1511,15 @@ function drupal_validate_utf8($text) { * * Because $_SERVER['REQUEST_URI'] is only available on Apache, we generate an * equivalent using other environment variables. + * + * @todo The above comment is incorrect: http://drupal.org/node/1547294. */ -function request_uri() { +function request_uri($omit_query_string = FALSE) { if (isset($_SERVER['REQUEST_URI'])) { $uri = $_SERVER['REQUEST_URI']; } else { - if (isset($_SERVER['argv'])) { + if (isset($_SERVER['argv'][0])) { $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0]; } elseif (isset($_SERVER['QUERY_STRING'])) { @@ -1507,7 +1532,28 @@ function request_uri() { // Prevent multiple slashes to avoid cross site requests via the Form API. $uri = '/' . ltrim($uri, '/'); - return $uri; + return $omit_query_string ? strtok($uri, '?') : $uri; +} + +/** + * Returns the current global reuqest object. + * + * @todo Replace this function with a proper dependency injection container. + * + * @staticvar Request $request + * @param Request $new_request + * The new request object to store. If you are not index.php, you probably + * should not be using this parameter. + * @return Request + * The current request object. + */ +function request(Request $new_request = NULL) { + static $request; + + if ($new_request) { + $request = $new_request; + } + return $request; } /** @@ -1568,16 +1614,8 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * NULL if message is already translated or not possible to * translate. * @param $severity - * The severity of the message; one of the following values as defined in - * @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink - * - WATCHDOG_EMERGENCY: Emergency, system is unusable. - * - WATCHDOG_ALERT: Alert, action must be taken immediately. - * - WATCHDOG_CRITICAL: Critical conditions. - * - WATCHDOG_ERROR: Error conditions. - * - WATCHDOG_WARNING: Warning conditions. - * - WATCHDOG_NOTICE: (default) Normal but significant conditions. - * - WATCHDOG_INFO: Informational messages. - * - WATCHDOG_DEBUG: Debug-level messages. + * The severity of the message, as per RFC 3164. Possible values are + * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. * @param $link * A link to associate with the message. * @@ -1768,7 +1806,7 @@ function drupal_is_denied($ip) { // won't be denied. However the user asked explicitly not to use the // database and also in this case it's quite likely that the user relies // on higher performance solutions like a firewall. - elseif (class_exists('Drupal\Core\Database\Database', FALSE)) { + elseif (class_exists('Database', FALSE)) { $denied = (bool)db_query("SELECT 1 FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField(); } return $denied; @@ -2143,8 +2181,24 @@ function _drupal_bootstrap_configuration() { // Initialize the configuration, including variables from settings.php. drupal_settings_initialize(); - // Activate the class loader. - drupal_classloader(); + // Include and activate the class loader. + $loader = drupal_classloader(); + + // Register explicit vendor namespaces. + $loader->registerNamespaces(array( + // All Symfony-borrowed code lives in /core/vendor/Symfony. + 'Symfony' => DRUPAL_ROOT . '/core/vendor', + )); + // Register the Drupal namespace for classes in core as a fallback. + // This allows to register additional namespaces within the Drupal namespace + // (e.g., for modules) and avoids an additional file_exists() on the Drupal + // core namespace, since the class loader can already determine the best + // namespace match based on a string comparison. It further allows modules to + // register/overload namespaces in Drupal core. + $loader->registerNamespaceFallbacks(array( + // All Drupal-namespaced code in core lives in /core/lib/Drupal. + 'Drupal' => DRUPAL_ROOT . '/core/lib', + )); } /** @@ -2181,7 +2235,7 @@ function _drupal_bootstrap_page_cache() { if (is_object($cache)) { header('X-Drupal-Cache: HIT'); // Restore the metadata cached with the page. - $_GET['q'] = $cache->data['path']; + _current_path($cache->data['path']); drupal_set_title($cache->data['title'], PASS_THROUGH); date_default_timezone_set(drupal_get_user_timezone()); // If the skipping of the bootstrap hooks is not enforced, call @@ -2325,10 +2379,6 @@ function drupal_container($reset = FALSE) { static $container = NULL; if ($reset || !isset($container)) { $container = new ContainerBuilder(); - // An interface language always needs to be available for t() and other - // functions. This default is overridden by drupal_language_initialize() - // during language negotiation. - $container->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language'); } return $container; } @@ -2418,9 +2468,9 @@ function drupal_maintenance_theme() { */ function drupal_fast_404() { $exclude_paths = variable_get('404_fast_paths_exclude', FALSE); - if ($exclude_paths && !preg_match($exclude_paths, $_GET['q'])) { + if ($exclude_paths && !preg_match($exclude_paths, request_path())) { $fast_paths = variable_get('404_fast_paths', FALSE); - if ($fast_paths && preg_match($fast_paths, $_GET['q'])) { + if ($fast_paths && preg_match($fast_paths, request_path())) { drupal_add_http_header('Status', '404 Not Found'); $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); // Replace @path in the variable with the page path. @@ -2549,32 +2599,43 @@ function language_multilingual() { /** * Returns a list of configured languages. * + * @param $only_enabled + * (optional) Whether to return only enabled languages. + * * @return * An associative array of languages, keyed by the language code, ordered by * weight ascending and name ascending. */ -function language_list() { +function language_list($only_enabled = FALSE) { $languages = &drupal_static(__FUNCTION__); // Initialize master language list. if (!isset($languages)) { + // Initialize local language list caches. + $languages = array('all' => array(), 'enabled' => array()); + + // Fill in master language list based on current configuration. $default = language_default(); if (language_multilingual() || module_exists('language')) { // Use language module configuration if available. - $languages = db_query('SELECT * FROM {language} ORDER BY weight ASC, name ASC')->fetchAllAssoc('langcode'); + $languages['all'] = db_query('SELECT * FROM {language} ORDER BY weight ASC, name ASC')->fetchAllAssoc('langcode'); } else { // No language module, so use the default language only. - $languages = array($default->langcode => $default); + $languages['all'][$default->langcode] = $default; } // Initialize default property so callers have an easy reference and can - // save the same object without data loss. - foreach ($languages as $langcode => $language) { - $languages[$langcode]->default = ($langcode == $default->langcode); + // save the same object without data loss. Also fill in the filtered list + // of enabled languages only. + foreach ($languages['all'] as $langcode => $language) { + $languages['all'][$langcode]->default = ($langcode == $default->langcode); + if ($language->enabled) { + $languages['enabled'][$langcode] = $languages['all'][$langcode]; + } } } - return $languages; + return $only_enabled ? $languages['enabled'] : $languages['all']; } /** @@ -2625,6 +2686,7 @@ function language_default() { 'langcode' => 'en', 'name' => 'English', 'direction' => 0, + 'enabled' => 1, 'weight' => 0, )); $default->default = TRUE; @@ -2655,42 +2717,45 @@ function request_path() { return $path; } - if (isset($_GET['q'])) { - // This is a request with a ?q=foo/bar query string. $_GET['q'] is - // overwritten in drupal_path_initialize(), but request_path() is called - // very early in the bootstrap process, so the original value is saved in - // $path and returned in later calls. - $path = $_GET['q']; - } - elseif (isset($_SERVER['REQUEST_URI'])) { - // This request is either a clean URL, or 'index.php', or nonsense. - // Extract the path from REQUEST_URI. - $request_path = strtok($_SERVER['REQUEST_URI'], '?'); - $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); - // Unescape and strip $base_path prefix, leaving q without a leading slash. - $path = substr(urldecode($request_path), $base_path_len + 1); - // If the path equals the script filename, either because 'index.php' was - // explicitly provided in the URL, or because the server added it to - // $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some - // versions of Microsoft IIS do this), the front page should be served. - if ($path == basename($_SERVER['PHP_SELF'])) { - $path = ''; - } - } - else { - // This is the front page. + // Get the part of the URI between the base path of the Drupal installation + // and the query string, and unescape it. + $request_path = request_uri(TRUE); + $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); + $path = substr(urldecode($request_path), $base_path_len + 1); + + // Depending on server configuration, the URI might or might not include the + // script name. For example, the front page might be accessed as + // http://example.com or as http://example.com/index.php, and the "user" + // page might be accessed as http://example.com/user or as + // http://example.com/index.php/user. Strip the script name from $path. + $script = basename($_SERVER['SCRIPT_NAME']); + if ($path == $script) { $path = ''; } + elseif (strpos($path, $script . '/') === 0) { + $path = substr($path, strlen($script) + 1); + } - // Under certain conditions Apache's RewriteRule directive prepends the value - // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing - // slash in place, hence we need to normalize $_GET['q']. + // Extra slashes can appear in URLs or under some conditions, added by Apache, + // so normalize. $path = trim($path, '/'); return $path; } /** + * @todo This is a temporary function pending refactoring Drupal to use + * Symfony's Request object exclusively. + */ +function _current_path($path = NULL) { + static $current_path = ''; + if (isset($path)) { + $current_path = $path; + } + return $current_path; +} + +/** * Returns a component of the current Drupal path. * * When viewing a page at the path "admin/structure/types", for example, arg(0) @@ -2726,7 +2791,9 @@ function arg($index = NULL, $path = NULL) { $arguments = &$drupal_static_fast['arguments']; if (!isset($path)) { - $path = $_GET['q']; + // @todo The public function current_path() is not available during early + // bootstrap. + $path = _current_path(); } if (!isset($arguments[$path])) { $arguments[$path] = explode('/', $path); @@ -2827,24 +2894,6 @@ function drupal_classloader() { $loader = new UniversalClassLoader(); break; } - - // Register explicit vendor namespaces. - $loader->registerNamespaces(array( - // All Symfony-borrowed code lives in /core/vendor/Symfony. - 'Symfony' => DRUPAL_ROOT . '/core/vendor', - )); - // Register the Drupal namespace for classes in core as a fallback. - // This allows to register additional namespaces within the Drupal namespace - // (e.g., for modules) and avoids an additional file_exists() on the Drupal - // core namespace, since the class loader can already determine the best - // namespace match based on a string comparison. It further allows modules - // to register/overload namespaces in Drupal core. - $loader->registerNamespaceFallbacks(array( - // All Drupal-namespaced code in core lives in /core/lib/Drupal. - 'Drupal' => DRUPAL_ROOT . '/core/lib', - )); - - // Register the loader with PHP. $loader->register(); } return $loader; diff --git a/core/includes/common.inc b/core/includes/common.inc index 353a9b5..327dc4a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1,5 +1,7 @@ $_GET['destination']); } else { - $path = $_GET['q']; + $path = current_path(); $query = drupal_http_build_query(drupal_get_query_parameters()); if ($query != '') { $path .= '?' . $query; @@ -711,7 +713,11 @@ function drupal_site_offline() { * bubble up to menu_execute_active_handler() should call drupal_not_found(). */ function drupal_not_found() { - drupal_deliver_page(MENU_NOT_FOUND); + + throw new NotFoundHttpException(); + + // @todo Remove this line. + //drupal_deliver_page(MENU_NOT_FOUND); } /** @@ -724,7 +730,11 @@ function drupal_not_found() { * drupal_access_denied(). */ function drupal_access_denied() { - drupal_deliver_page(MENU_ACCESS_DENIED); + + throw new AccessDeniedHttpException(); + + // @todo Remove this line. + //drupal_deliver_page(MENU_ACCESS_DENIED); } /** @@ -2243,31 +2253,20 @@ function url($path = NULL, array $options = array()) { $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - // With Clean URLs. - if (!empty($GLOBALS['conf']['clean_url'])) { - $path = drupal_encode_path($prefix . $path); - if ($options['query']) { - return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; - } - else { - return $base . $path . $options['fragment']; - } + // If Clean URLs are not enabled, we need to prefix the script name onto + // the link. + // @todo: Make this dynamic based on the request object without using a global + // request object. + if (empty($GLOBALS['conf']['clean_url'])) { + $base .= 'index.php/'; + } + + $path = drupal_encode_path($prefix . $path); + if ($options['query']) { + return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; } - // Without Clean URLs. else { - $path = $prefix . $path; - $query = array(); - if (!empty($path)) { - $query['q'] = $path; - } - if ($options['query']) { - // We do not use array_merge() here to prevent overriding $path via query - // parameters. - $query += $options['query']; - } - $query = $query ? ('?' . drupal_http_build_query($query)) : ''; - $script = isset($options['script']) ? $options['script'] : ''; - return $base . $script . $query . $options['fragment']; + return $base . $path . $options['fragment']; } } @@ -2404,7 +2403,7 @@ function l($text, $path, array $options = array()) { ); // Append active class. - if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && + if (($path == current_path() || ($path == '' && drupal_is_front_page())) && (empty($options['language']) || $options['language']->langcode == drupal_container()->get(LANGUAGE_TYPE_URL)->langcode)) { $options['attributes']['class'][] = 'active'; } @@ -2530,7 +2529,7 @@ function drupal_deliver_page($page_callback_result, $default_delivery_callback = // If a delivery callback is specified, but doesn't exist as a function, // something is wrong, but don't print anything, since it's not known // what format the response needs to be in. - watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR); + watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => current_path()), WATCHDOG_ERROR); } } @@ -2571,18 +2570,18 @@ function drupal_deliver_html_page($page_callback_result) { // Print a 404 page. drupal_add_http_header('Status', '404 Not Found'); - watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + watchdog('page not found', check_plain(current_path()), NULL, WATCHDOG_WARNING); // Check for and return a fast 404 page if configured. drupal_fast_404(); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + $_GET['destination'] = current_path(); } $path = drupal_get_normal_path(variable_get('site_404', '')); - if ($path && $path != $_GET['q']) { + if ($path && $path != current_path()) { // Custom 404 handler. Set the active item in case there are tabs to // display, or other dependencies on the path. menu_set_active_item($path); @@ -2603,15 +2602,15 @@ function drupal_deliver_html_page($page_callback_result) { case MENU_ACCESS_DENIED: // Print a 403 page. drupal_add_http_header('Status', '403 Forbidden'); - watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + watchdog('access denied', check_plain(current_path()), NULL, WATCHDOG_WARNING); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + $_GET['destination'] = current_path(); } $path = drupal_get_normal_path(variable_get('site_403', '')); - if ($path && $path != $_GET['q']) { + if ($path && $path != current_path()) { // Custom 403 handler. Set the active item in case there are tabs to // display or other dependencies on the path. menu_set_active_item($path); @@ -3518,13 +3517,7 @@ function drupal_build_css_cache($css) { $data = ''; $uri = ''; $map = variable_get('drupal_css_cache_files', array()); - // Create a new array so that only the file names are used to create the hash. - // This prevents new aggregates from being created unnecessarily. - $css_data = array(); - foreach ($css as $css_file) { - $css_data[] = $css_file['data']; - } - $key = hash('sha256', serialize($css_data)); + $key = hash('sha256', serialize($css)); if (isset($map[$key])) { $uri = $map[$key]; } @@ -5026,13 +5019,7 @@ function drupal_build_js_cache($files) { $contents = ''; $uri = ''; $map = variable_get('drupal_js_cache_files', array()); - // Create a new array so that only the file names are used to create the hash. - // This prevents new aggregates from being created unnecessarily. - $js_data = array(); - foreach ($files as $file) { - $js_data[] = $file['data']; - } - $key = hash('sha256', serialize($js_data)); + $key = hash('sha256', serialize($files)); if (isset($map[$key])) { $uri = $map[$key]; } @@ -5217,7 +5204,7 @@ function _drupal_bootstrap_full() { ini_set('error_log', 'public://error.log'); } - // Initialize $_GET['q'] prior to invoking hook_init(). + // Initialize current_path() prior to invoking hook_init(). drupal_path_initialize(); // Let all modules take action before the menu system handles the request. @@ -5249,15 +5236,15 @@ function _drupal_bootstrap_full() { * * @see drupal_page_header() */ -function drupal_page_set_cache() { +function drupal_page_set_cache($response_body) { global $base_root; if (drupal_page_is_cacheable()) { $cache = (object) array( 'cid' => $base_root . request_uri(), 'data' => array( - 'path' => $_GET['q'], - 'body' => ob_get_clean(), + 'path' => current_path(), + 'body' => $response_body, 'title' => drupal_get_title(), 'headers' => array(), ), diff --git a/core/includes/file.inc b/core/includes/file.inc index c4356a9..ab339f6 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -5,6 +5,9 @@ * API for handling file uploads and server file management. */ +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\StreamedResponse; use Drupal\Core\StreamWrapper\LocalStream; /** @@ -576,16 +579,16 @@ function file_save_htaccess($directory, $private = TRUE) { /** * Loads file objects from the database. * - * @param array|bool $fids - * An array of file IDs, or FALSE to load all files. - * @param array $conditions + * @param $fids + * An array of file IDs. + * @param $conditions * (deprecated) An associative array of conditions on the {file_managed} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. * - * @return array + * @return * An array of file objects, indexed by fid. * * @todo Remove $conditions in Drupal 8. @@ -595,8 +598,8 @@ function file_save_htaccess($directory, $private = TRUE) { * @see entity_load() * @see EntityFieldQuery */ -function file_load_multiple($fids = array(), array $conditions = array()) { - return entity_load_multiple('file', $fids, $conditions); +function file_load_multiple($fids = array(), $conditions = array()) { + return entity_load('file', $fids, $conditions); } /** @@ -2031,7 +2034,7 @@ function file_transfer($uri, $headers) { * @see system_menu() */ function file_download() { - // Merge remainder of arguments from GET['q'], into relative file path. + // Merge remaining path arguments into relative file path. $args = func_get_args(); $scheme = array_shift($args); $target = implode('/', $args); @@ -2046,18 +2049,27 @@ function file_download() { $function = $module . '_file_download'; $result = $function($uri); if ($result == -1) { - return drupal_access_denied(); + throw new AccessDeniedHttpException(); } if (isset($result) && is_array($result)) { $headers = array_merge($headers, $result); } } if (count($headers)) { - file_transfer($uri, $headers); + return new StreamedResponse(function() use ($uri) { + $scheme = file_uri_scheme($uri); + // Transfer file in 1024 byte chunks to save memory usage. + if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) { + while (!feof($fd)) { + print fread($fd, 1024); + } + fclose($fd); + } + }, 200, $headers); } - return drupal_access_denied(); + throw new AccessDeniedHttpException(); } - return drupal_not_found(); + throw new NotFoundHttpException(); } diff --git a/core/includes/form.inc b/core/includes/form.inc index 6d93420..9014999 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1248,7 +1248,7 @@ function drupal_redirect_form($form_state) { $function($form_state['redirect']); } } - drupal_goto($_GET['q']); + drupal_goto(current_path()); } } @@ -3597,20 +3597,6 @@ function form_process_fieldset(&$element, &$form_state) { // Contains form element summary functionalities. $element['#attached']['library'][] = array('system', 'drupal.form'); - return $element; -} - -/** - * Adds members of this group as actual elements for rendering. - * - * @param $element - * An associative array containing the properties and children of the - * fieldset. - * - * @return - * The modified element with all group members. - */ -function form_pre_render_fieldset($element) { // The .form-wrapper class is required for #states to treat fieldsets like // containers. if (!isset($element['#attributes']['class'])) { @@ -3626,6 +3612,20 @@ function form_pre_render_fieldset($element) { } } + return $element; +} + +/** + * Adds members of this group as actual elements for rendering. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * + * @return + * The modified element with all group members. + */ +function form_pre_render_fieldset($element) { // Fieldsets may be rendered outside of a Form API context. if (!isset($element['#parents']) || !isset($element['#groups'])) { return $element; @@ -4682,7 +4682,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd 'progressive' => TRUE, 'url' => $url, 'url_options' => array(), - 'source_url' => $_GET['q'], + 'source_url' => current_path(), 'redirect' => $redirect, 'theme' => $GLOBALS['theme_key'], 'redirect_callback' => $redirect_callback, diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 125852a..c9d5ebc 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -3,6 +3,9 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\Install\TaskException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + /** * @file * API functions for installing Drupal. @@ -227,6 +230,26 @@ function install_begin_request(&$install_state) { // Allow command line scripts to override server variables used by Drupal. require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; + // Ensure that the class loader is available so that we can leverage classes + // as part of the install routine. + $loader = drupal_classloader(); + + // Register explicit vendor namespaces. + $loader->registerNamespaces(array( + // All Symfony-borrowed code lives in /core/includes/Symfony. + 'Symfony' => DRUPAL_ROOT . '/core/vendor', + )); + // Register the Drupal namespace for classes in core as a fallback. + // This allows to register additional namespaces within the Drupal namespace + // (e.g., for modules) and avoids an additional file_exists() on the Drupal + // core namespace, since the class loader can already determine the best + // namespace match based on a string comparison. It further allows modules to + // register/overload namespaces in Drupal core. + $loader->registerNamespaceFallbacks(array( + // All Drupal-namespaced code in core lives in /core/includes/Drupal. + 'Drupal' => DRUPAL_ROOT . '/core/lib', + )); + if (!$install_state['interactive']) { drupal_override_server_variables($install_state['server']); } @@ -241,6 +264,14 @@ function install_begin_request(&$install_state) { drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + // A request object from the HTTPFoundation to tell us about the request. + $request = Request::createFromGlobals(); + + // Set the global $request object. This is a temporary measure to + // keep legacy utility functions working. It should be moved to a dependency + // injection container at some point. + request($request); + // This must go after drupal_bootstrap(), which unsets globals! global $conf; @@ -468,6 +499,15 @@ function install_run_task($task, &$install_state) { elseif ($current_batch == $function) { include_once DRUPAL_ROOT . '/core/includes/batch.inc'; $output = _batch_page(); + // Because Batch API now returns a JSON response for intermediary steps, + // but the installer doesn't handle Response objects yet, we will just + // send the output here and emulate the old model. + // @todo: Replace this when we refactor the installer to use a + // Request/Response workflow. + if ($output instanceof Response) { + $output->send(); + $output = NULL; + } // The task is complete when we try to access the batch page and receive // FALSE in return, since this means we are at a URL where we are no // longer requesting a batch ID. @@ -1522,11 +1562,8 @@ function install_configure_form($form, &$form_state, &$install_state) { // We add these strings as settings because JavaScript translation does not // work on install time. drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting'); - drupal_add_js('jQuery(function () { Drupal.cleanURLsInstallCheck(); });', 'inline'); // Add JS to show / hide the 'Email administrator about site updates' elements drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline'); - // Build menu to allow clean URL check. - menu_rebuild(); // Cache a fully-built schema. This is necessary for any invocation of // index.php because: (1) setting cache table entries requires schema @@ -1534,8 +1571,7 @@ function install_configure_form($form, &$form_state, &$install_state) { // loaded, so (3) if there is no cached schema, drupal_get_schema() will // try to generate one but with no loaded modules will return nothing. // - // This logically could be done during the 'install_finished' task, but the - // clean URL check requires it now. + // @todo Move this to the 'install_finished' task? drupal_get_schema(NULL, TRUE); // Return the form. @@ -1827,12 +1863,6 @@ function _install_configure_form($form, &$form_state, &$install_state) { '#attributes' => array('class' => array('timezone-detect')), ); - $form['server_settings']['clean_url'] = array( - '#type' => 'hidden', - '#default_value' => 0, - '#attributes' => array('id' => 'edit-clean-url', 'class' => array('install')), - ); - $form['update_notifications'] = array( '#type' => 'fieldset', '#title' => st('Update notifications'), @@ -1892,21 +1922,12 @@ function install_configure_form_submit($form, &$form_state) { // We precreated user 1 with placeholder values. Let's save the real values. $account = user_load(1); - $account->init = $account->mail = $form_state['values']['account']['mail']; - $account->roles = !empty($account->roles) ? $account->roles : array(); - $account->status = 1; - $account->timezone = $form_state['values']['date_default_timezone']; - $account->pass = $form_state['values']['account']['pass']; - $account->name = $form_state['values']['account']['name']; - $account->save(); + $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1, 'timezone' => $form_state['values']['date_default_timezone']); + user_save($account, array_merge($form_state['values']['account'], $merge_data)); // Load global $user and perform final login tasks. $user = user_load(1); user_login_finalize(); - if (isset($form_state['values']['clean_url'])) { - variable_set('clean_url', $form_state['values']['clean_url']); - } - // Record when this install ran. variable_set('install_time', $_SERVER['REQUEST_TIME']); } diff --git a/core/includes/install.inc b/core/includes/install.inc index 72adf1c..283e31c 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -121,8 +121,7 @@ function drupal_detect_baseurl($file = 'core/install.php') { $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://'; $host = $_SERVER['SERVER_NAME']; $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']); - $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']); - $dir = str_replace("/$file", '', $uri); + $dir = str_replace("/$file", '', $_SERVER['SCRIPT_NAME']); return "$proto$host$port$dir"; } diff --git a/core/includes/language.inc b/core/includes/language.inc index 8e5f1ac..01d418a 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -342,7 +342,8 @@ function language_negotiation_method_invoke($method_id, $method = NULL) { if (!isset($results[$method_id])) { global $user; - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); if (!isset($method)) { $negotiation_info = language_negotiation_info(); diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 96791e3..94911a6 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -441,7 +441,7 @@ function menu_set_item($path, $router_item) { function menu_get_item($path = NULL, $router_item = NULL) { $router_items = &drupal_static(__FUNCTION__); if (!isset($path)) { - $path = $_GET['q']; + $path = current_path(); } if (isset($router_item)) { $router_items[$path] = $router_item; @@ -498,7 +498,7 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) { // Allow other modules to change the site status but not the path because that // would not change the global variable. hook_url_inbound_alter() can be used // to change the path. Code later will not use the $read_only_path variable. - $read_only_path = !empty($path) ? $path : $_GET['q']; + $read_only_path = !empty($path) ? $path : current_path(); drupal_alter('menu_site_status', $page_callback_result, $read_only_path); // Only continue if the site status is not set. @@ -1043,11 +1043,11 @@ function menu_tree_output($tree) { $class[] = 'active-trail'; $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; } - // Normally, l() compares the href of every link with $_GET['q'] and sets + // Normally, l() compares the href of every link with current_path() and sets // the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. - if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { + if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != current_path()) { $data['link']['localized_options']['attributes']['class'][] = 'active'; } @@ -1846,11 +1846,11 @@ function menu_navigation_links($menu_name, $level = 0) { $class = ' active-trail'; $l['attributes']['class'][] = 'active-trail'; } - // Normally, l() compares the href of every link with $_GET['q'] and sets + // Normally, l() compares the href of every link with current_path() and sets // the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. - if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) { + if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != current_path()) { $l['attributes']['class'][] = 'active'; } // Keyed with the unique mlid to generate classes in theme_links(). @@ -1964,8 +1964,8 @@ function menu_local_tasks($level = 0) { // local tasks link to their parent, but the path of default local // tasks can still be accessed directly, in which case this link // would not be marked as active, since l() only compares the href - // with $_GET['q']. - if ($link['href'] != $_GET['q']) { + // with current_path(). + if ($link['href'] != current_path()) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( @@ -2040,8 +2040,8 @@ function menu_local_tasks($level = 0) { // Mark the link as active, if the current path is a (second-level) // local task of a default local task. Since this default local task // links to its parent, l() will not mark it as active, as it only - // compares the link's href to $_GET['q']. - if ($link['href'] != $_GET['q']) { + // compares the link's href to current_path(). + if ($link['href'] != current_path()) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( @@ -2421,7 +2421,7 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { $preferred_links = &drupal_static(__FUNCTION__); if (!isset($path)) { - $path = $_GET['q']; + $path = current_path(); } if (empty($selected_menu)) { @@ -3813,7 +3813,7 @@ function _menu_site_is_offline($check_only = FALSE) { // Ensure that the maintenance mode message is displayed only once // (allowing for page redirects) and specifically suppress its display on // the maintenance mode settings page. - if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') { + if (!$check_only && request()->attributes->get('system_path') != 'admin/config/development/maintenance') { if (user_access('administer site configuration')) { drupal_set_message(t('Operating in maintenance mode. Go online.', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE); } diff --git a/core/includes/module.inc b/core/includes/module.inc index 6192a38..0b357a1 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -452,8 +452,6 @@ function module_enable($module_list, $enable_dependencies = TRUE) { registry_update(); // Refresh the schema to include it. drupal_get_schema(NULL, TRUE); - // Update the theme registry to include it. - drupal_theme_rebuild(); // Allow modules to react prior to the installation of a module. module_invoke_all('modules_preinstall', array($module)); @@ -576,8 +574,6 @@ function module_disable($module_list, $disable_dependents = TRUE) { // Update the registry to remove the newly-disabled module. registry_update(); _system_update_bootstrap_status(); - // Update the theme registry to remove the newly-disabled module. - drupal_theme_rebuild(); } } diff --git a/core/includes/pager.inc b/core/includes/pager.inc index c579e7e..201c5ad 100644 --- a/core/includes/pager.inc +++ b/core/includes/pager.inc @@ -297,7 +297,7 @@ function pager_default_initialize($total, $limit, $element = 0) { function pager_get_query_parameters() { $query = &drupal_static(__FUNCTION__); if (!isset($query)) { - $query = drupal_get_query_parameters($_GET, array('q', 'page')); + $query = drupal_get_query_parameters($_GET, array('page')); } return $query; } @@ -638,7 +638,7 @@ function theme_pager_link($variables) { // none of the pager links is active at any time - but it should still be // possible to use l() here. // @see http://drupal.org/node/1410574 - $attributes['href'] = url($_GET['q'], array('query' => $query)); + $attributes['href'] = url(current_path(), array('query' => $query)); return '' . check_plain($text) . ''; } diff --git a/core/includes/path.inc b/core/includes/path.inc index d6a0201..0c7e4d4 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -10,15 +10,24 @@ */ /** - * Initialize the $_GET['q'] variable to the proper normal path. + * Initializes the current path to the proper normal path. */ function drupal_path_initialize() { - // Ensure $_GET['q'] is set before calling drupal_normal_path(), to support - // path caching with hook_url_inbound_alter(). - if (empty($_GET['q'])) { - $_GET['q'] = variable_get('site_frontpage', 'user'); + // At this point, the current path is either the request path (due to + // drupal_environment_initialize()) or some modified version of it due to + // other bootstrap code (e.g., language negotiation), but it has not yet been + // normalized by drupal_get_normal_path(). + $path = _current_path(); + + // If on the front page, resolve to the front page path, including for calls + // to current_path() while drupal_get_normal_path() is in progress. + if (empty($path)) { + $path = variable_get('site_frontpage', 'user'); + _current_path($path); } - $_GET['q'] = drupal_get_normal_path($_GET['q']); + + // Normalize the path. + _current_path(drupal_get_normal_path($path)); } /** @@ -234,7 +243,7 @@ function drupal_cache_system_paths() { function drupal_get_path_alias($path = NULL, $langcode = NULL) { // If no path is specified, use the current page's path. if ($path == NULL) { - $path = $_GET['q']; + $path = current_path(); } $result = $path; if ($alias = drupal_lookup_path('alias', $path, $langcode)) { @@ -289,9 +298,7 @@ function drupal_is_front_page() { $is_front_page = &$drupal_static_fast['is_front_page']; if (!isset($is_front_page)) { - // As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path, - // we can check it against the 'site_frontpage' variable. - $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'user')); + $is_front_page = (current_path() == variable_get('site_frontpage', 'user')); } return $is_front_page; @@ -340,9 +347,9 @@ function drupal_match_path($path, $patterns) { * - http://example.com/path/alias (which is a path alias for node/306) returns * "node/306" as opposed to the path alias. * - * This function is not available in hook_boot() so use $_GET['q'] instead. + * This function is not available in hook_boot() so use request_path() instead. * However, be careful when doing that because in the case of Example #3 - * $_GET['q'] will contain "path/alias". If "node/306" is needed, calling + * request_path() will contain "path/alias". If "node/306" is needed, calling * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. * * @return @@ -351,7 +358,7 @@ function drupal_match_path($path, $patterns) { * @see request_path() */ function current_path() { - return $_GET['q']; + return _current_path(); } /** diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc index 3c70b96..a900a61 100644 --- a/core/includes/tablesort.inc +++ b/core/includes/tablesort.inc @@ -150,7 +150,7 @@ function tablesort_header($cell, $header, $ts) { $ts['sort'] = 'asc'; $image = ''; } - $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); + $cell['data'] = l($cell['data'] . $image, current_path(), array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); unset($cell['field'], $cell['sort']); } @@ -193,7 +193,7 @@ function tablesort_cell($cell, $header, $ts, $i) { * page request except for those pertaining to table sorting. */ function tablesort_get_query_parameters() { - return drupal_get_query_parameters($_GET, array('q', 'sort', 'order')); + return drupal_get_query_parameters($_GET, array('sort', 'order')); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 99e872e..46b29bc 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -237,7 +237,7 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb /** * Get the theme registry. * - * @param bool $complete + * @param $complete * Optional boolean to indicate whether to return the complete theme registry * array or an instance of the ThemeRegistry class. If TRUE, the complete * theme registry array will be returned. This is useful if you want to @@ -252,20 +252,7 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb * class. */ function theme_get_registry($complete = TRUE) { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['registry'] = &drupal_static('theme_get_registry'); - } - $theme_registry = &$drupal_static_fast['registry']; - - // Initialize the theme, if this is called early in the bootstrap, or after - // static variables have been reset. - if (!is_array($theme_registry)) { - drupal_theme_initialize(); - $theme_registry = array(); - } - + static $theme_registry = array(); $key = (int) $complete; if (!isset($theme_registry[$key])) { @@ -348,7 +335,6 @@ function _theme_save_registry($theme, $registry) { * to add more theme hooks. */ function drupal_theme_rebuild() { - drupal_static_reset('theme_get_registry'); cache()->deletePrefix('theme_registry'); } @@ -957,6 +943,8 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { * @see themeable */ function theme($hook, $variables = array()) { + static $hooks = NULL; + // If called before all modules are loaded, we do not necessarily have a full // theme registry to work with, and therefore cannot process the theme // request properly. See also _theme_load_registry(). @@ -964,7 +952,10 @@ function theme($hook, $variables = array()) { throw new Exception(t('theme() may not be called until all modules are loaded.')); } - $hooks = theme_get_registry(FALSE); + if (!isset($hooks)) { + drupal_theme_initialize(); + $hooks = theme_get_registry(FALSE); + } // If an array of hook candidates were passed, use the first one that has an // implementation. @@ -1743,7 +1734,7 @@ function theme_links($variables) { // Handle links. if (isset($link['href'])) { - $is_current_path = ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page())); + $is_current_path = ($link['href'] == current_path() || ($link['href'] == '' && drupal_is_front_page())); $is_current_language = (empty($link['language']) || $link['language']->langcode == $language_url->langcode); if ($is_current_path && $is_current_language) { $class[] = 'active'; @@ -2546,9 +2537,6 @@ function template_preprocess_html(&$variables) { $variables['head_title_array'] = $head_title; $variables['head_title'] = implode(' | ', $head_title); - // Display the html.tpl.php’s default mobile metatags for responsive design. - $variables['default_mobile_metatags'] = TRUE; - // Populate the page template suggestions. if ($suggestions = theme_get_suggestions(arg(), 'html')) { $variables['theme_hook_suggestions'] = $suggestions; diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index a2d6870..3fd60c9 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -38,7 +38,7 @@ function _drupal_maintenance_theme() { // The bootstrap was not complete. So we are operating in a crippled // environment, we need to bootstrap just enough to allow hook invocations // to work. See _drupal_log_error(). - if (!class_exists('Drupal\Core\Database\Database', FALSE)) { + if (!class_exists('Database', FALSE)) { require_once DRUPAL_ROOT . '/core/includes/database.inc'; } diff --git a/core/includes/update.inc b/core/includes/update.inc index e5c62d8..2715fdf 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -184,42 +184,20 @@ function update_prepare_d8_language() { db_drop_field('languages', 'prefix'); db_drop_field('languages', 'domain'); db_drop_field('languages', 'native'); - db_drop_field('languages', 'enabled'); - - // Update language count. - variable_set('language_count', db_query('SELECT COUNT(language) FROM {languages}')->fetchField()); // Rename the languages table to language. db_rename_table('languages', 'language'); - // Install/enable the language module. We need to use the update specific - // version of this function to ensure schema conflicts don't happen due to - // our updated data. + // Finally install/enable the language module. We need to use the update + // specific version of this function to ensure schema conflicts don't happen + // due to our updated data. $modules = array('language'); update_module_add_to_system($modules); update_module_enable($modules); // Rename 'language' column to 'langcode'. - db_drop_primary_key('language'); - $langcode_spec = array( - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - 'description' => "Language code, e.g. 'de' or 'en-US'.", - ); - db_change_field('language', 'language', 'langcode', $langcode_spec, array('primary key' => array('langcode'))); - - // Update the 'language_default' system variable with the langcode change. - $language_default = variable_get('language_default'); - if (!empty($language_default)) { - if (isset($language_default->language)) { - $language_default->langcode = $language_default->language; - unset($language_default->language); - } - unset($language_default->enabled); - variable_set('language_default', $language_default); - } + require_once DRUPAL_ROOT . '/core/modules/language/language.install'; + language_update_8000(); } } diff --git a/core/includes/utility.inc b/core/includes/utility.inc index 5019852..d44e4dd 100644 --- a/core/includes/utility.inc +++ b/core/includes/utility.inc @@ -46,7 +46,7 @@ function drupal_var_export($var, $prefix = '') { $output = "'" . $var . "'"; } } - elseif (is_object($var) && get_class($var) === 'stdClass') { + else if (is_object($var) && get_class($var) === 'stdClass') { // var_export() will export stdClass objects using an undefined // magic method __set_state() leaving the export broken. This // workaround avoids this by casting the object as an array for diff --git a/core/lib/Drupal/Core/Cache/InstallBackend.php b/core/lib/Drupal/Core/Cache/InstallBackend.php index 0861090..f332fcc 100644 --- a/core/lib/Drupal/Core/Cache/InstallBackend.php +++ b/core/lib/Drupal/Core/Cache/InstallBackend.php @@ -57,7 +57,7 @@ class InstallBackend extends DatabaseBackend { */ function delete($cid) { try { - if (class_exists('Drupal\Core\Database\Database')) { + if (class_exists('Database')) { parent::delete($cid); } } @@ -69,7 +69,7 @@ class InstallBackend extends DatabaseBackend { */ function deleteMultiple(array $cids) { try { - if (class_exists('Drupal\Core\Database\Database')) { + if (class_exists('Database')) { parent::deleteMultiple($cids); } } @@ -81,7 +81,7 @@ class InstallBackend extends DatabaseBackend { */ function deletePrefix($prefix) { try { - if (class_exists('Drupal\Core\Database\Database')) { + if (class_exists('Database')) { parent::deletePrefix($prefix); } } @@ -90,7 +90,7 @@ class InstallBackend extends DatabaseBackend { function invalidateTags(array $tags) { try { - if (class_exists('Drupal\Core\Database\Database')) { + if (class_exists('Database')) { parent::invalidateTags($tags); } } @@ -102,7 +102,7 @@ class InstallBackend extends DatabaseBackend { */ function flush() { try { - if (class_exists('Drupal\Core\Database\Database')) { + if (class_exists('Database')) { parent::flush(); } } diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php new file mode 100644 index 0000000..b87b33c --- /dev/null +++ b/core/lib/Drupal/Core/ContentNegotiation.php @@ -0,0 +1,53 @@ +. + if ($request->get('ajax_iframe_upload', FALSE)) { + return 'iframeupload'; + } + + // AJAX calls need to be run through ajax rendering functions + elseif ($request->isXmlHttpRequest()) { + return 'ajax'; + } + + foreach ($request->getAcceptableContentTypes() as $mime_type) { + $format = $request->getFormat($mime_type); + if (!is_null($format)) { + return $format; + } + } + + // Do HTML last so that it always wins. + return 'html'; + } + +} + diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..26ff906 --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,88 @@ +dispatcher = $dispatcher; + $this->resolver = $resolver; + + $context = new RequestContext(); + $this->matcher = new UrlMatcher($context); + $this->dispatcher->addSubscriber(new RouterListener($this->matcher)); + + $negotiation = new ContentNegotiation(); + + // @todo Make this extensible rather than just hard coding some. + // @todo Add a subscriber to handle other things, too, like our Ajax + // replacement system. + $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation)); + $this->dispatcher->addSubscriber(new AccessSubscriber()); + $this->dispatcher->addSubscriber(new MaintenanceModeSubscriber()); + $this->dispatcher->addSubscriber(new PathSubscriber()); + $this->dispatcher->addSubscriber(new LegacyControllerSubscriber()); + $this->dispatcher->addSubscriber(new RequestCloseSubscriber()); + + // Some other form of error occured that wasn't handled by another kernel + // listener. That could mean that it's a method/mime-type/error + // combination that is not accounted for, or some other type of error. + // Either way, treat it as a server-level error and return an HTTP 500. + // By default, this will be an HTML-type response because that's a decent + // best guess if we don't know otherwise. + $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute'))); + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php new file mode 100644 index 0000000..04eac0a --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -0,0 +1,54 @@ +getRequest()->attributes->get('drupal_menu_item'); + + if (isset($router_item['access']) && !$router_item['access']) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php new file mode 100644 index 0000000..cc4da15 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php @@ -0,0 +1,64 @@ +getRequest()->attributes->get('drupal_menu_item'); + $controller = $event->getController(); + + // This BC logic applies only to functions. Otherwise, skip it. + if (is_string($controller) && function_exists($controller)) { + $new_controller = function() use ($router_item) { + return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); + }; + $event->setController($new_controller); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php new file mode 100644 index 0000000..2befe92 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -0,0 +1,59 @@ +getRequest()->attributes->get('system_path'); + drupal_alter('menu_site_status', $status, $read_only_path); + + // Only continue if the site is online. + if ($status != MENU_SITE_ONLINE) { + // Deliver the 503 page. + drupal_maintenance_theme(); + drupal_set_title(t('Site under maintenance')); + $content = theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))))); + $response = new Response('Service unavailable', 503); + $response->setContent($content); + $event->setResponse($response); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenanceModeCheck', 40); + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php new file mode 100644 index 0000000..9954bb1 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -0,0 +1,68 @@ +getRequest(); + + $path = ltrim($request->getPathInfo(), '/'); + + if (empty($path)) { + // @todo Temporary hack. Fix when configuration is injectable. + $path = variable_get('site_frontpage', 'user'); + } + $system_path = drupal_get_normal_path($path); + + $request->attributes->set('system_path', $system_path); + + // @todo Remove this line. + // Drupal uses current_path() directly in over 100 places at present, + // including writing back to it at times. Those are all critical bugs, + // even by Drupal 7 standards, but as many of the places that it does so + // are slated to be rewritten anyway we will save time and include this + // temporary hack. Removal of this line is a critical, Drupal-release + // blocking bug. + current_path() = $system_path; + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php new file mode 100644 index 0000000..a6f9ad8 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php @@ -0,0 +1,66 @@ +getResponse(); + $config = config('system.performance'); + + if ($config->get('cache') && ($cache = drupal_page_set_cache($response->getContent()))) { + drupal_serve_page_from_cache($cache); + } + else { + ob_flush(); + } + + _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); + drupal_cache_system_paths(); + module_implements_write_cache(); + system_run_automated_cron(); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::TERMINATE][] = array('onTerminate'); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php new file mode 100644 index 0000000..647b641 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -0,0 +1,131 @@ +negotiation = $negotiation; + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an JSON-type response is a JSON string, so just wrap it into a + * Response object. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onView(GetResponseEvent $event) { + + $request = $event->getRequest(); + + $method = 'on' . $this->negotiation->getContentType($request); + + if (method_exists($this, $method)) { + $event->setResponse($this->$method($event)); + } + else { + $event->setResponse(new Response('Unsupported Media Type', 415)); + } + } + + public function onJson(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + //print_r($page_callback_result); + + $response = new JsonResponse(); + $response->setContent($page_callback_result); + + return $response; + } + + public function onAjax(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + // Construct the response content from the page callback result. + $commands = ajax_prepare_response($page_callback_result); + $json = ajax_render($commands); + + // Build the actual response object. + $response = new JsonResponse(); + $response->setContent($json); + + return $response; + } + + public function onIframeUpload(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + // Construct the response content from the page callback result. + $commands = ajax_prepare_response($page_callback_result); + $json = ajax_render($commands); + + // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification + // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into + // links. This corrupts the JSON response. Protect the integrity of the + // JSON data by making it the value of a textarea. + // @see http://malsup.com/jquery/form/#file-upload + // @see http://drupal.org/node/1009382 + $html = ''; + + return new Response($html); + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an HTML-type response is a render array from a legacy page callback + * and render it. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onHtml(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + return new Response(drupal_render_page($page_callback_result)); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::VIEW][] = array('onView'); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php new file mode 100644 index 0000000..d937158 --- /dev/null +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -0,0 +1,211 @@ +kernel = $kernel; + $this->negotiation = $negotiation; + } + + /** + * Handles an exception on a request. + * + * @param FlattenException $exception + * The flattened exception. + * @param Request $request + * The request that generated the exception. + * @return \Symfony\Component\HttpFoundation\Response + * A response object to be sent to the server. + */ + public function execute(FlattenException $exception, Request $request) { + + $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request); + + if (method_exists($this, $method)) { + return $this->$method($exception, $request); + } + + return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode()); + + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Html(FlattenException $exception, Request $request) { + $event->setResponse(new Response('Method Not Allowed', 405)); + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Html(FlattenException $exception, Request $request) { + $system_path = $request->attributes->get('system_path'); + watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING); + + $path = drupal_get_normal_path(variable_get('site_403', '')); + if ($path && $path != $system_path) { + // Keep old path for reference, and to allow forms to redirect to it. + if (!isset($_GET['destination'])) { + $_GET['destination'] = $system_path; + } + + $subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all()); + + $response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST); + $response->setStatusCode(403, 'Access denied'); + } + else { + $response = new Response('Access Denied', 403); + + // @todo Replace this block with something cleaner. + $return = t('You are not authorized to access this page.'); + drupal_set_title(t('Access denied')); + drupal_set_page_content($return); + $page = element_info('page'); + $content = drupal_render_page($page); + + $response->setContent($content); + } + + return $response; + } + + /** + * Processes a NotFound exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on404Html(FlattenException $exception, Request $request) { + watchdog('page not found', check_plain(current_path()), NULL, WATCHDOG_WARNING); + + // Check for and return a fast 404 page if configured. + // @todo Inline this rather than using a function. + drupal_fast_404(); + + $system_path = $request->attributes->get('system_path'); + + // Keep old path for reference, and to allow forms to redirect to it. + if (!isset($_GET['destination'])) { + $_GET['destination'] = $system_path; + } + + $path = drupal_get_normal_path(variable_get('site_404', '')); + if ($path && $path != $system_path) { + // @todo: Um, how do I specify an override URL again? Totally not clear. + // Do that and sub-call the kernel rather than using meah(). + // @todo: The create() method expects a slash-prefixed path, but we + // store a normal system path in the site_404 variable. + $subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all()); + + $response = $this->kernel->handle($subrequest, HttpKernelInterface::SUB_REQUEST); + $response->setStatusCode(404, 'Not Found'); + } + else { + $response = new Response('Not Found', 404); + + // @todo Replace this block with something cleaner. + $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo())); + drupal_set_title(t('Page not found')); + drupal_set_page_content($return); + $page = element_info('page'); + $content = drupal_render_page($page); + + $response->setContent($content); + } + + return $response; + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(403, 'Access Denied'); + return $response; + } + + /** + * Processes a NotFound exception into an HTTP 404 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on404Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(404, 'Not Found'); + return $response; + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(405, 'Method Not Allowed'); + return $response; + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php index 0cd76e0..1b4abc9 100644 --- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php @@ -44,7 +44,7 @@ abstract class LocalStream implements StreamWrapperInterface { /** * Gets the path that the wrapper is responsible for. - * @TODO: Review this method name in D8 per http://drupal.org/node/701358 + * @todo: Review this method name in D8 per http://drupal.org/node/701358 * * @return string * String specifying the path. diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index 2dca5ba..ad2213a 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -213,7 +213,7 @@ class Updater { $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); // Run the updates. - // @TODO: decide if we want to implement this. + // @todo: decide if we want to implement this. $this->postUpdate(); // For now, just return a list of links of things to do. @@ -252,7 +252,7 @@ class Updater { $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); // Potentially enable something? - // @TODO: decide if we want to implement this. + // @todo: decide if we want to implement this. $this->postInstall(); // For now, just return a list of links of things to do. return $this->postInstallTasks(); diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php new file mode 100644 index 0000000..585fc45 --- /dev/null +++ b/core/lib/Drupal/Core/UrlMatcher.php @@ -0,0 +1,111 @@ +context = $context; + } + + /** + * {@inheritDoc} + * + * @api + */ + public function match($pathinfo) { + + $this->allow = array(); + + // Symfony uses a prefixing / but we don't yet. + $dpathinfo = ltrim($pathinfo, '/'); + + // Do our fancy frontpage logic. + if (empty($dpathinfo)) { + $dpathinfo = variable_get('site_frontpage', 'user'); + $pathinfo = '/' . $dpathinfo; + } + + if ($router_item = $this->matchDrupalItem($dpathinfo)) { + $ret = $this->convertDrupalItem($router_item); + // Stash the router item in the attributes while we're transitioning. + $ret['drupal_menu_item'] = $router_item; + + // Most legacy controllers (aka page callbacks) are in a separate file, + // so we have to include that. + if ($router_item['include_file']) { + require_once DRUPAL_ROOT . '/' . $router_item['include_file']; + } + + return $ret; + } + + // This matcher doesn't differentiate by method, so don't bother with those + // exceptions. + throw new ResourceNotFoundException(); + } + + /** + * Get a drupal menu item. + * + * @todo Make this return multiple possible candidates for the resolver to + * consider. + * + * @param string $path + * The path being looked up by + */ + protected function matchDrupalItem($path) { + // For now we can just proxy our procedural method. At some point this will + // become more complicated because we'll need to get back candidates for a + // path and them resolve them based on things like method and scheme which + // we currently can't do. + return menu_get_item($path); + } + + protected function convertDrupalItem($router_item) { + $route = array( + '_controller' => $router_item['page_callback'] + ); + + // Place argument defaults on the route. + // @todo: For some reason drush test runs have a serialized page_arguments + // but HTTP requests are unserialized. Hack to get around this for now. + // This might be because page arguments aren't unserialized in + // menu_get_item() when the access is denied. + !is_array($router_item['page_arguments']) ? $page_arguments = unserialize($router_item['page_arguments']) : $page_arguments = $router_item['page_arguments']; + foreach ($page_arguments as $k => $v) { + $route[$k] = $v; + } + return $route; + return new Route($router_item['href'], $route); + } +} diff --git a/core/misc/ajax.js b/core/misc/ajax.js index c35241c..072cd2b 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -115,16 +115,12 @@ Drupal.ajax = function (base, element, element_settings) { // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let // the server detect when it needs to degrade gracefully. - // There are five scenarios to check for: + // There are four scenarios to check for: // 1. /nojs/ // 2. /nojs$ - The end of a URL string. - // 3. /nojs? - Followed by a query (with clean URLs enabled). - // E.g.: path/nojs?destination=foobar - // 4. /nojs& - Followed by a query (without clean URLs enabled). - // E.g.: ?q=path/nojs&destination=foobar - // 5. /nojs# - Followed by a fragment. - // E.g.: path/nojs#myfragment - this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); + // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar). + // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment). + this.url = element_settings.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1'); this.wrapper = '#' + element_settings.wrapper; // If there isn't a form, jQuery.ajax() will be used instead, allowing us to diff --git a/core/misc/drupal.js b/core/misc/drupal.js index c7917b0..5bbbf51 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -202,6 +202,13 @@ Drupal.t = function (str, args, options) { }; /** + * Returns the URL to a Drupal page. + */ +Drupal.url = function (path) { + return Drupal.settings.basePath + Drupal.settings.scriptPath + path; +} + +/** * Format a string containing a count of items. * * This function ensures that the string is pluralized correctly. Since Drupal.t() is diff --git a/core/misc/timezone.js b/core/misc/timezone.js index 62b7d4b..6f82bc1 100644 --- a/core/misc/timezone.js +++ b/core/misc/timezone.js @@ -50,8 +50,8 @@ Drupal.behaviors.setTimezone = { var element = this; $.ajax({ async: false, - url: settings.basePath, - data: { q: path, date: dateString }, + url: Drupal.url(path), + data: { date: dateString }, dataType: 'json', success: function (data) { if (data) { diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 09da1cf..ac868b4 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -401,11 +401,9 @@ function _aggregator_parse_opml($opml) { * An object describing the feed to be refreshed. * * @see aggregator_menu() + * @see aggregator_admin_refresh_feed_access() */ function aggregator_admin_refresh_feed($feed) { - if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) { - return MENU_ACCESS_DENIED; - } aggregator_refresh($feed); drupal_goto('admin/config/services/aggregator'); } diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index caac279..5efc773 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -138,7 +138,8 @@ function aggregator_menu() { 'title' => 'Update items', 'page callback' => 'aggregator_admin_refresh_feed', 'page arguments' => array(5), - 'access arguments' => array('administer news feeds'), + 'access callback' => 'aggregator_admin_refresh_feed_access', + 'access arguments' => array(5), 'file' => 'aggregator.admin.inc', ); $items['admin/config/services/aggregator/list'] = array( @@ -796,3 +797,23 @@ function aggregator_preprocess_block(&$variables) { $variables['attributes_array']['role'] = 'complementary'; } } + +/** + * Access callback: Determines if feed refresh is accessible. + * + * @param $feed + * An object describing the feed to be refreshed. + * + * @see aggregator_admin_refresh_feed() + * @see aggregator_menu() + */ +function aggregator_admin_refresh_feed_access($feed) { + if (!user_access('administer news feeds')) { + return FALSE; + } + $token = request()->query->get('token'); + if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->fid)) { + return FALSE; + } + return TRUE; +} diff --git a/core/modules/block/block.js b/core/modules/block/block.js index dabb570..097b9a7 100644 --- a/core/modules/block/block.js +++ b/core/modules/block/block.js @@ -131,8 +131,24 @@ Drupal.behaviors.blockDrag = { var select = $(this); tableDrag.rowObject = new tableDrag.row(row); - // Find the correct region and insert the row as the last in the region. - table.find('.region-' + select[0].value + '-message').nextUntil('.region-message').last().before(row); + // Find the correct region and insert the row as the first in the region. + table.find('tr.region-message').each(function () { + if ($(this).is('.region-' + select[0].value + '-message')) { + // Add the new row and remove the old one. + $(this).after(row); + // Manually update weights and restripe. + tableDrag.updateFields(row.get(0)); + tableDrag.rowObject.changed = true; + if (tableDrag.oldRowElement) { + $(tableDrag.oldRowElement).removeClass('drag-previous'); + } + tableDrag.oldRowElement = row.get(0); + tableDrag.restripeTable(); + tableDrag.rowObject.markChanged(); + tableDrag.oldRowElement = row; + row.addClass('drag-previous'); + } + }); // Modify empty regions with added or removed fields. checkEmptyRegions(table, row); diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 069e879..a7b3390 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -628,9 +628,9 @@ function block_form_user_profile_form_alter(&$form, &$form_state) { /** * Implements hook_user_presave(). */ -function block_user_presave($account) { - if (isset($account->block)) { - $account->data['block'] = $account->block; +function block_user_presave(&$edit, $account) { + if (isset($edit['block'])) { + $edit['data']['block'] = $edit['block']; } } @@ -836,13 +836,10 @@ function block_block_list_alter(&$blocks) { // with different case. Ex: /Page, /page, /PAGE. $pages = drupal_strtolower($block->pages); if ($block->visibility < BLOCK_VISIBILITY_PHP) { - // Convert the Drupal path to lowercase - $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); - // Compare the lowercase internal and lowercase path alias (if any). - $page_match = drupal_match_path($path, $pages); - if ($path != $_GET['q']) { - $page_match = $page_match || drupal_match_path($_GET['q'], $pages); - } + // Compare the lowercase path alias (if any) and internal path. + $path = current_path(); + $path_alias = drupal_strtolower(drupal_get_path_alias($path)); + $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages)); // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), // the block is displayed on all pages except those listed in $block->pages. // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those diff --git a/core/modules/block/block.test b/core/modules/block/block.test index f4a9549..4af6240 100644 --- a/core/modules/block/block.test +++ b/core/modules/block/block.test @@ -542,8 +542,8 @@ class BlockCacheTestCase extends DrupalWebTestCase { $this->normal_user_alt = $this->drupalCreateUser(); // Sync the roles, since drupalCreateUser() creates separate roles for // the same permission sets. + user_save($this->normal_user_alt, array('roles' => $this->normal_user->roles)); $this->normal_user_alt->roles = $this->normal_user->roles; - $this->normal_user_alt->save(); // Enable our test block. $edit['blocks[block_test_test_cache][region]'] = 'sidebar_first'; @@ -844,189 +844,3 @@ class BlockHiddenRegionTestCase extends DrupalWebTestCase { $this->assertText('Search', t('Block was displayed on the front page.')); } } - -/** - * Functional tests for the language list configuration forms. - */ -class BlockLanguageTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Language block visibility', - 'description' => 'Tests if a block can be configure to be only visibile on a particular language.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp('language', 'block'); - } - - /** - * Tests the visibility settings for the blocks based on language. - */ - public function testLanguageBlockVisibility() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Check if the visibility setting is available. - $this->drupalGet('admin/structure/block/add'); - $this->assertField('langcodes[en]', t('Language visibility field is visible.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[en]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Change the default language. - $edit = array( - 'site_default' => 'fr', - ); - $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); - - // Reset the static cache of the language list. - drupal_static_reset('language_list'); - - // Check that a page has a block - $this->drupalGet('', array('language' => language_load('en'))); - $this->assertText($body, t('The body of the custom block appears on the page.')); - - // Check that a page doesn't has a block for the current language anymore - $this->drupalGet('', array('language' => language_load('fr'))); - $this->assertNoText($body, t('The body of the custom block does not appear on the page.')); - } - - /** - * Tests if the visibility settings are removed if the language is deleted. - */ - public function testLanguageBlockVisibilityLanguageDelete() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[fr]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Check that we have an entry in the database after saving the setting. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); - - // Delete the language. - $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); - - // Check that the setting related to this language has been deleted. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); - } - - /** - * Tests if the visibility settings are removed if the block is deleted. - */ - public function testLanguageBlockVisibilityBlockDelete() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[fr]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Check that we have an entry in the database after saving the setting. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); - - // Delete the custom block. - $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete')); - - // Check that the setting related to this block has been deleted. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); - } -} diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc index 80a0770..265b381 100644 --- a/core/modules/book/book.admin.inc +++ b/core/modules/book/book.admin.inc @@ -5,8 +5,6 @@ * Admin page callbacks for the book module. */ -use Drupal\node\Node; - /** * Page callback: Returns an administrative overview of all books. * @@ -68,7 +66,7 @@ function book_admin_settings_validate($form, &$form_state) { /** * Form constructor for administering a single book's hierarchy. * - * @param Drupal\node\Node $node + * @param $node * The node of the top-level page in the book. * * @see book_menu() @@ -76,7 +74,7 @@ function book_admin_settings_validate($form, &$form_state) { * @see book_admin_edit_submit() * @ingroup forms */ -function book_admin_edit($form, $form_state, Node $node) { +function book_admin_edit($form, $form_state, $node) { drupal_set_title($node->title); $form['#node'] = $node; _book_admin_table($node, $form); @@ -137,7 +135,7 @@ function book_admin_edit_submit($form, &$form_state) { $node->revision = 1; $node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title'])); - $node->save(); + node_save($node); watchdog('content', 'book: updated %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid)); } } @@ -149,14 +147,14 @@ function book_admin_edit_submit($form, &$form_state) { /** * Builds the table portion of the form for the book administration page. * - * @param Drupal\node\Node $node + * @param $node * The node of the top-level page in the book. * @param $form * The form that is being modified. * * @see book_admin_edit() */ -function _book_admin_table(Node $node, &$form) { +function _book_admin_table($node, &$form) { $form['table'] = array( '#theme' => 'book_admin_table', '#tree' => TRUE, diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 44de175..a29a331 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -5,8 +5,6 @@ * Allows users to create and organize related content in an outline. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -88,12 +86,12 @@ function book_permission() { /** * Adds relevant book links to the node's links. * - * @param Drupal\node\Node $node + * @param $node * The book page node to add links to. * @param $view_mode * The view mode of the node. */ -function book_node_view_link(Node $node, $view_mode) { +function book_node_view_link($node, $view_mode) { $links = array(); if (isset($node->book['depth'])) { @@ -203,24 +201,24 @@ function book_menu() { * - admin/content/book/%node * - node/%node/outline * - * @param Drupal\node\Node $node + * @param $node * The node whose outline tab is to be viewed. * * @see book_menu() */ -function _book_outline_access(Node $node) { +function _book_outline_access($node) { return user_access('administer book outlines') && node_access('view', $node); } /** * Access callback: Determines if the user can remove nodes from the outline. * - * @param Drupal\node\Node $node + * @param $node * The node to remove from the outline. * * @see book_menu() */ -function _book_outline_remove_access(Node $node) { +function _book_outline_remove_access($node) { return _book_node_is_removable($node) && _book_outline_access($node); } @@ -522,10 +520,10 @@ function _book_parent_select($book_link) { /** * Builds the common elements of the book form for the node and outline forms. * - * @param Drupal\node\Node $node + * @param $node * The node whose form is being viewed. */ -function _book_add_form_elements(&$form, &$form_state, Node $node) { +function _book_add_form_elements(&$form, &$form_state, $node) { // If the form is being processed during the Ajax callback of our book bid // dropdown, then $form_state will hold the value that was selected. if (isset($form_state['values']['book'])) { @@ -627,13 +625,13 @@ function book_form_update($form, $form_state) { * outline through node addition, node editing, node deletion, or the outline * tab. * - * @param Drupal\node\Node $node + * @param $node * The node that is being saved, added, deleted, or moved. * * @return * TRUE if the menu link was saved; FALSE otherwise. */ -function _book_update_outline(Node $node) { +function _book_update_outline($node) { if (empty($node->book['bid'])) { return FALSE; } @@ -891,7 +889,7 @@ function book_node_load($nodes, $types) { /** * Implements hook_node_view(). */ -function book_node_view(Node $node, $view_mode) { +function book_node_view($node, $view_mode) { if ($view_mode == 'full') { if (!empty($node->book['bid']) && empty($node->in_preview)) { $node->content['book_navigation'] = array( @@ -923,10 +921,14 @@ function book_page_alter(&$page) { /** * Implements hook_node_presave(). */ -function book_node_presave(Node $node) { +function book_node_presave($node) { // Always save a revision for non-administrators. if (!empty($node->book['bid']) && !user_access('administer nodes')) { $node->revision = 1; + // The database schema requires a log message for every revision. + if (!isset($node->log)) { + $node->log = ''; + } } // Make sure a new node gets a new menu link. if (empty($node->nid)) { @@ -937,7 +939,7 @@ function book_node_presave(Node $node) { /** * Implements hook_node_insert(). */ -function book_node_insert(Node $node) { +function book_node_insert($node) { if (!empty($node->book['bid'])) { if ($node->book['bid'] == 'new') { // New nodes that are their own book. @@ -952,7 +954,7 @@ function book_node_insert(Node $node) { /** * Implements hook_node_update(). */ -function book_node_update(Node $node) { +function book_node_update($node) { if (!empty($node->book['bid'])) { if ($node->book['bid'] == 'new') { // New nodes that are their own book. @@ -967,7 +969,7 @@ function book_node_update(Node $node) { /** * Implements hook_node_predelete(). */ -function book_node_predelete(Node $node) { +function book_node_predelete($node) { if (!empty($node->book['bid'])) { if ($node->nid == $node->book['bid']) { // Handle deletion of a top-level post. @@ -991,7 +993,7 @@ function book_node_predelete(Node $node) { /** * Implements hook_node_prepare(). */ -function book_node_prepare(Node $node) { +function book_node_prepare($node) { // Prepare defaults for the add/edit form. if (empty($node->book) && (user_access('add content to books') || user_access('administer book outlines'))) { $node->book = array(); @@ -1289,7 +1291,7 @@ function book_export_traverse($tree, $visit_func) { /** * Generates printer-friendly HTML for a node. * - * @param Drupal\node\Node $node + * @param $node * The node that will be output. * @param $children * All the rendered child nodes within the current node. @@ -1299,7 +1301,7 @@ function book_export_traverse($tree, $visit_func) { * * @see book_export_traverse() */ -function book_node_export(Node $node, $children = '') { +function book_node_export($node, $children = '') { $build = node_view($node, 'print'); unset($build['#theme']); // @todo Rendering should happen in the template using render(). diff --git a/core/modules/book/book.pages.inc b/core/modules/book/book.pages.inc index aedde16..042bcf5 100644 --- a/core/modules/book/book.pages.inc +++ b/core/modules/book/book.pages.inc @@ -5,8 +5,6 @@ * User page callbacks for the book module. */ -use Drupal\node\Node; - /** * Page callback: Prints a listing of all books. * @@ -95,12 +93,12 @@ function book_export_html($nid) { /** * Page callback: Shows the outline form for a single node. * - * @param Drupal\node\Node $node + * @param $node * The book node for which to show the outline. * * @see book_menu() */ -function book_outline(Node $node) { +function book_outline($node) { drupal_set_title($node->title); return drupal_get_form('book_outline_form', $node); } @@ -110,14 +108,14 @@ function book_outline(Node $node) { * * Allows handling of all book outline operations via the outline tab. * - * @param Drupal\node\Node $node + * @param $node * The book node for which to show the outline. * * @see book_outline_form_submit() * @see book_remove_button_submit() * @ingroup forms */ -function book_outline_form($form, &$form_state, Node $node) { +function book_outline_form($form, &$form_state, $node) { if (!isset($node->book)) { // The node is not part of any book yet - set default options. $node->book = _book_link_defaults($node->nid); @@ -199,14 +197,14 @@ function book_outline_form_submit($form, &$form_state) { /** * Form constructor to confirm removal of a node from a book. * - * @param Drupal\node\Node $node + * @param $node * The node to delete. * * @see book_remove_form_submit() * @see book_menu() * @ingroup forms */ -function book_remove_form($form, &$form_state, Node $node) { +function book_remove_form($form, &$form_state, $node) { $form['#node'] = $node; $title = array('%title' => $node->title); diff --git a/core/modules/book/book.test b/core/modules/book/book.test index f7d8280..54444b2 100644 --- a/core/modules/book/book.test +++ b/core/modules/book/book.test @@ -5,8 +5,6 @@ * Tests for book.module. */ -use Drupal\node\Node; - class BookTestCase extends DrupalWebTestCase { protected $book; // $book_author is a user with permission to create and edit books. @@ -110,7 +108,7 @@ class BookTestCase extends DrupalWebTestCase { /** * Check the outline of sub-pages; previous, up, and next; and printer friendly version. * - * @param Node $node + * @param $node * Node to check. * @param $nodes * Nodes that should be in outline. @@ -123,7 +121,7 @@ class BookTestCase extends DrupalWebTestCase { * @param $breadcrumb * The nodes that should be displayed in the breadcrumb. */ - function checkBookNode(Node $node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) { + function checkBookNode($node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) { // $number does not use drupal_static as it should not be reset // since it uniquely identifies each call to checkBookNode(). static $number = 0; diff --git a/core/modules/comment/comment-wrapper.tpl.php b/core/modules/comment/comment-wrapper.tpl.php index 1f97851..5e58a67 100644 --- a/core/modules/comment/comment-wrapper.tpl.php +++ b/core/modules/comment/comment-wrapper.tpl.php @@ -20,7 +20,7 @@ * the template. * * The following variables are provided for contextual information. - * - $node: Node entity the comments are attached to. + * - $node: Node object the comments are attached to. * The constants below the variables show the possible values and should be * used for comparison. * - $display_mode diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index d84b785..9758075 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -258,7 +258,7 @@ function comment_confirm_delete_page($cid) { if ($comment = comment_load($cid)) { return drupal_get_form('comment_confirm_delete', $comment); } - return MENU_NOT_FOUND; + drupal_not_found(); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 0302913..2ba86cc 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -9,8 +9,6 @@ * book page, etc. */ -use Drupal\node\Node; - /** * Comment is awaiting approval. */ @@ -277,7 +275,8 @@ function comment_menu() { 'title' => 'Approve', 'page callback' => 'comment_approve', 'page arguments' => array(1), - 'access arguments' => array('administer comments'), + 'access callback' => 'comment_approve_access', + 'access arguments' => array(1), 'file' => 'comment.pages.inc', 'weight' => 1, ); @@ -500,12 +499,10 @@ function comment_permalink($cid) { // Find the current display page for this comment. $page = comment_get_display_page($comment->cid, $node->type); - // Set $_GET['q'] and $_GET['page'] ourselves so that the node callback - // behaves as it would when visiting the page directly. - $_GET['q'] = 'node/' . $node->nid; - $_GET['page'] = $page; - // Return the node view, this will show the correct comment in context. + // @todo Refactor to use Symfony's Request object. + _current_path('node/' . $node->nid); + $_GET['page'] = $page; return menu_execute_active_handler('node/' . $node->nid, FALSE); } drupal_not_found(); @@ -547,13 +544,13 @@ function comment_get_recent($number = 10) { * Number of comments. * @param $new_replies * Number of new replies. - * @param Drupal\node\Node $node + * @param $node * The first new comment node. * * @return * "page=X" if the page number is greater than zero; empty string otherwise. */ -function comment_new_page_count($num_comments, $new_replies, Node $node) { +function comment_new_page_count($num_comments, $new_replies, $node) { $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); $pagenum = NULL; @@ -631,7 +628,7 @@ function theme_comment_block() { /** * Implements hook_node_view(). */ -function comment_node_view(Node $node, $view_mode) { +function comment_node_view($node, $view_mode) { $links = array(); if ($node->comment != COMMENT_NODE_HIDDEN) { @@ -739,14 +736,14 @@ function comment_node_view(Node $node, $view_mode) { /** * Builds the comment-related elements for node detail pages. * - * @param Drupal\node\Node $node - * The node entity for which to build the comment-related elements. + * @param $node + * The node object for which to build the comment-related elements. * * @return * A renderable array representing the comment-related page elements for the * node. */ -function comment_node_page_additions(Node $node) { +function comment_node_page_additions($node) { $additions = array(); // Only attempt to render comments if the node has visible comments. @@ -785,7 +782,7 @@ function comment_node_page_additions(Node $node) { /** * Retrieves comments for a thread. * - * @param Drupal\node\Node $node + * @param $node * The node whose comment(s) needs rendering. * @param $mode * The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED. @@ -849,7 +846,7 @@ function comment_node_page_additions(Node $node) { * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need * to consider the trailing "/" so we use a substring only. */ -function comment_get_thread(Node $node, $mode, $comments_per_page) { +function comment_get_thread($node, $mode, $comments_per_page) { $query = db_select('comment', 'c')->extend('PagerDefault'); $query->addField('c', 'cid'); $query @@ -938,7 +935,7 @@ function comment_prepare_thread(&$comments) { * * @param Comment $comment * The comment object. - * @param Drupal\node\Node $node + * @param $node * The node the comment is attached to. * @param $view_mode * View mode, e.g. 'full', 'teaser'... @@ -949,7 +946,7 @@ function comment_prepare_thread(&$comments) { * @return * An array as expected by drupal_render(). */ -function comment_view(Comment $comment, Node $node, $view_mode = 'full', $langcode = NULL) { +function comment_view(Comment $comment, $node, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->langcode; } @@ -1008,7 +1005,7 @@ function comment_view(Comment $comment, Node $node, $view_mode = 'full', $langco * * @param Comment $comment * A comment object. - * @param Drupal\node\Node $node + * @param $node * The node the comment is attached to. * @param $view_mode * View mode, e.g. 'full', 'teaser'... @@ -1016,7 +1013,7 @@ function comment_view(Comment $comment, Node $node, $view_mode = 'full', $langco * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. */ -function comment_build_content(Comment $comment, Node $node, $view_mode = 'full', $langcode = NULL) { +function comment_build_content(Comment $comment, $node, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->langcode; } @@ -1052,13 +1049,13 @@ function comment_build_content(Comment $comment, Node $node, $view_mode = 'full' * * @param Comment $comment * The comment object. - * @param Drupal\node\Node $node + * @param $node * The node the comment is attached to. * * @return * A structured array of links. */ -function comment_links(Comment $comment, Node $node) { +function comment_links(Comment $comment, $node) { $links = array(); if ($node->comment == COMMENT_NODE_OPEN) { if (user_access('administer comments') && user_access('post comments')) { @@ -1113,7 +1110,7 @@ function comment_links(Comment $comment, Node $node) { * * @param $comments * An array of comments as returned by comment_load_multiple(). - * @param Drupal\node\Node $node + * @param $node * The node the comments are attached to. * @param $view_mode * View mode, e.g. 'full', 'teaser'... @@ -1128,7 +1125,7 @@ function comment_links(Comment $comment, Node $node) { * * @see drupal_render() */ -function comment_view_multiple($comments, Node $node, $view_mode = 'full', $weight = 0, $langcode = NULL) { +function comment_view_multiple($comments, $node, $view_mode = 'full', $weight = 0, $langcode = NULL) { field_attach_prepare_view('comment', $comments, $view_mode, $langcode); entity_prepare_view('comment', $comments, $langcode); @@ -1310,7 +1307,7 @@ function comment_node_load($nodes, $types) { /** * Implements hook_node_prepare(). */ -function comment_node_prepare(Node $node) { +function comment_node_prepare($node) { if (!isset($node->comment)) { $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN); } @@ -1319,7 +1316,7 @@ function comment_node_prepare(Node $node) { /** * Implements hook_node_insert(). */ -function comment_node_insert(Node $node) { +function comment_node_insert($node) { // Allow bulk updates and inserts to temporarily disable the // maintenance of the {node_comment_statistics} table. if (variable_get('comment_maintain_node_statistics', TRUE)) { @@ -1339,7 +1336,7 @@ function comment_node_insert(Node $node) { /** * Implements hook_node_predelete(). */ -function comment_node_predelete(Node $node) { +function comment_node_predelete($node) { $cids = db_query('SELECT cid FROM {comment} WHERE nid = :nid', array(':nid' => $node->nid))->fetchCol(); comment_delete_multiple($cids); db_delete('node_comment_statistics') @@ -1350,7 +1347,7 @@ function comment_node_predelete(Node $node) { /** * Implements hook_node_update_index(). */ -function comment_node_update_index(Node $node) { +function comment_node_update_index($node) { $index_comments = &drupal_static(__FUNCTION__); if ($index_comments === NULL) { @@ -1399,7 +1396,7 @@ function comment_update_index() { * Formats a comment count string and returns it, for display with search * results. */ -function comment_node_search_result(Node $node) { +function comment_node_search_result($node) { // Do not make a string if comments are hidden. if (user_access('access comments') && $node->comment != COMMENT_NODE_HIDDEN) { $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField(); @@ -1502,19 +1499,19 @@ function comment_delete_multiple($cids) { /** * Loads comments from the database. * - * @param array|bool $cids - * An array of comment IDs, or FALSE to load all comments. - * @param array $conditions + * @param $cids + * An array of comment IDs. + * @param $conditions * (deprecated) An associative array of conditions on the {comments} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. - * @param bool $reset + * @param $reset * Whether to reset the internal static entity cache. Note that the static * cache is disabled in comment_entity_info() by default. * - * @return array + * @return * An array of comment objects, indexed by comment ID. * * @todo Remove $conditions in Drupal 8. @@ -1522,16 +1519,16 @@ function comment_delete_multiple($cids) { * @see entity_load() * @see EntityFieldQuery */ -function comment_load_multiple($cids = array(), array $conditions = array(), $reset = FALSE) { - return entity_load_multiple('comment', $cids, $conditions, $reset); +function comment_load_multiple($cids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('comment', $cids, $conditions, $reset); } /** * Loads the entire comment by comment ID. * - * @param int $cid + * @param $cid * The ID of the comment to be loaded. - * @param bool $reset + * @param $reset * Whether to reset the internal static entity cache. Note that the static * cache is disabled in comment_entity_info() by default. * @@ -1539,7 +1536,8 @@ function comment_load_multiple($cids = array(), array $conditions = array(), $re * The comment object. */ function comment_load($cid, $reset = FALSE) { - return entity_load('comment', $cid); + $comment = comment_load_multiple(array($cid), array(), $reset); + return $comment ? $comment[$cid] : FALSE; } /** @@ -2516,3 +2514,23 @@ function comment_file_download_access($field, $entity_type, $entity) { return FALSE; } } + +/** + * Access callback: Determines if comment approval is accessible. + * + * @param $cid + * A comment identifier. + * + * @see comment_approve() + * @see comment_menu() + */ +function comment_approve_access($cid) { + if (!user_access('administer comments')) { + return FALSE; + } + $token = request()->query->get('token'); + if (!isset($token) || !drupal_valid_token($token, "comment/$cid/approve")) { + return FALSE; + } + return TRUE; +} diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index bac078b..02020ff 100644 --- a/core/modules/comment/comment.pages.inc +++ b/core/modules/comment/comment.pages.inc @@ -5,8 +5,6 @@ * User page callbacks for the Comment module. */ -use Drupal\node\Node; - /** * Form constructor for the comment reply form. * @@ -19,7 +17,7 @@ use Drupal\node\Node; * The node or comment that is being replied to must appear above the comment * form to provide the user context while authoring the comment. * - * @param Drupal\node\Node $node + * @param $node * Every comment belongs to a node. This is that node. * @param $pid * (optional) Some comments are replies to other comments. In those cases, @@ -28,7 +26,7 @@ use Drupal\node\Node; * @return * The rendered parent node or comment plus the new comment form. */ -function comment_reply(Node $node, $pid = NULL) { +function comment_reply($node, $pid = NULL) { // Set the breadcrumb trail. drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, 'node/' . $node->nid))); $op = isset($_POST['op']) ? $_POST['op'] : ''; @@ -105,11 +103,9 @@ function comment_reply(Node $node, $pid = NULL) { * A comment identifier. * * @see comment_menu() + * @see comment_approve_access() */ function comment_approve($cid) { - if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) { - return MENU_ACCESS_DENIED; - } if ($comment = comment_load($cid)) { $comment->status = COMMENT_PUBLISHED; comment_save($comment); @@ -117,5 +113,5 @@ function comment_approve($cid) { drupal_set_message(t('Comment approved.')); drupal_goto('node/' . $comment->nid); } - return MENU_NOT_FOUND; + drupal_not_found(); } diff --git a/core/modules/comment/comment.test b/core/modules/comment/comment.test index 1fc8297..259e420 100644 --- a/core/modules/comment/comment.test +++ b/core/modules/comment/comment.test @@ -23,8 +23,8 @@ class CommentHelperCase extends DrupalWebTestCase { /** * Posts a comment. * - * @param Node|NULL $node - * Node to post comment on or NULL to post to the previusly loaded page. + * @param $node + * Node to post comment on. * @param $comment * Comment body. * @param $subject diff --git a/core/modules/comment/comment.tpl.php b/core/modules/comment/comment.tpl.php index e94e6e4..6a17387 100644 --- a/core/modules/comment/comment.tpl.php +++ b/core/modules/comment/comment.tpl.php @@ -46,7 +46,7 @@ * * These two variables are provided for context: * - $comment: Full comment object. - * - $node: Node entity the comments are attached to. + * - $node: Node object the comments are attached to. * * Other variables: * - $classes_array: Array of html class attribute values. It is flattened diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index c695c7e..3d32962 100644 --- a/core/modules/contact/contact.module +++ b/core/modules/contact/contact.module @@ -173,7 +173,7 @@ function contact_mail($key, &$message, $params) { '!site-name' => variable_get('site_name', 'Drupal'), '!subject' => $params['subject'], '!category' => isset($params['category']['category']) ? $params['category']['category'] : '', - '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), + '!form-url' => url(current_path(), array('absolute' => TRUE, 'language' => $language)), '!sender-name' => user_format_name($params['sender']), '!sender-url' => $params['sender']->uid ? url('user/' . $params['sender']->uid, array('absolute' => TRUE, 'language' => $language)) : $params['sender']->mail, ); @@ -233,8 +233,8 @@ function contact_form_user_profile_form_alter(&$form, &$form_state) { /** * Implements hook_user_presave(). */ -function contact_user_presave($account) { - $account->data['contact'] = isset($account->contact) ? $account->contact : variable_get('contact_default_status', 1); +function contact_user_presave(&$edit, $account) { + $edit['data']['contact'] = isset($edit['contact']) ? $edit['contact'] : variable_get('contact_default_status', 1); } /** diff --git a/core/modules/contact/contact.test b/core/modules/contact/contact.test index 490d8f8..d7f26ac 100644 --- a/core/modules/contact/contact.test +++ b/core/modules/contact/contact.test @@ -374,8 +374,7 @@ class ContactPersonalTestCase extends DrupalWebTestCase { // Re-create our contacted user as a blocked user. $this->contact_user = $this->drupalCreateUser(); - $this->contact_user->status = 0; - $this->contact_user->save(); + user_save($this->contact_user, array('status' => 0)); // Test that blocked users can still be contacted by admin. $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php index 0f8f7b8..e983778 100644 --- a/core/modules/entity/entity.api.php +++ b/core/modules/entity/entity.api.php @@ -123,7 +123,6 @@ * display settings specific to the view mode. * * @see entity_load() - * @see entity_load_multiple() * @see hook_entity_info_alter() */ function hook_entity_info() { @@ -428,7 +427,7 @@ function hook_entity_view_alter(&$build, $type) { * Act on entities as they are being prepared for view. * * Allows you to operate on multiple entities as they are being prepared for - * view. Only use this if attaching the data during the entity loading phase + * view. Only use this if attaching the data during the entity_load() phase * is not appropriate, for example when attaching other 'entity' style objects. * * @param $entities diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc index 9a00718..9136c58 100644 --- a/core/modules/entity/entity.class.inc +++ b/core/modules/entity/entity.class.inc @@ -414,7 +414,7 @@ class Entity implements EntityInterface { */ public function createDuplicate() { $duplicate = clone $this; - $duplicate->id = NULL; + $duplicate->{$this->idKey} = NULL; return $duplicate; } diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module index b4e5b9b..9b2b6f2 100644 --- a/core/modules/entity/entity.module +++ b/core/modules/entity/entity.module @@ -191,31 +191,7 @@ function entity_create_stub_entity($entity_type, $ids) { } /** - * Loads an entity from the database. - * - * @param string $entity_type - * The entity type to load, e.g. node or user. - * @param int $id - * The id of the entity to load. - * @param bool $reset - * Whether to reset the internal cache for the requested entity type. - * - * @return object - * The entity object, or FALSE if there is no entity with the given id. - * - * @see hook_entity_info() - * @see entity_load_multiple() - * @see DrupalEntityControllerInterface - * @see DrupalDefaultEntityController - * @see EntityFieldQuery - */ -function entity_load($entity_type, $id, $reset = FALSE) { - $entities = entity_load_multiple($entity_type, array($id), array(), $reset); - return isset($entities[$id]) ? $entities[$id] : FALSE; -} - -/** - * Loads multiple entities from the database. + * Loads entities from the database. * * This function should be used whenever you need to load more than one entity * from the database. The entities are loaded into memory and will not require @@ -230,19 +206,19 @@ function entity_load($entity_type, $id, $reset = FALSE) { * DrupalDefaultEntityController class. See node_entity_info() and the * NodeController in node.module as an example. * - * @param string $entity_type + * @param $entity_type * The entity type to load, e.g. node or user. - * @param array|bool $ids + * @param $ids * An array of entity IDs, or FALSE to load all entities. - * @param array $conditions + * @param $conditions * (deprecated) An associative array of conditions on the base table, where * the keys are the database fields and the values are the values those * fields must have. Instead, it is preferable to use EntityFieldQuery to * retrieve a list of entity IDs loadable by this function. - * @param bool $reset + * @param $reset * Whether to reset the internal cache for the requested entity type. * - * @return array + * @return * An array of entity objects indexed by their ids. * * @todo Remove $conditions in Drupal 8. @@ -252,7 +228,7 @@ function entity_load($entity_type, $id, $reset = FALSE) { * @see DrupalDefaultEntityController * @see EntityFieldQuery */ -function entity_load_multiple($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) { +function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) { if ($reset) { entity_get_controller($entity_type)->resetCache(); } diff --git a/core/modules/entity/entity.query.inc b/core/modules/entity/entity.query.inc index 6d312ea..1eee32d 100644 --- a/core/modules/entity/entity.query.inc +++ b/core/modules/entity/entity.query.inc @@ -733,7 +733,7 @@ class EntityFieldQuery { * @code * $result = $query->execute(); * if (!empty($result[$my_type])) { - * $entities = entity_load_multiple($my_type, array_keys($result[$my_type])); + * $entities = entity_load($my_type, array_keys($result[$my_type])); * } * @endcode */ diff --git a/core/modules/entity/tests/entity.test b/core/modules/entity/tests/entity.test index cd9b879..57600a3 100644 --- a/core/modules/entity/tests/entity.test +++ b/core/modules/entity/tests/entity.test @@ -103,9 +103,10 @@ class EntityTranslationTestCase extends DrupalWebTestCase { } function setUp() { + // Enable translations for the test entity type. We cannot use + // variable_set() here as variables are cleared by parent::setUp(); + $GLOBALS['entity_test_translation'] = TRUE; parent::setUp('entity_test', 'language', 'locale'); - // Enable translations for the test entity type. - variable_set('entity_test_translation', TRUE); // Create a translatable test field. $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test index 33dbb2e..be59e99 100644 --- a/core/modules/entity/tests/entity_crud_hook_test.test +++ b/core/modules/entity/tests/entity_crud_hook_test.test @@ -62,7 +62,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { * Tests hook invocations for CRUD operations on comments. */ public function testCommentHooks() { - $node = entity_create('node', array( + $node = (object) array( 'uid' => 1, 'type' => 'article', 'title' => 'Test node', @@ -70,11 +70,11 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { 'comment' => 2, 'promote' => 0, 'sticky' => 0, - 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'language' => LANGUAGE_NOT_SPECIFIED, 'created' => REQUEST_TIME, 'changed' => REQUEST_TIME, - )); - $node->save(); + ); + node_save($node); $nid = $node->nid; $comment = entity_create('comment', array( @@ -86,7 +86,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { 'created' => REQUEST_TIME, 'changed' => REQUEST_TIME, 'status' => 1, - 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'language' => LANGUAGE_NOT_SPECIFIED, )); $_SESSION['entity_crud_hook_test'] = array(); @@ -189,7 +189,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { * Tests hook invocations for CRUD operations on nodes. */ public function testNodeHooks() { - $node = entity_create('node', array( + $node = (object) array( 'uid' => 1, 'type' => 'article', 'title' => 'Test node', @@ -197,12 +197,12 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { 'comment' => 2, 'promote' => 0, 'sticky' => 0, - 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'language' => LANGUAGE_NOT_SPECIFIED, 'created' => REQUEST_TIME, 'changed' => REQUEST_TIME, - )); + ); $_SESSION['entity_crud_hook_test'] = array(); - $node->save(); + node_save($node); $this->assertHookMessageOrder(array( 'entity_crud_hook_test_node_presave called', @@ -221,7 +221,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { $_SESSION['entity_crud_hook_test'] = array(); $node->title = 'New title'; - $node->save(); + node_save($node); $this->assertHookMessageOrder(array( 'entity_crud_hook_test_node_presave called', @@ -356,15 +356,16 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { * Tests hook invocations for CRUD operations on users. */ public function testUserHooks() { - $account = entity_create('user', array( + $edit = array( 'name' => 'Test user', 'mail' => 'test@example.com', 'created' => REQUEST_TIME, 'status' => 1, 'language' => 'en', - )); + ); + $account = (object) $edit; $_SESSION['entity_crud_hook_test'] = array(); - $account->save(); + $account = user_save($account, $edit); $this->assertHookMessageOrder(array( 'entity_crud_hook_test_user_presave called', @@ -374,7 +375,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { )); $_SESSION['entity_crud_hook_test'] = array(); - user_load($account->uid); + $account = user_load($account->uid); $this->assertHookMessageOrder(array( 'entity_crud_hook_test_entity_load called for type user', @@ -382,8 +383,8 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { )); $_SESSION['entity_crud_hook_test'] = array(); - $account->name = 'New name'; - $account->save(); + $edit['name'] = 'New name'; + $account = user_save($account, $edit); $this->assertHookMessageOrder(array( 'entity_crud_hook_test_user_presave called', diff --git a/core/modules/entity/tests/modules/entity_test/entity_test.module b/core/modules/entity/tests/modules/entity_test/entity_test.module index f6d2e3a..f7dffa0 100644 --- a/core/modules/entity/tests/modules/entity_test/entity_test.module +++ b/core/modules/entity/tests/modules/entity_test/entity_test.module @@ -20,7 +20,7 @@ function entity_test_entity_info() { ), ); // Optionally specify a translation handler for testing translations. - if (variable_get('entity_test_translation')) { + if (!empty($GLOBALS['entity_test_translation'])) { $items['entity_test']['translation']['entity_test'] = TRUE; } return $items; @@ -29,33 +29,34 @@ function entity_test_entity_info() { /** * Loads a test entity. * - * @param int $id + * @param $id * A test entity ID. - * @param bool $reset + * @param $reset * A boolean indicating that the internal cache should be reset. * * @return Entity * The loaded entity object, or FALSE if the entity cannot be loaded. */ function entity_test_load($id, $reset = FALSE) { - return entity_load('entity_test', $id, $reset); + $result = entity_load('entity_test', array($id), array(), $reset); + return reset($result); } /** * Loads multiple test entities based on certain conditions. * - * @param array|bool $ids - * An array of entity IDs, or FALSE to load all entities. - * @param array $conditions + * @param $ids + * An array of entity IDs. + * @param $conditions * An array of conditions to match against the {entity} table. - * @param bool $reset + * @param $reset * A boolean indicating that the internal cache should be reset. * - * @return array + * @return * An array of test entity objects, indexed by ID. */ function entity_test_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) { - return entity_load_multiple('entity_test', $ids, $conditions, $reset); + return entity_load('entity_test', $ids, $conditions, $reset); } /** diff --git a/core/modules/field/field.form.inc b/core/modules/field/field.form.inc index 2ced5b0..8faa22b 100644 --- a/core/modules/field/field.form.inc +++ b/core/modules/field/field.form.inc @@ -53,8 +53,6 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, // If field module handles multiple values for this form element, and we are // displaying an individual element, process the multiple value form. if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - // Store the entity in the form. - $form['#entity'] = $entity; $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); } // If the widget is handling multiple values (e.g Options), or if we are @@ -66,7 +64,6 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, if (function_exists($function)) { $element = array( '#entity_type' => $instance['entity_type'], - '#entity' => $entity, '#bundle' => $instance['bundle'], '#field_name' => $field_name, '#language' => $langcode, @@ -176,7 +173,6 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form, $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $element = array( '#entity_type' => $instance['entity_type'], - '#entity' => $form['#entity'], '#bundle' => $instance['bundle'], '#field_name' => $field_name, '#language' => $langcode, diff --git a/core/modules/field/modules/list/list.module b/core/modules/field/modules/list/list.module index f0b1b80..c2bff2a 100644 --- a/core/modules/field/modules/list/list.module +++ b/core/modules/field/modules/list/list.module @@ -221,39 +221,24 @@ function list_field_update_field($field, $prior_field, $has_data) { * * @param $field * The field definition. - * @param $instance - * (optional) A field instance array. Defaults to NULL. - * @param $entity_type - * (optional) The type of entity; e.g. 'node' or 'user'. Defaults to NULL. - * @param $entity - * (optional) The entity object. Defaults to NULL. * * @return * The array of allowed values. Keys of the array are the raw stored values * (number or text), values of the array are the display labels. */ -function list_allowed_values($field, $instance = NULL, $entity_type = NULL, $entity = NULL) { +function list_allowed_values($field) { $allowed_values = &drupal_static(__FUNCTION__, array()); if (!isset($allowed_values[$field['id']])) { $function = $field['settings']['allowed_values_function']; - // If $cacheable is FALSE, then the allowed values are not statically - // cached. See list_test_dynamic_values_callback() for an example of - // generating dynamic and uncached values. - $cacheable = TRUE; if (!empty($function)) { - $values = $function($field, $instance, $entity_type, $entity, $cacheable); + $values = $function($field); } else { $values = $field['settings']['allowed_values']; } - if ($cacheable) { - $allowed_values[$field['id']] = $values; - } - else { - return $values; - } + $allowed_values[$field['id']] = $values; } return $allowed_values[$field['id']]; @@ -388,7 +373,7 @@ function _list_values_in_use($field, $values) { * - 'list_illegal_value': The value is not part of the list of allowed values. */ function list_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity); + $allowed_values = list_allowed_values($field); foreach ($items as $delta => $item) { if (!empty($item['value'])) { if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) { @@ -434,8 +419,8 @@ function list_field_widget_info_alter(&$info) { /** * Implements hook_options_list(). */ -function list_options_list($field, $instance, $entity_type, $entity) { - return list_allowed_values($field, $instance, $entity_type, $entity); +function list_options_list($field, $instance) { + return list_allowed_values($field); } /** @@ -462,7 +447,7 @@ function list_field_formatter_view($entity_type, $entity, $field, $instance, $la switch ($display['type']) { case 'list_default': - $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity); + $allowed_values = list_allowed_values($field); foreach ($items as $delta => $item) { if (isset($allowed_values[$item['value']])) { $output = field_filter_xss($allowed_values[$item['value']]); diff --git a/core/modules/field/modules/list/tests/list.test b/core/modules/field/modules/list/tests/list.test index f441f4d..988f6a3 100644 --- a/core/modules/field/modules/list/tests/list.test +++ b/core/modules/field/modules/list/tests/list.test @@ -114,89 +114,6 @@ class ListFieldTestCase extends FieldTestCase { } /** - * Sets up a List field for testing allowed values functions. - */ -class ListDynamicValuesTestCase extends FieldTestCase { - function setUp() { - parent::setUp(array('list', 'field_test', 'list_test')); - - $this->field_name = 'test_list'; - $this->field = array( - 'field_name' => $this->field_name, - 'type' => 'list_text', - 'cardinality' => 1, - 'settings' => array( - 'allowed_values_function' => 'list_test_dynamic_values_callback', - ), - ); - $this->field = field_create_field($this->field); - - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'required' => TRUE, - 'widget' => array( - 'type' => 'options_select', - ), - ); - $this->instance = field_create_instance($this->instance); - $this->test = array( - 'id' => mt_rand(1, 10), - // Make sure this does not equal the ID so that - // list_test_dynamic_values_callback() always returns 4 values. - 'vid' => mt_rand(20, 30), - 'bundle' => 'test_bundle', - 'label' => $this->randomName(), - ); - $this->entity = call_user_func_array('field_test_create_stub_entity', $this->test); - } -} - -/** - * Tests the List field allowed values function. - */ -class ListDynamicValuesValidationTestCase extends ListDynamicValuesTestCase { - public static function getInfo() { - return array( - 'name' => 'List field dynamic values', - 'description' => 'Test the List field allowed values function.', - 'group' => 'Field types', - ); - } - - /** - * Test that allowed values function gets the entity. - */ - function testDynamicAllowedValues() { - // Verify that the test passes against every value we had. - foreach ($this->test as $key => $value) { - $this->entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = $value; - try { - field_attach_validate('test_entity', $this->entity); - $this->pass("$key should pass"); - } - catch (FieldValidationException $e) { - // This will display as an exception, no need for a separate error. - throw($e); - } - } - // Now verify that the test does not pass against anything else. - foreach ($this->test as $key => $value) { - $this->entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = is_numeric($value) ? (100 - $value) : ('X' . $value); - $pass = FALSE; - try { - field_attach_validate('test_entity', $this->entity); - } - catch (FieldValidationException $e) { - $pass = TRUE; - } - $this->assertTrue($pass, $key . ' should not pass'); - } - } -} - -/** * List module UI tests. */ class ListFieldUITestCase extends FieldTestCase { diff --git a/core/modules/field/modules/list/tests/list_test.module b/core/modules/field/modules/list/tests/list_test.module index aa53337..8d53404 100644 --- a/core/modules/field/modules/list/tests/list_test.module +++ b/core/modules/field/modules/list/tests/list_test.module @@ -21,12 +21,3 @@ function list_test_allowed_values_callback($field) { return $values; } - -/** - * An entity-bound allowed values callback. - */ -function list_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) { - $cacheable = FALSE; - // We need the values of the entity as keys. - return drupal_map_assoc(array_merge(array($entity->ftlabel), entity_extract_ids($entity_type, $entity))); -} diff --git a/core/modules/field/modules/options/options.api.php b/core/modules/field/modules/options/options.api.php index ce2ec62..d1ac0db 100644 --- a/core/modules/field/modules/options/options.api.php +++ b/core/modules/field/modules/options/options.api.php @@ -19,11 +19,6 @@ * The instance definition. It is recommended to only use instance level * properties to filter out values from a list defined by field level * properties. - * @param $entity_type - * The entity type the field is attached to. - * @param $entity - * The entity object the field is attached to, or NULL if no entity - * exists (e.g. in field settings page). * * @return * The array of options for the field. Array keys are the values to be @@ -34,7 +29,7 @@ * widget. The HTML tags defined in _field_filter_xss_allowed_tags() are * allowed, other tags will be filtered. */ -function hook_options_list($field, $instance, $entity_type, $entity) { +function hook_options_list($field, $instance) { // Sample structure. $options = array( 0 => t('Zero'), diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module index 3862ba7..04b88d8 100644 --- a/core/modules/field/modules/options/options.module +++ b/core/modules/field/modules/options/options.module @@ -79,11 +79,8 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan $has_value = isset($items[0][$value_key]); $properties = _options_properties($type, $multiple, $required, $has_value); - $entity_type = $element['#entity_type']; - $entity = $element['#entity']; - // Prepare the list of options. - $options = _options_get_options($field, $instance, $properties, $entity_type, $entity); + $options = _options_get_options($field, $instance, $properties); // Put current field values in shape. $default_value = _options_storage_to_form($items, $options, $value_key, $properties); @@ -211,7 +208,7 @@ function _options_properties($type, $multiple, $required, $has_value) { if (!$required) { $properties['empty_option'] = 'option_none'; } - elseif (!$has_value) { + else if (!$has_value) { $properties['empty_option'] = 'option_select'; } } @@ -240,9 +237,9 @@ function _options_properties($type, $multiple, $required, $has_value) { /** * Collects the options for a field. */ -function _options_get_options($field, $instance, $properties, $entity_type, $entity) { +function _options_get_options($field, $instance, $properties) { // Get the list of options. - $options = (array) module_invoke($field['module'], 'options_list', $field, $instance, $entity_type, $entity); + $options = (array) module_invoke($field['module'], 'options_list', $field, $instance); // Sanitize the options. _options_prepare_options($options, $properties); diff --git a/core/modules/field/modules/options/options.test b/core/modules/field/modules/options/options.test index be446cd..b945949 100644 --- a/core/modules/field/modules/options/options.test +++ b/core/modules/field/modules/options/options.test @@ -519,38 +519,3 @@ class OptionsWidgetsTestCase extends FieldTestCase { } } -/** - * Test an options select on a list field with a dynamic allowed values function. - */ -class OptionsSelectDynamicValuesTestCase extends ListDynamicValuesTestCase { - public static function getInfo() { - return array( - 'name' => 'Options select dynamic values', - 'description' => 'Test an options select on a list field with a dynamic allowed values function.', - 'group' => 'Field types', - ); - } - - /** - * Tests the 'options_select' widget (single select). - */ - function testSelectListDynamic() { - // Create an entity. - $this->entity->is_new = TRUE; - field_test_entity_save($this->entity); - // Create a web user. - $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($web_user); - - // Display form. - $this->drupalGet('test-entity/manage/' . $this->entity->ftid . '/edit'); - $options = $this->xpath('//select[@id="edit-test-list-und"]/option'); - $this->assertEqual(count($options), count($this->test) + 1); - foreach ($options as $option) { - $value = (string) $option['value']; - if ($value != '_none') { - $this->assertTrue(array_search($value, $this->test)); - } - } - } -} diff --git a/core/modules/field_ui/field_ui.test b/core/modules/field_ui/field_ui.test index fb71c5d..9b71064 100644 --- a/core/modules/field_ui/field_ui.test +++ b/core/modules/field_ui/field_ui.test @@ -5,8 +5,6 @@ * Tests for field_ui.module. */ -use Drupal\node\Node; - /** * Provides common functionality for the Field UI test classes. */ @@ -617,7 +615,7 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase { /** * Asserts that a string is found in the rendered node in a view mode. * - * @param Node $node + * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. @@ -629,14 +627,14 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase { * @return * TRUE on pass, FALSE on fail. */ - function assertNodeViewText(Node $node, $view_mode, $text, $message) { + function assertNodeViewText($node, $view_mode, $text, $message) { return $this->assertNodeViewTextHelper($node, $view_mode, $text, $message, FALSE); } /** * Asserts that a string is not found in the rendered node in a view mode. * - * @param Node $node + * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. @@ -647,7 +645,7 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase { * @return * TRUE on pass, FALSE on fail. */ - function assertNodeViewNoText(Node $node, $view_mode, $text, $message) { + function assertNodeViewNoText($node, $view_mode, $text, $message) { return $this->assertNodeViewTextHelper($node, $view_mode, $text, $message, TRUE); } @@ -657,7 +655,7 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase { * This helper function is used by assertNodeViewText() and * assertNodeViewNoText(). * - * @param Node $node + * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. @@ -671,7 +669,7 @@ class FieldUIManageDisplayTestCase extends FieldUITestCase { * @return * TRUE on pass, FALSE on fail. */ - function assertNodeViewTextHelper(Node $node, $view_mode, $text, $message, $not_exists) { + function assertNodeViewTextHelper($node, $view_mode, $text, $message, $not_exists) { // Make sure caches on the tester side are refreshed after changes // submitted on the tested side. field_info_cache_clear(); diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 0aef8de..a2a5a80 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -163,7 +163,8 @@ function file_file_download($uri, $field_type = 'file') { foreach ($field_references as $entity_type => $type_references) { foreach ($type_references as $id => $reference) { // Try to load $entity and $field. - $entity = entity_load($entity_type, $id); + $entity = entity_load($entity_type, array($id)); + $entity = reset($entity); $field = NULL; if ($entity) { // Load all fields for that entity. diff --git a/core/modules/file/tests/file.test b/core/modules/file/tests/file.test index 92b7ef3..05083fc 100644 --- a/core/modules/file/tests/file.test +++ b/core/modules/file/tests/file.test @@ -709,8 +709,9 @@ class FileFieldRevisionTestCase extends FileFieldTestCase { // Attach the second file to a user. $user = $this->drupalCreateUser(); - $user->{$field_name}[LANGUAGE_NOT_SPECIFIED][0] = (array) $node_file_r3; - $user->save(); + $edit = (array) $user; + $edit[$field_name][LANGUAGE_NOT_SPECIFIED][0] = (array) $node_file_r3; + user_save($user, $edit); $this->drupalGet('user/' . $user->uid . '/edit'); // Delete the third revision and check that the file is not deleted yet. diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 2d7313f..635754c 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -5,8 +5,6 @@ * Provides discussion forums. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -246,13 +244,13 @@ function forum_uri($forum) { /** * Check whether a content type can be used in a forum. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * * @return * Boolean indicating if the node can be assigned to a forum. */ -function _forum_node_check_node_type(Node $node) { +function _forum_node_check_node_type($node) { // Fetch information about the forum field. $field = field_info_instance('node', 'taxonomy_forums', $node->type); @@ -262,7 +260,7 @@ function _forum_node_check_node_type(Node $node) { /** * Implements hook_node_view(). */ -function forum_node_view(Node $node, $view_mode) { +function forum_node_view($node, $view_mode) { $vid = variable_get('forum_nav_vocabulary', 0); $vocabulary = taxonomy_vocabulary_load($vid); if (_forum_node_check_node_type($node)) { @@ -287,7 +285,7 @@ function forum_node_view(Node $node, $view_mode) { * * Check in particular that only a "leaf" term in the associated taxonomy. */ -function forum_node_validate(Node $node, $form) { +function forum_node_validate($node, $form) { if (_forum_node_check_node_type($node)) { $langcode = $form['taxonomy_forums']['#language']; // vocabulary is selected, not a "container" term. @@ -323,7 +321,7 @@ function forum_node_validate(Node $node, $form) { * * Assign forum taxonomy when adding a topic from within a forum. */ -function forum_node_presave(Node $node) { +function forum_node_presave($node) { if (_forum_node_check_node_type($node)) { // Make sure all fields are set properly: $node->icon = !empty($node->icon) ? $node->icon : ''; @@ -343,7 +341,7 @@ function forum_node_presave(Node $node) { /** * Implements hook_node_update(). */ -function forum_node_update(Node $node) { +function forum_node_update($node) { if (_forum_node_check_node_type($node)) { if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) { if (!empty($node->forum_tid)) { @@ -391,7 +389,7 @@ function forum_node_update(Node $node) { /** * Implements hook_node_insert(). */ -function forum_node_insert(Node $node) { +function forum_node_insert($node) { if (_forum_node_check_node_type($node)) { if (!empty($node->forum_tid)) { $nid = db_insert('forum') @@ -408,7 +406,7 @@ function forum_node_insert(Node $node) { /** * Implements hook_node_predelete(). */ -function forum_node_predelete(Node $node) { +function forum_node_predelete($node) { if (_forum_node_check_node_type($node)) { db_delete('forum') ->condition('nid', $node->nid) @@ -903,10 +901,9 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { $nids[] = $record->nid; } if ($nids) { - $nodes = node_load_multiple($nids); - $query = db_select('node', 'n')->extend('TableSort'); - $query->fields('n', array('nid')); + $query->fields('n', array('title', 'nid', 'type', 'sticky', 'created', 'uid')); + $query->addField('n', 'comment', 'comment_mode'); $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count')); @@ -926,16 +923,7 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { ->orderByHeader($forum_topic_list_header) ->condition('n.nid', $nids); - $result = array(); - foreach ($query->execute() as $row) { - $topic = $nodes[$row->nid]; - $topic->comment_mode = $topic->comment; - - foreach ($row as $key => $value) { - $topic->{$key} = $value; - } - $result[] = $topic; - } + $result = $query->execute(); } else { $result = array(); diff --git a/core/modules/forum/forum.test b/core/modules/forum/forum.test index 135f551..4991752 100644 --- a/core/modules/forum/forum.test +++ b/core/modules/forum/forum.test @@ -5,8 +5,6 @@ * Tests for forum.module. */ -use Drupal\node\Node; - class ForumTestCase extends DrupalWebTestCase { protected $admin_user; protected $edit_own_topics_user; @@ -459,14 +457,14 @@ class ForumTestCase extends DrupalWebTestCase { * * @param $node_user * The user who creates the node. - * @param Node $node + * @param $node * The node being checked. * @param $admin * Boolean to indicate whether the user can 'access administration pages'. * @param $response * The exptected HTTP response code. */ - private function verifyForums($node_user, Node $node, $admin, $response = 200) { + private function verifyForums($node_user, $node, $admin, $response = 200) { $response2 = ($admin) ? 200 : 403; // View forum help node. diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc index d3f9b2d..6138b53 100644 --- a/core/modules/image/image.field.inc +++ b/core/modules/image/image.field.inc @@ -25,7 +25,6 @@ function image_field_info() { 'title_field' => 0, 'max_resolution' => '', 'min_resolution' => '', - 'default_image' => 0, ), 'default_widget' => 'image_image', 'default_formatter' => 'image', @@ -157,15 +156,6 @@ function image_field_instance_settings_form($field, $instance) { '#weight' => 11, ); - // Add the default image to the instance. - $form['default_image'] = array( - '#title' => t('Default image'), - '#type' => 'managed_file', - '#description' => t("If no image is uploaded, this image will be shown on display and will override the field's default image."), - '#default_value' => $settings['default_image'], - '#upload_location' => $field['settings']['uri_scheme'] . '://default_images/', - ); - return $form; } @@ -200,19 +190,8 @@ function image_field_load($entity_type, $entities, $field, $instances, $langcode function image_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { // If there are no files specified at all, use the default. foreach ($entities as $id => $entity) { - if (empty($items[$id])) { - $fid = 0; - // Use the default for the instance if one is available. - if ($instances[$id]['settings']['default_image']) { - $fid = $instances[$id]['settings']['default_image']; - } - // Otherwise, use the default for the field. - elseif ($field['settings']['default_image']) { - $fid = $field['settings']['default_image']; - } - - // Add the default image if one is found. - if ($fid && ($file = file_load($fid))) { + if (empty($items[$id]) && $field['settings']['default_image']) { + if ($file = file_load($field['settings']['default_image'])) { $items[$id][0] = (array) $file + array( 'is_default' => TRUE, 'alt' => '', diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 9e13c85..a3e60c8 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -412,73 +412,6 @@ function image_field_update_field($field, $prior_field, $has_data) { } /** - * Implements hook_field_delete_instance(). - */ -function image_field_delete_instance($instance) { - // Only act on image fields. - $field = field_read_field($instance['field_name']); - if ($field['type'] != 'image') { - return; - } - - // The value of a managed_file element can be an array if the #extended - // property is set to TRUE. - $fid = $instance['settings']['default_image']; - if (is_array($fid)) { - $fid = $fid['fid']; - } - - // Remove the default image when the instance is deleted. - if ($fid && ($file = file_load($fid))) { - file_usage_delete($file, 'image', 'default_image', $instance['id']); - } -} - -/** - * Implements hook_field_update_instance(). - */ -function image_field_update_instance($instance, $prior_instance) { - // Only act on image fields. - $field = field_read_field($instance['field_name']); - if ($field['type'] != 'image') { - return; - } - - // The value of a managed_file element can be an array if the #extended - // property is set to TRUE. - $fid_new = $instance['settings']['default_image']; - if (is_array($fid_new)) { - $fid_new = $fid_new['fid']; - } - $fid_old = $prior_instance['settings']['default_image']; - if (is_array($fid_old)) { - $fid_old = $fid_old['fid']; - } - - // If the old and new files do not match, update the default accordingly. - $file_new = $fid_new ? file_load($fid_new) : FALSE; - if ($fid_new != $fid_old) { - // Save the new file, if present. - if ($file_new) { - $file_new->status = FILE_STATUS_PERMANENT; - file_save($file_new); - file_usage_add($file_new, 'image', 'default_image', $instance['id']); - } - // Delete the old file, if present. - if ($fid_old && ($file_old = file_load($fid_old))) { - file_usage_delete($file_old, 'image', 'default_image', $instance['id']); - } - } - - // If the upload destination changed, then move the file. - if ($file_new && (file_uri_scheme($file_new->uri) != $field['settings']['uri_scheme'])) { - $directory = $field['settings']['uri_scheme'] . '://default_images/'; - file_prepare_directory($directory, FILE_CREATE_DIRECTORY); - file_move($file_new, $directory . $file_new->filename); - } -} - -/** * Clear cached versions of a specific file in all styles. * * @param $path @@ -650,10 +583,7 @@ function image_style_options($include_empty = TRUE) { if ($include_empty && !empty($styles)) { $options[''] = t(''); } - // Use the array concatenation operator '+' here instead of array_merge(), - // because the latter loses the datatype of the array keys, turning - // associative string keys into numeric ones without warning. - $options = $options + drupal_map_assoc(array_keys($styles)); + $options = array_merge($options, drupal_map_assoc(array_keys($styles))); if (empty($options)) { $options[''] = t('No defined styles'); } @@ -869,10 +799,10 @@ function image_style_url($style_name, $path) { $uri = image_style_path($style_name, $path); // If not using clean URLs, the image derivative callback is only available - // with the query string. If the file does not exist, use url() to ensure + // with the script path. If the file does not exist, use url() to ensure // that it is included. Once the file exists it's fine to fall back to the // actual file path, this avoids bootstrapping PHP once the files are built. - if (!variable_get('clean_url') && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { + if ($GLOBALS['script_path'] && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath(); return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE)); } diff --git a/core/modules/image/image.test b/core/modules/image/image.test index d3b3374..160c183 100644 --- a/core/modules/image/image.test +++ b/core/modules/image/image.test @@ -210,7 +210,7 @@ class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase { $generate_url = image_style_url($this->style_name, $original_uri); if (!$clean_url) { - $this->assertTrue(strpos($generate_url, '?q=') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); + $this->assertTrue(strpos($generate_url, 'index.php') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); } // Fetch the URL that generates the file. @@ -368,21 +368,6 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase { } /** - * Test creating an image style with a numeric name and ensuring it can be - * applied to an image. - */ - function testNumericStyleName() { - $style_name = rand(); - $edit = array( - 'name' => $style_name, - ); - $this->drupalPost('admin/config/media/image-styles/add', $edit, t('Create new style')); - $this->assertRaw(t('Style %name was created.', array('%name' => $style_name)), t('Image style successfully created.')); - $options = image_style_options(); - $this->assertTrue(array_key_exists($style_name, $options), t('Array key %key exists.', array('%key' => $style_name))); - } - - /** * General test to add a style, add/remove/edit effects to it, then delete it. */ function testStyle() { @@ -545,8 +530,7 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase { // Create an image field that uses the new style. $field_name = strtolower($this->randomName(10)); - $this->createImageField($field_name, 'article'); - $instance = field_info_instance('node', $field_name, 'article'); + $instance = $this->createImageField($field_name, 'article'); $instance['display']['default']['type'] = 'image'; $instance['display']['default']['settings']['image_style'] = $style_name; field_update_instance($instance); @@ -651,7 +635,7 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { // sent by Drupal. $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.')); $this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.')); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.')); + $this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'),'private') !== FALSE, t('Cache-Control header was sent.')); // Log out and try to access the file. $this->drupalLogout(); @@ -1257,232 +1241,3 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { } } } - -/** - * Tests default image settings. - */ -class ImageFieldDefaultImagesTestCase extends ImageFieldTestCase { - - public static function getInfo() { - return array( - 'name' => 'Image field default images tests', - 'description' => 'Tests setting up default images both to the field and field instance.', - 'group' => 'Image', - ); - } - - function setUp() { - parent::setUp(array('field_ui')); - } - - /** - * Tests CRUD for fields and fields instances with default images. - */ - function testDefaultImages() { - // Create files to use as the default images. - $files = $this->drupalGetTestFiles('image'); - $default_images = array(); - foreach (array('field', 'instance', 'instance2', 'field_new', 'instance_new') as $image_target) { - $file = array_pop($files); - $file = file_save($file); - $default_images[$image_target] = $file; - } - - // Create an image field and add an instance to the article content type. - $field_name = strtolower($this->randomName()); - $field_settings = array( - 'default_image' => $default_images['field']->fid, - ); - $instance_settings = array( - 'default_image' => $default_images['instance']->fid, - ); - $widget_settings = array( - 'preview_image_style' => 'medium', - ); - $this->createImageField($field_name, 'article', $field_settings, $instance_settings, $widget_settings); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, 'article'); - - // Add another instance with another default image to the page content type. - $instance2 = array_merge($instance, array( - 'bundle' => 'page', - 'settings' => array( - 'default_image' => $default_images['instance2']->fid, - ), - )); - field_create_instance($instance2); - $instance2 = field_info_instance('node', $field_name, 'page'); - - - // Confirm the defaults are present on the article field admin form. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); - $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', - $default_images['field']->fid, - format_string( - 'Article image field default equals expected file ID of @fid.', - array('@fid' => $default_images['field']->fid) - ) - ); - $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', - $default_images['instance']->fid, - format_string( - 'Article image field instance default equals expected file ID of @fid.', - array('@fid' => $default_images['instance']->fid) - ) - ); - - // Confirm the defaults are present on the page field admin form. - $this->drupalGet("admin/structure/types/manage/page/fields/$field_name"); - $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', - $default_images['field']->fid, - format_string( - 'Page image field default equals expected file ID of @fid.', - array('@fid' => $default_images['field']->fid) - ) - ); - $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', - $default_images['instance2']->fid, - format_string( - 'Page image field instance default equals expected file ID of @fid.', - array('@fid' => $default_images['instance2']->fid) - ) - ); - - // Confirm that the image default is shown for a new article node. - $article = $this->drupalCreateNode(array('type' => 'article')); - $article_built = node_view($article); - $this->assertEqual( - $article_built[$field_name]['#items'][0]['fid'], - $default_images['instance']->fid, - format_string( - 'A new article node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance']->fid) - ) - ); - - // Confirm that the image default is shown for a new page node. - $page = $this->drupalCreateNode(array('type' => 'page')); - $page_built = node_view($page); - $this->assertEqual( - $page_built[$field_name]['#items'][0]['fid'], - $default_images['instance2']->fid, - format_string( - 'A new page node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance2']->fid) - ) - ); - - // Upload a new default for the field. - $field['settings']['default_image'] = $default_images['field_new']->fid; - field_update_field($field); - - // Confirm that the new field default is used on the article admin form. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); - $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', - $default_images['field_new']->fid, - format_string( - 'Updated image field default equals expected file ID of @fid.', - array('@fid' => $default_images['field_new']->fid) - ) - ); - - // Reload the nodes and confirm the field instance defaults are used. - $article_built = node_view($article = node_load($article->nid, NULL, $reset = TRUE)); - $page_built = node_view($page = node_load($page->nid, NULL, $reset = TRUE)); - $this->assertEqual( - $article_built[$field_name]['#items'][0]['fid'], - $default_images['instance']->fid, - format_string( - 'An existing article node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance']->fid) - ) - ); - $this->assertEqual( - $page_built[$field_name]['#items'][0]['fid'], - $default_images['instance2']->fid, - format_string( - 'An existing page node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance2']->fid) - ) - ); - - // Upload a new default for the article's field instance. - $instance['settings']['default_image'] = $default_images['instance_new']->fid; - field_update_instance($instance); - - // Confirm the new field instance default is used on the article field - // admin form. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); - $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', - $default_images['instance_new']->fid, - format_string( - 'Updated article image field instance default equals expected file ID of @fid.', - array('@fid' => $default_images['instance_new']->fid) - ) - ); - - // Reload the nodes. - $article_built = node_view($article = node_load($article->nid, NULL, $reset = TRUE)); - $page_built = node_view($page = node_load($page->nid, NULL, $reset = TRUE)); - - // Confirm the article uses the new default. - $this->assertEqual( - $article_built[$field_name]['#items'][0]['fid'], - $default_images['instance_new']->fid, - format_string( - 'An existing article node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance_new']->fid) - ) - ); - // Confirm the page remains unchanged. - $this->assertEqual( - $page_built[$field_name]['#items'][0]['fid'], - $default_images['instance2']->fid, - format_string( - 'An existing page node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance2']->fid) - ) - ); - - // Remove the instance default from articles. - $instance['settings']['default_image'] = NULL; - field_update_instance($instance); - - // Confirm the article field instance default has been removed. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); - $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', - '', - 'Updated article image field instance default has been successfully removed.' - ); - - // Reload the nodes. - $article_built = node_view($article = node_load($article->nid, NULL, $reset = TRUE)); - $page_built = node_view($page = node_load($page->nid, NULL, $reset = TRUE)); - // Confirm the article uses the new field (not instance) default. - $this->assertEqual( - $article_built[$field_name]['#items'][0]['fid'], - $default_images['field_new']->fid, - format_string( - 'An existing article node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['field_new']->fid) - ) - ); - // Confirm the page remains unchanged. - $this->assertEqual( - $page_built[$field_name]['#items'][0]['fid'], - $default_images['instance2']->fid, - format_string( - 'An existing page node without an image has the expected default image file ID of @fid.', - array('@fid' => $default_images['instance2']->fid) - ) - ); - } - -} diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index b05a130..61feb3b 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -19,6 +19,7 @@ function language_admin_overview_form($form, &$form_state) { '#tree' => TRUE, '#header' => array( t('Name'), + t('Enabled'), t('Default'), t('Weight'), t('Operations'), @@ -31,6 +32,13 @@ function language_admin_overview_form($form, &$form_state) { $form['languages'][$langcode]['name'] = array( '#markup' => check_plain($language->name), ); + $form['languages'][$langcode]['enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enable @title', array('@title' => $language->name)), + '#title_display' => 'invisible', + '#default_value' => (int) $language->enabled, + '#disabled' => $langcode == $default->langcode, + ); $form['languages'][$langcode]['default'] = array( '#type' => 'radio', '#parents' => array('site_default'), @@ -146,6 +154,21 @@ function language_admin_overview_form_submit($form, &$form_state) { foreach ($languages as $langcode => $language) { $language->default = ($form_state['values']['site_default'] == $langcode); $language->weight = $form_state['values']['languages'][$langcode]['weight']; + + if ($language->default || $old_default->langcode == $langcode) { + // Automatically enable the default language and the language which was + // default previously (because we will not get the value from that + // disabled checkbox). + $form_state['values']['languages'][$langcode]['enabled'] = 1; + } + $language->enabled = (int) !empty($form_state['values']['languages'][$langcode]['enabled']); + + // If the interface language has been disabled make sure that the form + // redirect includes the new default language as a query parameter. + if ($language->enabled == FALSE && $langcode == $GLOBALS['language_interface']->langcode) { + $form_state['redirect'] = array('admin/config/regional/language', array('language' => $languages[$form_state['values']['site_default']])); + } + language_save($language); } @@ -460,6 +483,7 @@ function language_negotiation_configure_form_table(&$form, $type) { '#language_negotiation_info' => array(), '#show_operations' => FALSE, 'weight' => array('#tree' => TRUE), + 'enabled' => array('#tree' => TRUE), ); $negotiation_info = $form['#language_negotiation_info']; @@ -670,7 +694,8 @@ function language_negotiation_configure_url_form($form, &$form_state) { ), ); - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); $prefixes = language_negotiation_url_prefixes(); $domains = language_negotiation_url_domains(); foreach ($languages as $langcode => $language) { @@ -679,7 +704,7 @@ function language_negotiation_configure_url_form($form, &$form_state) { '#title' => t('%language (%langcode) path prefix', array('%language' => $language->name, '%langcode' => $language->langcode)), '#maxlength' => 64, '#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '', - '#field_prefix' => $base_url . '/' . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => $base_url . '/', ); $form['domain'][$langcode] = array( '#type' => 'textfield', @@ -706,7 +731,8 @@ function language_negotiation_configure_url_form($form, &$form_state) { * the prefix and domain are only blank for the default. */ function language_negotiation_configure_url_form_validate($form, &$form_state) { - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); // Count repeated values for uniqueness check. $count = array_count_values($form_state['values']['prefix']); diff --git a/core/modules/language/language.install b/core/modules/language/language.install index 5a962fa..92f61a0 100644 --- a/core/modules/language/language.install +++ b/core/modules/language/language.install @@ -76,6 +76,12 @@ function language_schema() { 'default' => 0, 'description' => 'Direction of language (Left-to-Right = 0, Right-to-Left = 1).', ), + 'enabled' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Enabled flag (1 = Enabled, 0 = Disabled).', + ), 'weight' => array( 'type' => 'int', 'not null' => TRUE, @@ -90,3 +96,31 @@ function language_schema() { ); return $schema; } + +/** + * Rename {language}.language to {language}.langcode. + * + * @see update_prepare_d8_language() + */ +function language_update_8000() { + // Rename language column to langcode and set it again as the primary key. + if (db_field_exists('language', 'language')) { + db_drop_primary_key('language'); + $langcode_spec = array( + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + 'description' => "Language code, e.g. 'de' or 'en-US'.", + ); + db_change_field('language', 'language', 'langcode', $langcode_spec, array('primary key' => array('langcode'))); + } + + // Update the 'language_default' system variable, if configured. + $language_default = variable_get('language_default'); + if (!empty($language_default) && isset($language_default->language)) { + $language_default->langcode = $language_default->language; + unset($language_default->language); + variable_set('language_default', $language_default); + } +} diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 63b3743..f8773b0 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -165,6 +165,10 @@ function language_save($language) { $language->direction = isset($predefined[$language->langcode][2]) ? $predefined[$language->langcode][2] : LANGUAGE_LTR; } + // Set to enabled for the default language and unless specified otherwise. + if (!empty($language->default) || !isset($language->enabled)) { + $language->enabled = TRUE; + } // Let other modules modify $language before saved. module_invoke_all('language_presave', $language); @@ -188,7 +192,7 @@ function language_save($language) { } // Update language count based on enabled language count. - variable_set('language_count', db_query('SELECT COUNT(langcode) FROM {language}')->fetchField()); + variable_set('language_count', db_query('SELECT COUNT(langcode) FROM {language} WHERE enabled = 1')->fetchField()); // Kill the static cache in language_list(). drupal_static_reset('language_list'); @@ -216,7 +220,9 @@ function language_delete($langcode) { ->condition('langcode', $language->langcode) ->execute(); - variable_set('language_count', variable_get('language_count', 1) - 1); + if ($language->enabled) { + variable_set('language_count', variable_get('language_count', 1) - 1); + } drupal_static_reset('language_list'); @@ -463,7 +469,7 @@ function language_block_info() { */ function language_block_view($type) { if (language_multilingual()) { - $path = drupal_is_front_page() ? '' : $_GET['q']; + $path = drupal_is_front_page() ? '' : current_path(); $links = language_negotiation_get_switch_links($type, $path); if (isset($links->links)) { diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index 6269e8b..42ba04a 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -209,9 +209,11 @@ function language_from_url($languages) { switch (variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LANGUAGE_NEGOTIATION_URL_PREFIX: - // $_GET['q'] might not be available at this time, because path - // initialization runs after the language bootstrap phase. - list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); + // Language negotiation happens before the public function current_path() + // is available. + // @todo Refactor with Symfony's Request object. + list($language, $path) = language_url_split_prefix(_current_path(), $languages); + _current_path($path); if ($language !== FALSE) { $language_url = $language->langcode; } @@ -293,7 +295,8 @@ function language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_ * Translation links may be provided by other modules. */ function language_switcher_url($type, $path) { - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); $links = array(); foreach ($languages as $language) { @@ -315,11 +318,11 @@ function language_switcher_session($type, $path) { $param = variable_get('language_negotiation_session_param', 'language'); $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->langcode; - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); $links = array(); $query = $_GET; - unset($query['q']); foreach ($languages as $language) { $langcode = $language->langcode; @@ -351,7 +354,8 @@ function language_url_rewrite_url(&$path, &$options) { $languages = &$drupal_static_fast['languages']; if (!isset($languages)) { - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); $languages = array_flip(array_keys($languages)); } @@ -436,7 +440,8 @@ function language_url_rewrite_session(&$path, &$options) { if (!isset($query_rewrite)) { global $user; if (!$user->uid) { - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); $query_param = check_plain(variable_get('language_negotiation_session_param', 'language')); $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; $query_rewrite = isset($languages[$query_value]) && language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_SESSION); diff --git a/core/modules/language/language.test b/core/modules/language/language.test index f0000e1..7209dc0 100644 --- a/core/modules/language/language.test +++ b/core/modules/language/language.test @@ -74,6 +74,20 @@ class LanguageListTest extends DrupalWebTestCase { $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertText(t('The default language cannot be deleted.'), t('Failed to delete the default language.')); + // Check if we can disable a language. + $edit = array( + 'languages[en][enabled]' => FALSE, + ); + $this->drupalPost(NULL, $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-languages-en-enabled', t('Language disabled.')); + + // Set disabled language to be the default and ensure it is re-enabled. + $edit = array( + 'site_default' => 'en', + ); + $this->drupalPost(NULL, $edit, t('Save configuration')); + $this->assertFieldChecked('edit-languages-en-enabled', t('Default language re-enabled.')); + // Ensure 'edit' link works. $this->clickLink(t('edit')); $this->assertTitle(t('Edit language | Drupal'), t('Page title is "Edit language".')); @@ -86,16 +100,11 @@ class LanguageListTest extends DrupalWebTestCase { $this->assertRaw($name, t('The language has been updated.')); $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); - // Change back the default language. - $edit = array( - 'site_default' => 'en', - ); - $this->drupalPost(NULL, $edit, t('Save configuration')); // Ensure 'delete' link works. $this->drupalGet('admin/config/regional/language'); $this->clickLink(t('delete')); $this->assertText(t('Are you sure you want to delete the language'), t('"delete" link is correct.')); - // Delete a language. + // Delete an enabled language. $this->drupalGet('admin/config/regional/language/delete/' . $langcode); // First test the 'cancel' link. $this->clickLink(t('Cancel')); @@ -113,13 +122,20 @@ class LanguageListTest extends DrupalWebTestCase { $this->assertResponse(404, t('Language no longer found.')); // Make sure the "language_count" variable has been updated correctly. drupal_static_reset('language_list'); - $languages = language_list(); - $this->assertEqual(variable_get('language_count', 1), count($languages), t('Language count is correct.')); - // Delete French. - $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); - // Get the count of languages. + $enabled_languages = language_list(TRUE); + $this->assertEqual(variable_get('language_count', 1), count($enabled_languages), t('Language count is correct.')); + // Delete a disabled language. + // Disable an enabled language. + $edit = array( + 'languages[fr][enabled]' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-languages-fr-enabled', t('French language disabled.')); + // Get the count of enabled languages. drupal_static_reset('language_list'); - $languages = language_list(); + $enabled_languages = language_list(TRUE); + // Delete the disabled language. + $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); // We need raw here because %language and %langcode will add HTML. $t_args = array('%language' => 'French', '%langcode' => 'fr'); $this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('Disabled language has been removed.')); @@ -128,7 +144,7 @@ class LanguageListTest extends DrupalWebTestCase { $this->drupalGet('admin/config/regional/language/delete/fr'); $this->assertResponse(404, t('Language no longer found.')); // Make sure the "language_count" variable has not changed. - $this->assertEqual(variable_get('language_count', 1), count($languages), t('Language count is correct.')); + $this->assertEqual(variable_get('language_count', 1), count($enabled_languages), t('Language count is correct.')); // Ensure we can delete the English language. Right now English is the only // language so we must add a new language and make it the default before @@ -229,6 +245,7 @@ class LanguageDependencyInjectionTest extends DrupalWebTestCase { 'langcode' => 'fr', 'name' => 'French', 'direction' => 0, + 'enabled' => 1, 'weight' => 0, 'default' => TRUE, ); @@ -249,3 +266,190 @@ class LanguageDependencyInjectionTest extends DrupalWebTestCase { variable_del('language_default'); } } + + +/** + * Functional tests for the language list configuration forms. + */ +class LanguageBlockVisibilityTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Language block visibility', + 'description' => 'Tests if a block can be configure to be only visibile on a particular language.', + 'group' => 'Language', + ); + } + + function setUp() { + parent::setUp('language', 'locale', 'block'); + } + + /** + * Tests the visibility settings for the blocks based on language. + */ + public function testLanguageBlockVisibility() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Check if the visibility setting is available. + $this->drupalGet('admin/structure/block/add'); + $this->assertField('langcodes[en]', t('Language visibility field is visible.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[en]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Change the default language. + $edit = array( + 'site_default' => 'fr', + ); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + + // Reset the static cache of the language list. + drupal_static_reset('language_list'); + + // Check that a page has a block + $this->drupalGet('', array('language' => language_load('en'))); + $this->assertText($body, t('The body of the custom block appears on the page.')); + + // Check that a page doesn't has a block for the current language anymore + $this->drupalGet('', array('language' => language_load('fr'))); + $this->assertNoText($body, t('The body of the custom block does not appear on the page.')); + } + + /** + * Tests if the visibility settings are removed if the language is deleted. + */ + public function testLanguageBlockVisibilityLanguageDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the language. + $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); + + // Check that the setting related to this language has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } + + /** + * Tests if the visibility settings are removed if the block is deleted. + */ + public function testLanguageBlockVisibilityBlockDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the custom block. + $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete')); + + // Check that the setting related to this block has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } +} diff --git a/core/modules/locale/locale.admin.inc b/core/modules/locale/locale.admin.inc index 4a4867c..d74f714 100644 --- a/core/modules/locale/locale.admin.inc +++ b/core/modules/locale/locale.admin.inc @@ -45,7 +45,9 @@ function locale_date_format_language_overview_page() { array('data' => t('Operations'), 'colspan' => '2'), ); - $languages = language_list(); + // Get the enabled languages only. + $languages = language_list(TRUE); + foreach ($languages as $langcode => $language) { $row = array(); $row[] = $language->name; diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 8cd4fd1..ed3d3a6 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -12,7 +12,7 @@ include_once DRUPAL_ROOT . '/core/includes/gettext.inc'; */ function locale_translate_import_form($form, &$form_state) { drupal_static_reset('language_list'); - $languages = language_list(); + $languages = language_list(TRUE); // Initialize a language list to the ones available, including English if we // are to translate Drupal to English as well. @@ -127,7 +127,7 @@ function locale_translate_import_form_submit($form, &$form_state) { * Builds form to export Gettext translation files. */ function locale_translate_export_form($form, &$form_state) { - $languages = language_list(); + $languages = language_list(TRUE); $language_options = array(); foreach ($languages as $langcode => $language) { if ($langcode != 'en' || locale_translate_english()) { diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 4351342..fd210ed 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -197,7 +197,7 @@ function locale_init() { // in $conf. This should happen on all pages except the date and time formats // settings page, where we want to display the site default and not the // localized version. - if (strpos($_GET['q'], 'admin/config/regional/date-time/formats') !== 0) { + if (strpos(current_path(), 'admin/config/regional/date-time/formats') !== 0) { $languages = array($language_interface->langcode); // Setup appropriate date formats for this locale. @@ -221,6 +221,85 @@ function locale_permission() { } /** + * Form builder callback to display language selection widget. + * + * @ingroup forms + * @see locale_form_alter() + */ +function locale_language_selector_form($user) { + global $language_interface; + // Get list of enabled languages only. + $languages = language_list(TRUE); + + // If the user is being created, we set the user language to the page language. + $user_preferred_language = $user->uid ? user_preferred_language($user) : $language_interface; + + $names = array(); + foreach ($languages as $langcode => $item) { + $names[$langcode] = $item->name; + } + // Get language negotiation settings. + $mode = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT; + $form['locale'] = array( + '#type' => 'fieldset', + '#title' => t('Language settings'), + '#weight' => 1, + ); + $form['locale']['preferred_langcode'] = array( + '#type' => (count($names) <= 5 ? 'radios' : 'select'), + '#title' => t('Language'), + '#default_value' => $user_preferred_language->langcode, + '#options' => $names, + '#description' => $mode ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."), + ); + // User entities contain both a langcode property (for identifying the + // language of the entity data) and a preferred_langcode property (see above). + // Rather than provide a UI forcing the user to choose both separately, + // assume that the user profile data is in the user's preferred language. This + // element provides that synchronization. For use-cases where this + // synchronization is not desired, a module can alter or remove this element. + $form['locale']['langcode'] = array( + '#type' => 'value', + '#value_callback' => '_locale_language_selector_langcode_value', + // For the synchronization to work, this element must have a larger weight + // than the preferred_langcode element. Set a large weight here in case + // a module alters the weight of the other element. + '#weight' => 100, + ); + return $form; +} + +/** + * Sets the value of the user register and profile forms' langcode element. + * + * @see locale_language_selector_form() + */ +function _locale_language_selector_langcode_value($element, $input, &$form_state) { + $form_state['complete_form']['locale']['preferred_langcode']['#description'] .= ' ' . t("This is also assumed to be the primary language of this account's profile information."); + return $form_state['values']['preferred_langcode']; +} + +/** + * Implements hook_form_alter(). + * + * Adds language fields to user forms. + */ +function locale_form_alter(&$form, &$form_state, $form_id) { + // Only alter user forms if there is more than one language. + if (language_multilingual()) { + // Display language selector when either creating a user on the admin + // interface or editing a user account. + if ($form_id == 'user_register_form' || $form_id == 'user_profile_form') { + $selector = locale_language_selector_form($form['#user']); + if ($form_id == 'user_register_form') { + $selector['locale']['#access'] = user_access('administer users'); + } + $form += $selector; + } + } +} + +/** * Implements hook_form_BASE_FORM_ID_alter(). */ function locale_form_node_form_alter(&$form, &$form_state) { @@ -239,8 +318,9 @@ function locale_form_node_form_alter(&$form, &$form_state) { */ function locale_field_node_form_submit($form, &$form_state) { if (field_has_translation_handler('node', 'locale')) { - $bundle = $form_state['values']['type']; - $node_language = $form_state['values']['langcode']; + $node = (object) $form_state['values']; + $available_languages = field_content_languages(); + list(, , $bundle) = entity_extract_ids('node', $node); foreach (field_info_instances('node', $bundle) as $instance) { $field_name = $instance['field_name']; @@ -249,8 +329,8 @@ function locale_field_node_form_submit($form, &$form_state) { // Handle a possible language change: new language values are inserted, // previous ones are deleted. - if ($field['translatable'] && $previous_langcode != $node_language) { - $form_state['values'][$field_name][$node_language] = $form_state['values'][$field_name][$previous_langcode]; + if ($field['translatable'] && $previous_langcode != $node->langcode) { + $form_state['values'][$field_name][$node->langcode] = $node->{$field_name}[$previous_langcode]; $form_state['values'][$field_name][$previous_langcode] = array(); } } diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 8f26052..74603a9 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -156,7 +156,7 @@ function locale_translation_filters() { // Get all languages, except English drupal_static_reset('language_list'); - $languages = language_list(); + $languages = language_list(TRUE); $language_options = array(); foreach ($languages as $langcode => $language) { if ($langcode != 'en' || locale_translate_english()) { diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test index fa78e14..534ea75 100644 --- a/core/modules/locale/locale.test +++ b/core/modules/locale/locale.test @@ -1930,6 +1930,24 @@ class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase { 'direction' => '0', ); $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Add custom language and disable it. + // Code for the language. + $langcode_disabled = 'xx-yy'; + // The English name for the language. This will be translated. + $name_disabled = $this->randomName(16); + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode_disabled, + 'name' => $name_disabled, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Disable the language. + $edit = array( + 'languages[' . $langcode_disabled . '][enabled]' => FALSE, + ); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); $this->drupalLogout(); // Login as normal user and edit account settings. @@ -1940,6 +1958,8 @@ class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase { $this->assertText(t('Language'), t('Language selector available.')); // Ensure custom language is present. $this->assertText($name, t('Language present on form.')); + // Ensure disabled language isn't present. + $this->assertNoText($name_disabled, t('Disabled language not present on form.')); // Switch to our custom language. $edit = array( 'preferred_langcode' => $langcode, @@ -2181,7 +2201,7 @@ class LocalePathFunctionalTest extends DrupalWebTestCase { // Test that both node titles link to our path alias. $this->drupalGet(''); - $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path); + $custom_path_url = base_path() . $GLOBALS['script_path'] . $custom_path; $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title)); $this->assertTrue(!empty($elements), t('First node links to the path alias.')); $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title)); @@ -2269,6 +2289,25 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { ); $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add disabled custom language. + // Code for the language. + $langcode_disabled = 'xx-yy'; + // The English name for the language. + $name_disabled = $this->randomName(16); + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode_disabled, + 'name' => $name_disabled, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Disable second custom language. + $path = 'admin/config/regional/language'; + $edit = array( + 'languages[' . $langcode_disabled . '][enabled]' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + // Set "Basic page" content type to use multilingual support. $this->drupalGet('admin/structure/types/manage/page'); $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); @@ -2289,8 +2328,10 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { $this->drupalGet('node/add/page'); // Verify language select list is present. $this->assertFieldByName('langcode', NULL, t('Language select present on add Basic page form.')); - // Ensure language appears. - $this->assertText($name, t('Language present.')); + // Ensure enabled language appears. + $this->assertText($name, t('Enabled language present.')); + // Ensure disabled language doesn't appear. + $this->assertNoText($name_disabled, t('Disabled language not present.')); // Create "Basic page" content. $node_title = $this->randomName(); @@ -2479,7 +2520,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // is for some reason not found when doing translate search. This might // be some bug. drupal_static_reset('language_list'); - $languages = language_list(); + $languages = language_list(TRUE); variable_set('language_default', $languages['vi']); // First visit this page to make sure our target string is searchable. $this->drupalGet('admin/config'); @@ -2651,7 +2692,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Check that the language switcher active link matches the given browser // language. - $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $langcode_browser_fallback : "?q=$langcode_browser_fallback")); + $args = array(':url' => base_path() . $GLOBALS['script_path'] . $langcode_browser_fallback); $fields = $this->xpath('//div[@id="block-language-language-interface"]//a[@class="language-link active" and starts-with(@href, :url)]', $args); $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name, t('The browser language is the URL active language')); @@ -2686,21 +2727,21 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { ); $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); - // Build the link we're going to test based on the clean url setting. - $link = (!empty($GLOBALS['conf']['clean_url'])) ? 'it.example.com/admin' : 'it.example.com/?q=admin'; + // Build the link we're going to test. + $link = 'it.example.com/admin'; global $is_https; - // Test URL in another language: http://it.example.com/?q=admin. + // Test URL in another language: http://it.example.com/admin. // Base path gives problems on the testbot, so $correct_link is hard-coded. // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test). - $italian_url = url('admin', array('language' => $languages['it'])); + $italian_url = url('admin', array('language' => $languages['it'], 'script' => '')); $url_scheme = ($is_https) ? 'https://' : 'http://'; $correct_link = $url_scheme . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $italian_url))); // Test https via options. variable_set('https', TRUE); - $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it'])); + $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url))); variable_set('https', FALSE); @@ -2708,7 +2749,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Test https via current url scheme. $temp_https = $is_https; $is_https = TRUE; - $italian_url = url('admin', array('language' => $languages['it'])); + $italian_url = url('admin', array('language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (via current url scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url))); $is_https = $temp_https; @@ -2739,6 +2780,15 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { $edit['predefined_langcode'] = 'fr'; $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + // Install Italian language. + $edit = array(); + $edit['predefined_langcode'] = 'it'; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Disable Italian language. + $edit = array('languages[it][enabled]' => FALSE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + // Enable URL language detection and selection. $edit = array('language_interface[enabled][language-url]' => 1); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); @@ -2750,9 +2800,13 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { } /** - * Check that non-installed languages are not considered. + * Check that disabled or non-installed languages are not considered. */ function testUrlRewritingEdgeCases() { + // Check URL rewriting with a disabled language. + $languages = language_list(); + $this->checkUrl($languages['it'], t('Path language is ignored if language is disabled.'), t('URL language negotiation does not work with disabled languages')); + // Check URL rewriting with a non-installed language. $non_existing = language_default(); $non_existing->langcode = $this->randomName(); @@ -2767,9 +2821,9 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { * is actually not working. */ private function checkUrl($language, $message1, $message2) { - $options = array('language' => $language); + $options = array('language' => $language, 'script' => ''); $base_path = trim(base_path(), '/'); - $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/'); + $rewritten_path = trim(str_replace($base_path, '', url('node', $options)), '/'); $segments = explode('/', $rewritten_path, 2); $prefix = $segments[0]; $path = isset($segments[1]) ? $segments[1] : $prefix; diff --git a/core/modules/menu/menu.admin.js b/core/modules/menu/menu.admin.js index 4e5bf07..42d69d5 100644 --- a/core/modules/menu/menu.admin.js +++ b/core/modules/menu/menu.admin.js @@ -22,9 +22,8 @@ Drupal.menu_update_parent_list = function () { values.push(Drupal.checkPlain($.trim($(this).val()))); }); - var url = Drupal.settings.basePath + 'admin/structure/menu/parents'; $.ajax({ - url: location.protocol + '//' + location.host + url, + url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'), type: 'POST', data: {'menus[]' : values}, dataType: 'json', diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 5d55d06..1f8bd3a 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -11,8 +11,6 @@ * URLs to be added to the main site navigation menu. */ -use Drupal\node\Node; - /** * Maximum length of menu name as entered by the user. Database length is 32 * and we add a menu- prefix. @@ -510,21 +508,21 @@ function menu_block_view_alter(&$data, $block) { /** * Implements hook_node_insert(). */ -function menu_node_insert(Node $node) { +function menu_node_insert($node) { menu_node_save($node); } /** * Implements hook_node_update(). */ -function menu_node_update(Node $node) { +function menu_node_update($node) { menu_node_save($node); } /** * Helper for hook_node_insert() and hook_node_update(). */ -function menu_node_save(Node $node) { +function menu_node_save($node) { if (isset($node->menu)) { $link = &$node->menu; if (empty($link['enabled'])) { @@ -553,7 +551,7 @@ function menu_node_save(Node $node) { /** * Implements hook_node_predelete(). */ -function menu_node_predelete(Node $node) { +function menu_node_predelete($node) { // Delete all menu module links that point to this node. $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu'", array(':path' => 'node/' . $node->nid), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $m) { @@ -564,7 +562,7 @@ function menu_node_predelete(Node $node) { /** * Implements hook_node_prepare(). */ -function menu_node_prepare(Node $node) { +function menu_node_prepare($node) { if (empty($node->menu)) { // Prepare the node for the edit form so that $node->menu always exists. $menu_name = strtok(variable_get('menu_parent_' . $node->type, 'main-menu:0'), ':'); @@ -717,7 +715,7 @@ function menu_form_node_form_alter(&$form, $form_state) { * * @see menu_form_node_form_alter() */ -function menu_node_submit(Node $node, $form, $form_state) { +function menu_node_submit($node, $form, $form_state) { // Decompose the selected menu parent option into 'menu_name' and 'plid', if // the form used the default parent selection widget. if (!empty($form_state['values']['menu']['parent'])) { diff --git a/core/modules/node/lib/Drupal/node/Node.php b/core/modules/node/lib/Drupal/node/Node.php deleted file mode 100644 index 787358e..0000000 --- a/core/modules/node/lib/Drupal/node/Node.php +++ /dev/null @@ -1,170 +0,0 @@ - no comments - * COMMENT_NODE_CLOSED => comments are read-only - * COMMENT_NODE_OPEN => open (read/write) - * - * @var integer - */ - public $comment; - - /** - * The node promotion status. - * - * Promoted nodes should be displayed on the front page of the site. The - * value is either NODE_PROMOTED or NODE_NOT_PROMOTED. - * - * @var integer - */ - public $promote; - - /** - * The node sticky status. - * - * Sticky nodes should be displayed at the top of lists in which they appear. - * The value is either NODE_STICKY or NODE_NOT_STICKY. - * - * @var integer - */ - public $sticky; - - /** - * The node translation set ID. - * - * Translations sets are based on the ID of the node containing the source - * text for the translation set. - * - * @var integer - */ - public $tnid; - - /** - * The node translation status. - * - * If the translation page needs to be updated the value is 1, otherwise 0. - * - * @var integer - */ - public $translate; - - /** - * The node revision creation timestamp. - * - * @var integer - */ - public $revision_timestamp; - - /** - * The node revision author's user ID. - * - * @var integer - */ - public $revision_uid; - - /** - * Implements EntityInterface::id(). - */ - public function id() { - return $this->nid; - } - - /** - * Implements EntityInterface::bundle(). - */ - public function bundle() { - return $this->type; - } - - /** - * Overrides Entity::createDuplicate(). - */ - public function createDuplicate() { - $duplicate = clone $this; - $duplicate->nid = NULL; - $duplicate->vid = NULL; - return $duplicate; - } -} diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php deleted file mode 100644 index 0cdca80..0000000 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ /dev/null @@ -1,291 +0,0 @@ -created)) { - $node->created = REQUEST_TIME; - } - - return $node; - } - - /** - * Overrides EntityDatabaseStorageController::delete(). - */ - public function delete($ids) { - $entities = $ids ? $this->load($ids) : FALSE; - if (!$entities) { - // If no IDs or invalid IDs were passed, do nothing. - return; - } - $transaction = db_transaction(); - - try { - $this->preDelete($entities); - foreach ($entities as $id => $entity) { - $this->invokeHook('predelete', $entity); - } - $ids = array_keys($entities); - - db_delete($this->entityInfo['base table']) - ->condition($this->idKey, $ids, 'IN') - ->execute(); - - if ($this->revisionKey) { - db_delete($this->revisionTable) - ->condition($this->idKey, $ids, 'IN') - ->execute(); - } - - // Reset the cache as soon as the changes have been applied. - $this->resetCache($ids); - - $this->postDelete($entities); - foreach ($entities as $id => $entity) { - $this->invokeHook('delete', $entity); - } - // Ignore slave server temporarily. - db_ignore_slave(); - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception($this->entityType, $e); - throw new EntityStorageException($e->getMessage, $e->getCode, $e); - } - } - - /** - * Overrides EntityDatabaseStorageController::save(). - */ - public function save(EntityInterface $entity) { - $transaction = db_transaction(); - try { - // Load the stored entity, if any. - if (!$entity->isNew() && !isset($entity->original)) { - $entity->original = entity_load_unchanged($this->entityType, $entity->id()); - } - - $this->preSave($entity); - $this->invokeHook('presave', $entity); - - if ($entity->isNew()) { - $op = 'insert'; - $return = drupal_write_record($this->entityInfo['base table'], $entity); - $entity->enforceIsNew(FALSE); - } - else { - $op = 'update'; - $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); - } - - if ($this->revisionKey) { - $this->saveRevision($entity); - } - - // Reset general caches, but keep caches specific to certain entities. - $this->resetCache($op == 'update' ? array($entity->{$this->idKey}): array()); - - $this->postSave($entity, $op == 'update'); - $this->invokeHook($op, $entity); - - // Ignore slave server temporarily. - db_ignore_slave(); - unset($entity->original); - - return $return; - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception($this->entityType, $e); - throw new EntityStorageException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * Saves a node revision. - * - * @param EntityInterface $node - * The node entity. - */ - protected function saveRevision(EntityInterface $entity) { - $record = clone $entity; - $record->uid = $entity->revision_uid; - $record->timestamp = $entity->revision_timestamp; - - if (empty($entity->{$this->revisionKey}) || !empty($entity->revision)) { - drupal_write_record($this->revisionTable, $record); - db_update($this->entityInfo['base table']) - ->fields(array($this->revisionKey => $record->{$this->revisionKey})) - ->condition($this->idKey, $entity->{$this->idKey}) - ->execute(); - } - else { - drupal_write_record($this->revisionTable, $record, $this->revisionKey); - } - // Make sure to update the new revision key for the entity. - $entity->{$this->revisionKey} = $record->{$this->revisionKey}; - } - - /** - * Overrides DrupalDefaultEntityController::attachLoad(). - */ - protected function attachLoad(&$nodes, $revision_id = FALSE) { - // Create an array of nodes for each content type and pass this to the - // object type specific callback. - $typed_nodes = array(); - foreach ($nodes as $id => $entity) { - $typed_nodes[$entity->type][$id] = $entity; - } - - // Call object type specific callbacks on each typed array of nodes. - foreach ($typed_nodes as $node_type => $nodes_of_type) { - if (node_hook($node_type, 'load')) { - $function = node_type_get_base($node_type) . '_load'; - $function($nodes_of_type); - } - } - // Besides the list of nodes, pass one additional argument to - // hook_node_load(), containing a list of node types that were loaded. - $argument = array_keys($typed_nodes); - $this->hookLoadArguments = array($argument); - parent::attachLoad($nodes, $revision_id); - } - - /** - * Overrides DrupalDefaultEntityController::buildQuery(). - */ - protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { - // Ensure that uid is taken from the {node} table, - // alias timestamp to revision_timestamp and add revision_uid. - $query = parent::buildQuery($ids, $conditions, $revision_id); - $fields =& $query->getFields(); - unset($fields['timestamp']); - $query->addField('revision', 'timestamp', 'revision_timestamp'); - $fields['uid']['table'] = 'base'; - $query->addField('revision', 'uid', 'revision_uid'); - return $query; - } - - /** - * Overrides EntityDatabaseStorageController::invokeHook(). - */ - protected function invokeHook($hook, EntityInterface $node) { - if ($hook == 'insert' || $hook == 'update') { - node_invoke($node, $hook); - } - else if ($hook == 'predelete') { - // 'delete' is triggered in 'predelete' is here to preserve hook ordering - // from Drupal 7. - node_invoke($node, 'delete'); - } - - parent::invokeHook($hook, $node); - - if ($hook == 'presave') { - if ($node->isNew() || !empty($node->revision)) { - // When inserting either a new node or a new node revision, $node->log - // must be set because {node_revision}.log is a text column and therefore - // cannot have a default value. However, it might not be set at this - // point (for example, if the user submitting a node form does not have - // permission to create revisions), so we ensure that it is at least an - // empty string in that case. - // @todo: Make the {node_revision}.log column nullable so that we can - // remove this check. - if (!isset($node->log)) { - $node->log = ''; - } - } - elseif (!isset($node->log) || $node->log === '') { - // If we are updating an existing node without adding a new revision, we - // need to make sure $node->log is unset whenever it is empty. As long as - // $node->log is unset, drupal_write_record() will not attempt to update - // the existing database column when re-saving the revision; therefore, - // this code allows us to avoid clobbering an existing log entry with an - // empty one. - unset($node->log); - } - - // When saving a new node revision, unset any existing $node->vid so as to - // ensure that a new revision will actually be created, then store the old - // revision ID in a separate property for use by node hook implementations. - if (!$node->isNew() && !empty($node->revision) && $node->vid) { - $node->old_vid = $node->vid; - $node->vid = NULL; - } - } - } - - /** - * Overrides EntityDatabaseStorageController::preSave(). - */ - protected function preSave(EntityInterface $node) { - // Before saving the node, set changed and revision times. - $node->changed = REQUEST_TIME; - - if ($this->revisionKey && !empty($node->revision)) { - $node->revision_timestamp = REQUEST_TIME; - - if (!isset($node->revision_uid)) { - $node->revision_uid = $GLOBALS['user']->uid; - } - } - } - - /** - * Overrides EntityDatabaseStorageController::postSave(). - */ - function postSave(EntityInterface $node, $update) { - node_access_acquire_grants($node, $update); - } - - /** - * Overrides EntityDatabaseStorageController::preDelete(). - */ - function preDelete($entities) { - if (module_exists('search')) { - foreach ($entities as $id => $entity) { - search_reindex($entity->nid, 'node'); - } - } - } - - /** - * Overrides EntityDatabaseStorageController::postDelete(). - */ - protected function postDelete($nodes) { - // Delete values from other tables also referencing this node. - $ids = array_keys($nodes); - - db_delete('history') - ->condition('nid', $ids, 'IN') - ->execute(); - db_delete('node_access') - ->condition('nid', $ids, 'IN') - ->execute(); - } -} diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index ac362e8..ac18da7 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -106,7 +106,7 @@ function node_filters() { // Language filter if language support is present. if (language_multilingual()) { - $languages = language_list(); + $languages = language_list(TRUE); $language_options = array(LANGUAGE_NOT_SPECIFIED => t('- None -')); foreach ($languages as $langcode => $language) { $language_options[$langcode] = $language->name; @@ -319,7 +319,7 @@ function _node_mass_update_helper($nid, $updates) { foreach ($updates as $name => $value) { $node->$name = $value; } - $node->save(); + node_save($node); return $node; } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 2511ce5..2324b47 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -56,8 +56,8 @@ * - hook_entity_update() (all) * - hook_node_access_records() (all) * - hook_node_access_records_alter() (all) - * - Loading a node (calling node_load(), node_load_multiple(), entity_load() - * or entity_load_multiple() with $entity_type of 'node'): + * - Loading a node (calling node_load(), node_load_multiple(), or + * entity_load() with $entity_type of 'node'): * - Node and revision information is read from database. * - hook_load() (node-type-specific) * - field_attach_load_revision() and field_attach_load() @@ -253,7 +253,7 @@ function hook_node_grants($account, $op) { * * Note: a deny all grant is not written to the database; denies are implicit. * - * @param Drupal\node\Node $node + * @param $node * The node that has just been saved. * * @return @@ -262,7 +262,7 @@ function hook_node_grants($account, $op) { * @see _node_access_write_grants() * @ingroup node_access */ -function hook_node_access_records(Drupal\node\Node $node) { +function hook_node_access_records($node) { // We only care about the node if it has been marked private. If not, it is // treated just like any other node and we completely ignore it. if ($node->private) { @@ -313,7 +313,7 @@ function hook_node_access_records(Drupal\node\Node $node) { * * @param $grants * The $grants array returned by hook_node_access_records(). - * @param Drupal\node\Node $node + * @param $node * The node for which the grants were acquired. * * The preferred use of this hook is in a module that bridges multiple node @@ -325,7 +325,7 @@ function hook_node_access_records(Drupal\node\Node $node) { * @see hook_node_grants_alter() * @ingroup node_access */ -function hook_node_access_records_alter(&$grants, Drupal\node\Node $node) { +function hook_node_access_records_alter(&$grants, $node) { // Our module allows editors to mark specific articles with the 'is_preview' // field. If the node being saved has a TRUE value for that field, then only // our grants are retained, and other grants are removed. Doing so ensures @@ -456,14 +456,14 @@ function hook_node_operations() { * field_attach_delete() are called, and before the node is removed from the * node table in the database. * - * @param Drupal\node\Node $node + * @param $node * The node that is about to be deleted. * * @see hook_node_predelete() * @see node_delete_multiple() * @ingroup node_api_hooks */ -function hook_node_predelete(Drupal\node\Node $node) { +function hook_node_predelete($node) { db_delete('mytable') ->condition('nid', $node->nid) ->execute(); @@ -475,14 +475,14 @@ function hook_node_predelete(Drupal\node\Node $node) { * This hook is invoked from node_delete_multiple() after field_attach_delete() * has been called and after the node has been removed from the database. * - * @param Drupal\node\Node $node + * @param $node * The node that has been deleted. * * @see hook_node_predelete() * @see node_delete_multiple() * @ingroup node_api_hooks */ -function hook_node_delete(Drupal\node\Node $node) { +function hook_node_delete($node) { drupal_set_message(t('Node: @title has been deleted', array('@title' => $node->title))); } @@ -493,12 +493,12 @@ function hook_node_delete(Drupal\node\Node $node) { * removed from the node_revision table, and before * field_attach_delete_revision() is called. * - * @param Drupal\node\Node $node + * @param $node * The node revision (node object) that is being deleted. * * @ingroup node_api_hooks */ -function hook_node_revision_delete(Drupal\node\Node $node) { +function hook_node_revision_delete($node) { db_delete('mytable') ->condition('vid', $node->vid) ->execute(); @@ -511,12 +511,12 @@ function hook_node_revision_delete(Drupal\node\Node $node) { * node table in the database, after the type-specific hook_insert() is invoked, * and after field_attach_insert() is called. * - * @param Drupal\node\Node $node + * @param $node * The node that is being created. * * @ingroup node_api_hooks */ -function hook_node_insert(Drupal\node\Node $node) { +function hook_node_insert($node) { db_insert('mytable') ->fields(array( 'nid' => $node->nid, @@ -583,8 +583,8 @@ function hook_node_load($nodes, $types) { * the default home page at path 'node', a recent content block, etc.) See * @link node_access Node access rights @endlink for a full explanation. * - * @param Drupal\node\Node|string $node - * Either a node entity or the machine name of the content type on which to + * @param object|string $node + * Either a node object or the machine name of the content type on which to * perform the access check. * @param string $op * The operation to be performed. Possible values: @@ -634,12 +634,12 @@ function hook_node_access($node, $op, $account) { * This hook is invoked from node_object_prepare() after the type-specific * hook_prepare() is invoked. * - * @param Drupal\node\Node $node + * @param $node * The node that is about to be shown on the add/edit form. * * @ingroup node_api_hooks */ -function hook_node_prepare(Drupal\node\Node $node) { +function hook_node_prepare($node) { if (!isset($node->comment)) { $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN); } @@ -651,7 +651,7 @@ function hook_node_prepare(Drupal\node\Node $node) { * This hook is invoked from node_search_execute(), after node_load() * and node_view() have been called. * - * @param Drupal\node\Node $node + * @param $node * The node being displayed in a search result. * * @return array @@ -665,7 +665,7 @@ function hook_node_prepare(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_node_search_result(Drupal\node\Node $node) { +function hook_node_search_result($node) { $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField(); return array('comment' => format_plural($comments, '1 comment', '@count comments')); } @@ -676,12 +676,12 @@ function hook_node_search_result(Drupal\node\Node $node) { * This hook is invoked from node_save() before the node is saved to the * database. * - * @param Drupal\node\Node $node + * @param $node * The node that is being inserted or updated. * * @ingroup node_api_hooks */ -function hook_node_presave(Drupal\node\Node $node) { +function hook_node_presave($node) { if ($node->nid && $node->moderate) { // Reset votes when node is updated: $node->score = 0; @@ -697,12 +697,12 @@ function hook_node_presave(Drupal\node\Node $node) { * table in the database, after the type-specific hook_update() is invoked, and * after field_attach_update() is called. * - * @param Drupal\node\Node $node + * @param $node * The node that is being updated. * * @ingroup node_api_hooks */ -function hook_node_update(Drupal\node\Node $node) { +function hook_node_update($node) { db_update('mytable') ->fields(array('extra' => $node->extra)) ->condition('nid', $node->nid) @@ -715,7 +715,7 @@ function hook_node_update(Drupal\node\Node $node) { * This hook is invoked during search indexing, after node_load(), and after * the result of node_view() is added as $node->rendered to the node object. * - * @param Drupal\node\Node $node + * @param $node * The node being indexed. * * @return string @@ -723,7 +723,7 @@ function hook_node_update(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_node_update_index(Drupal\node\Node $node) { +function hook_node_update_index($node) { $text = ''; $comments = db_query('SELECT subject, comment, format FROM {comment} WHERE nid = :nid AND status = :status', array(':nid' => $node->nid, ':status' => COMMENT_PUBLISHED)); foreach ($comments as $comment) { @@ -747,7 +747,7 @@ function hook_node_update_index(Drupal\node\Node $node) { * hook_node_presave() instead. If it is really necessary to change * the node at the validate stage, you can use form_set_value(). * - * @param Drupal\node\Node $node + * @param $node * The node being validated. * @param $form * The form being used to edit the node. @@ -756,7 +756,7 @@ function hook_node_update_index(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_node_validate(Drupal\node\Node $node, $form, &$form_state) { +function hook_node_validate($node, $form, &$form_state) { if (isset($node->end) && isset($node->start)) { if ($node->start > $node->end) { form_set_error('time', t('An event may not end before it starts.')); @@ -775,8 +775,8 @@ function hook_node_validate(Drupal\node\Node $node, $form, &$form_state) { * properties. See hook_field_attach_submit() for customizing field-related * properties. * - * @param Drupal\node\Node $node - * The node entity being updated in response to a form submission. + * @param $node + * The node object being updated in response to a form submission. * @param $form * The form being used to edit the node. * @param $form_state @@ -784,7 +784,7 @@ function hook_node_validate(Drupal\node\Node $node, $form, &$form_state) { * * @ingroup node_api_hooks */ -function hook_node_submit(Drupal\node\Node $node, $form, &$form_state) { +function hook_node_submit($node, $form, &$form_state) { // Decompose the selected menu parent option into 'menu_name' and 'plid', if // the form used the default parent selection widget. if (!empty($form_state['values']['menu']['parent'])) { @@ -804,7 +804,7 @@ function hook_node_submit(Drupal\node\Node $node, $form, &$form_state) { * the RSS item generated for this node. * For details on how this is used, see node_feed(). * - * @param Drupal\node\Node $node + * @param $node * The node that is being assembled for rendering. * @param $view_mode * The $view_mode parameter from node_view(). @@ -817,7 +817,7 @@ function hook_node_submit(Drupal\node\Node $node, $form, &$form_state) { * * @ingroup node_api_hooks */ -function hook_node_view(Drupal\node\Node $node, $view_mode, $langcode) { +function hook_node_view($node, $view_mode, $langcode) { $node->content['my_additional_field'] = array( '#markup' => $additional_field, '#weight' => 10, @@ -1027,12 +1027,12 @@ function hook_node_type_delete($info) { * removed from the node table in the database, before hook_node_delete() is * invoked, and before field_attach_delete() is called. * - * @param Drupal\node\Node $node + * @param $node * The node that is being deleted. * * @ingroup node_api_hooks */ -function hook_delete(Drupal\node\Node $node) { +function hook_delete($node) { db_delete('mytable') ->condition('nid', $node->nid) ->execute(); @@ -1047,12 +1047,12 @@ function hook_delete(Drupal\node\Node $node) { * This hook is invoked from node_object_prepare() before the general * hook_node_prepare() is invoked. * - * @param Drupal\node\Node $node + * @param $node * The node that is about to be shown on the add/edit form. * * @ingroup node_api_hooks */ -function hook_prepare(Drupal\node\Node $node) { +function hook_prepare($node) { if ($file = file_check_upload($field_name)) { $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE)); if ($file) { @@ -1083,7 +1083,7 @@ function hook_prepare(Drupal\node\Node $node) { * displayed automatically by the node module. This hook just needs to * return the node title and form editing fields specific to the node type. * - * @param Drupal\node\Node $node + * @param $node * The node being added or edited. * @param $form_state * The form state array. @@ -1094,7 +1094,7 @@ function hook_prepare(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_form(Drupal\node\Node $node, &$form_state) { +function hook_form($node, &$form_state) { $type = node_type_get_type($node); $form['title'] = array( @@ -1135,12 +1135,12 @@ function hook_form(Drupal\node\Node $node, &$form_state) { * node table in the database, before field_attach_insert() is called, and * before hook_node_insert() is invoked. * - * @param Drupal\node\Node $node + * @param $node * The node that is being created. * * @ingroup node_api_hooks */ -function hook_insert(Drupal\node\Node $node) { +function hook_insert($node) { db_insert('mytable') ->fields(array( 'nid' => $node->nid, @@ -1193,12 +1193,12 @@ function hook_load($nodes) { * node table in the database, before field_attach_update() is called, and * before hook_node_update() is invoked. * - * @param Drupal\node\Node $node + * @param $node * The node that is being updated. * * @ingroup node_api_hooks */ -function hook_update(Drupal\node\Node $node) { +function hook_update($node) { db_update('mytable') ->fields(array('extra' => $node->extra)) ->condition('nid', $node->nid) @@ -1222,7 +1222,7 @@ function hook_update(Drupal\node\Node $node) { * have no effect. The preferred method to change a node's content is to use * hook_node_presave() instead. * - * @param Drupal\node\Node $node + * @param $node * The node being validated. * @param $form * The form being used to edit the node. @@ -1231,7 +1231,7 @@ function hook_update(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_validate(Drupal\node\Node $node, $form, &$form_state) { +function hook_validate($node, $form, &$form_state) { if (isset($node->end) && isset($node->start)) { if ($node->start > $node->end) { form_set_error('time', t('An event may not end before it starts.')); @@ -1249,7 +1249,7 @@ function hook_validate(Drupal\node\Node $node, $form, &$form_state) { * so that the node type module can define a custom method for display, or * add to the default display. * - * @param Drupal\node\Node $node + * @param $node * The node to be displayed, as returned by node_load(). * @param $view_mode * View mode, e.g. 'full', 'teaser', ... @@ -1268,7 +1268,7 @@ function hook_validate(Drupal\node\Node $node, $form, &$form_state) { * * @ingroup node_api_hooks */ -function hook_view(Drupal\node\Node $node, $view_mode) { +function hook_view($node, $view_mode) { if ($view_mode == 'full' && node_is_page($node)) { $breadcrumb = array(); $breadcrumb[] = l(t('Home'), NULL); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 5e1c3b3..951f82d 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -3,7 +3,7 @@ use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Database\Query\SelectExtender; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\node\Node; +use Symfony\Component\HttpFoundation\Response; /** * @file @@ -184,8 +184,7 @@ function node_entity_info() { $return = array( 'node' => array( 'label' => t('Node'), - 'controller class' => 'Drupal\node\NodeStorageController', - 'entity class' => 'Drupal\node\Node', + 'controller class' => 'NodeController', 'base table' => 'node', 'revision table' => 'node_revision', 'uri callback' => 'node_uri', @@ -262,11 +261,8 @@ function node_field_display_node_alter(&$display, $context) { /** * Entity uri callback. - * - * @param Drupal\node\Node $node - * A node entity. */ -function node_uri(Node $node) { +function node_uri($node) { return array( 'path' => 'node/' . $node->nid, ); @@ -319,10 +315,10 @@ function node_title_list($result, $title = NULL) { /** * Updates the 'last viewed' timestamp of the specified node for current user. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. */ -function node_tag_new(Node $node) { +function node_tag_new($node) { global $user; if ($user->uid) { db_merge('history') @@ -382,7 +378,7 @@ function node_mark($nid, $timestamp) { /** * Extracts the type name. * - * @param Drupal\node\Node|string $node + * @param $node * Either a string or object, containing the node type information. * * @return @@ -410,8 +406,8 @@ function node_type_get_types() { /** * Returns the node type of the passed node or node type string. * - * @param Drupal\node\Node|string $node - * A node entity or string that indicates the node type to return. + * @param $node + * A node object or string that indicates the node type to return. * * @return * A single node type, as an object, or FALSE if the node type is not found. @@ -433,8 +429,8 @@ function node_type_get_type($node) { * execute node-type-specific hooks. For types defined in the user interface * and managed by node.module, the base is 'node_content'. * - * @param Drupal\node\Node|string $node - * A node entity or string that indicates the node type to return. + * @param $node + * A node object or string that indicates the node type to return. * * @return * The node type base or FALSE if the node type is not found. @@ -463,8 +459,8 @@ function node_type_get_names() { /** * Returns the node type name of the passed node or node type string. * - * @param Drupal\node\Node|string $node - * A node entity or string that indicates the node type to return. + * @param $node + * A node object or string that indicates the node type to return. * * @return * The node type name or FALSE if the node type is not found. @@ -877,8 +873,8 @@ function node_rdf_mapping() { /** * Determines whether a node hook exists. * - * @param Drupal\node\Node|string $node - * A node entity or a string containing the node type. + * @param $node + * A node object or a string containing the node type. * @param $hook * A string containing the name of the hook. * @@ -893,8 +889,8 @@ function node_hook($node, $hook) { /** * Invokes a node hook. * - * @param Drupal\node\Node|string $node - * A node entity or a string containing the node type. + * @param $node + * A node object or a string containing the node type. * @param $hook * A string containing the name of the hook. * @param $a2, $a3, $a4 @@ -918,41 +914,41 @@ function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { * from the database. Nodes are loaded into memory and will not require * database access if loaded again during the same page request. * - * @param array|bool $nids - * (optional) An array of node IDs, or FALSE to load all nodes. - * @param array $conditions + * @param $nids + * (optional) An array of node IDs. + * @param $conditions * (deprecated) An associative array of conditions on the {node} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. - * @param bool $reset + * @param $reset * (optional) Whether to reset the internal node_load() cache. * - * @return array - * An array of node entities indexed by nid. + * @return + * An array of node objects indexed by nid. * * @todo Remove $conditions in Drupal 8. * - * @see entity_load_multiple() + * @see entity_load() * @see EntityFieldQuery */ -function node_load_multiple($nids = array(), array $conditions = array(), $reset = FALSE) { - return entity_load_multiple('node', $nids, $conditions, $reset); +function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('node', $nids, $conditions, $reset); } /** - * Loads a node entity from the database. + * Loads a node object from the database. * - * @param int $nid + * @param $nid * (optional) The node ID. - * @param int $vid + * @param $vid * (optional) The revision ID. - * @param bool $reset + * @param $reset * (optional) Whether to reset the node_load_multiple() cache. * - * @return Drupal\node\Node|false - * A fully-populated node entity, or FALSE if the node is not found. + * @return + * A fully-populated node object, or FALSE if the node is not found. */ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { $nids = (isset($nid) ? array($nid) : array()); @@ -962,15 +958,15 @@ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { } /** - * Prepares a node entity for editing. + * Prepares a node object for editing. * * Fills in a few default values, and then invokes hook_prepare() on the node * type module, and hook_node_prepare() on all modules. * - * @param Drupal\node\Node $node - * The node entity. + * @param $node + * The node object. */ -function node_object_prepare(Node $node) { +function node_object_prepare($node) { // Set up default values, if required. $node_options = variable_get('node_options_' . $node->type, array('status', 'promote')); // If this is a new node, fill in the default values. @@ -987,7 +983,7 @@ function node_object_prepare(Node $node) { } else { $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O'); - // Remove the log message from the original node entity. + // Remove the log message from the original node object. $node->log = NULL; } // Always use the default revision setting. @@ -1053,11 +1049,6 @@ function node_submit($node) { } } - // If a new revision is created, save the current user as revision author. - if (!empty($node->revision)) { - $node->revision_uid = $user->uid; - } - $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME; $node->validated = TRUE; @@ -1067,12 +1058,166 @@ function node_submit($node) { /** * Saves changes to a node or adds a new node. * - * @param Drupal\node\Node $node - * The $node entity to be saved. If $node->nid is + * @param $node + * The $node object to be saved. If $node->nid is * omitted (or $node->is_new is TRUE), a new node will be added. */ -function node_save(Node $node) { - $node->save(); +function node_save($node) { + $transaction = db_transaction(); + + try { + // Load the stored entity, if any. + if (!empty($node->nid) && !isset($node->original)) { + $node->original = entity_load_unchanged('node', $node->nid); + } + + field_attach_presave('node', $node); + global $user; + + // Determine if we will be inserting a new node. + if (!isset($node->is_new)) { + $node->is_new = empty($node->nid); + } + + // Set the timestamp fields. + if (empty($node->created)) { + $node->created = REQUEST_TIME; + } + // The changed timestamp is always updated for bookkeeping purposes, + // for example: revisions, searching, etc. + $node->changed = REQUEST_TIME; + + $node->timestamp = REQUEST_TIME; + $update_node = TRUE; + + // Let modules modify the node before it is saved to the database. + module_invoke_all('node_presave', $node); + module_invoke_all('entity_presave', $node, 'node'); + + if ($node->is_new || !empty($node->revision)) { + // When inserting either a new node or a new node revision, $node->log + // must be set because {node_revision}.log is a text column and therefore + // cannot have a default value. However, it might not be set at this + // point (for example, if the user submitting a node form does not have + // permission to create revisions), so we ensure that it is at least an + // empty string in that case. + // @todo: Make the {node_revision}.log column nullable so that we can + // remove this check. + if (!isset($node->log)) { + $node->log = ''; + } + } + elseif (!isset($node->log) || $node->log === '') { + // If we are updating an existing node without adding a new revision, we + // need to make sure $node->log is unset whenever it is empty. As long as + // $node->log is unset, drupal_write_record() will not attempt to update + // the existing database column when re-saving the revision; therefore, + // this code allows us to avoid clobbering an existing log entry with an + // empty one. + unset($node->log); + } + + // When saving a new node revision, unset any existing $node->vid so as to + // ensure that a new revision will actually be created, then store the old + // revision ID in a separate property for use by node hook implementations. + if (!$node->is_new && !empty($node->revision) && $node->vid) { + $node->old_vid = $node->vid; + unset($node->vid); + } + + // Save the node and node revision. + if ($node->is_new) { + // For new nodes, save new records for both the node itself and the node + // revision. + drupal_write_record('node', $node); + _node_save_revision($node, $user->uid); + $op = 'insert'; + } + else { + // For existing nodes, update the node record which matches the value of + // $node->nid. + drupal_write_record('node', $node, 'nid'); + // Then, if a new node revision was requested, save a new record for + // that; otherwise, update the node revision record which matches the + // value of $node->vid. + if (!empty($node->revision)) { + _node_save_revision($node, $user->uid); + } + else { + _node_save_revision($node, $user->uid, 'vid'); + $update_node = FALSE; + } + $op = 'update'; + } + if ($update_node) { + db_update('node') + ->fields(array('vid' => $node->vid)) + ->condition('nid', $node->nid) + ->execute(); + } + + // Call the node specific callback (if any). This can be + // node_invoke($node, 'insert') or + // node_invoke($node, 'update'). + node_invoke($node, $op); + + // Save fields. + $function = "field_attach_$op"; + $function('node', $node); + + module_invoke_all('node_' . $op, $node); + module_invoke_all('entity_' . $op, $node, 'node'); + + // Update the node access table for this node. There's no need to delete + // existing records if the node is new. + $delete = $op == 'update'; + node_access_acquire_grants($node, $delete); + + // Clear internal properties. + unset($node->is_new); + unset($node->original); + // Clear the static loading cache. + entity_get_controller('node')->resetCache(array($node->nid)); + + // Ignore slave server temporarily to give time for the + // saved node to be propagated to the slave. + db_ignore_slave(); + } + catch (Exception $e) { + $transaction->rollback(); + watchdog_exception('node', $e); + throw $e; + } +} + +/** + * Saves a revision with the ID of the current user. + * + * The resulting revision ID is available afterward in $node->vid. + * + * @param $node + * The node object to be processed. + * @param $uid + * User ID of the current user. + * @param $update + * (optional) To indicate that this is a new record to be inserted, omit this + * argument. If this is an update, this argument specifies the primary keys' + * field names. If there is only 1 field in the key, you may pass in a string; + * if there are multiple fields in the key, pass in an array. + * + * @see node_save() + */ +function _node_save_revision($node, $uid, $update = NULL) { + $temp_uid = $node->uid; + $node->uid = $uid; + if (isset($update)) { + drupal_write_record('node_revision', $node, $update); + } + else { + drupal_write_record('node_revision', $node); + } + // Have node object still show node owner's uid, not revision author's. + $node->uid = $temp_uid; } /** @@ -1095,7 +1240,59 @@ function node_delete($nid) { * @see hook_node_delete() */ function node_delete_multiple($nids) { - entity_delete_multiple('node', $nids); + $transaction = db_transaction(); + if (!empty($nids)) { + $nodes = node_load_multiple($nids, array()); + + try { + foreach ($nodes as $nid => $node) { + // Call the node-specific callback (if any): + node_invoke($node, 'delete'); + + // Allow modules to act prior to node deletion. + module_invoke_all('node_predelete', $node); + module_invoke_all('entity_predelete', $node, 'node'); + + field_attach_delete('node', $node); + + // Remove this node from the search index if needed. + // This code is implemented in node module rather than in search module, + // because node module is implementing search module's API, not the other + // way around. + if (module_exists('search')) { + search_reindex($nid, 'node'); + } + } + + // Delete after calling hooks so that they can query node tables as needed. + db_delete('node') + ->condition('nid', $nids, 'IN') + ->execute(); + db_delete('node_revision') + ->condition('nid', $nids, 'IN') + ->execute(); + db_delete('history') + ->condition('nid', $nids, 'IN') + ->execute(); + db_delete('node_access') + ->condition('nid', $nids, 'IN') + ->execute(); + + foreach ($nodes as $nid => $node) { + // Allow modules to respond to node deletion. + module_invoke_all('node_delete', $node); + module_invoke_all('entity_delete', $node, 'node'); + } + } + catch (Exception $e) { + $transaction->rollback(); + watchdog_exception('node', $e); + throw $e; + } + + // Clear the page and block and node_load_multiple caches. + entity_get_controller('node')->resetCache(); + } } /** @@ -1129,8 +1326,8 @@ function node_revision_delete($revision_id) { /** * Generates an array for rendering the given node. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $view_mode * (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'full.' * @param $langcode @@ -1140,7 +1337,7 @@ function node_revision_delete($revision_id) { * @return * An array as expected by drupal_render(). */ -function node_view(Node $node, $view_mode = 'full', $langcode = NULL) { +function node_view($node, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->langcode; } @@ -1194,15 +1391,15 @@ function node_view(Node $node, $view_mode = 'full', $langcode = NULL) { * Contributed modules might define additional view modes, or use existing * view modes in additional contexts. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $view_mode * (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'full.' * @param $langcode * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. */ -function node_build_content(Node $node, $view_mode = 'full', $langcode = NULL) { +function node_build_content($node, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->langcode; } @@ -1255,8 +1452,8 @@ function node_build_content(Node $node, $view_mode = 'full', $langcode = NULL) { /** * Page callback: Generates an array which displays a node detail page. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $message * (optional) A flag which sets a page title relevant to the revision being * viewed. @@ -1266,7 +1463,7 @@ function node_build_content(Node $node, $view_mode = 'full', $langcode = NULL) { * * @see node_menu() */ -function node_show(Node $node, $message = FALSE) { +function node_show($node, $message = FALSE) { if ($message) { drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH); } @@ -1283,13 +1480,13 @@ function node_show(Node $node, $message = FALSE) { /** * Checks whether the current page is the full page view of the passed-in node. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * * @return * The ID of the node if this is a full page view, otherwise FALSE. */ -function node_is_page(Node $node) { +function node_is_page($node) { $page_node = menu_get_object(); return (!empty($page_node) ? $page_node->nid == $node->nid : FALSE); } @@ -1343,7 +1540,7 @@ function template_preprocess_node(&$variables) { $variables['title'] = check_plain($node->title); $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node); - // Flatten the node entity's member fields. + // Flatten the node object's member fields. $variables = array_merge((array) $node, $variables); // Helpful $content variable for templates. @@ -1712,7 +1909,7 @@ function theme_node_search_admin($variables) { /** * Access callback: Checks node revision access. * - * @param Drupal\node\Node $node + * @param $node * The node to check. * @param $op * (optional) The specific operation being checked. Defaults to 'view.' @@ -1725,7 +1922,7 @@ function theme_node_search_admin($variables) { * * @see node_menu() */ -function _node_revision_access(Node $node, $op = 'view', $account = NULL) { +function _node_revision_access($node, $op = 'view', $account = NULL) { $access = &drupal_static(__FUNCTION__, array()); $map = array( @@ -2013,12 +2210,12 @@ function node_type_page_title($type) { /** * Title callback: Displays the node's title. * - * @param Drupal\node\Node $node - * The node entity. + * @param $node + * The node object. * * @see node_menu() */ -function node_page_title(Node $node) { +function node_page_title($node) { return $node->title; } @@ -2037,11 +2234,8 @@ function node_last_changed($nid) { /** * Returns a list of all the existing revision numbers for the node passed in. - * - * @param Drupal\node\Node $node - * The node entity. */ -function node_revision_list(Node $node) { +function node_revision_list($node) { $revisions = array(); $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revision} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = :nid ORDER BY r.vid DESC', array(':nid' => $node->nid)); foreach ($result as $revision) { @@ -2130,7 +2324,7 @@ function node_block_save($delta = '', $edit = array()) { * (optional) The maximum number of nodes to find. Defaults to 10. * * @return - * An array of node entities or an empty array if there are no recent + * An array of partial node objects or an empty array if there are no recent * nodes visible to the current user. */ function node_get_recent($number = 10) { @@ -2169,7 +2363,7 @@ function node_get_recent($number = 10) { * * @param $variables * An associative array containing: - * - nodes: An array of recent node entities. + * - nodes: An array of recent node objects. * * @ingroup themeable */ @@ -2210,7 +2404,7 @@ function theme_node_recent_block($variables) { * * @param $variables * An associative array containing: - * - node: A node entity. + * - node: A node object. * * @ingroup themeable */ @@ -2458,8 +2652,7 @@ function node_feed($nids = FALSE, $channel = array()) { $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language'], $channel_extras); $output .= "\n"; - drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8'); - print $output; + return new Response($output, 200, array('Content-Type' => 'application/rss+xml; charset=utf-8')); } /** @@ -2546,12 +2739,12 @@ function node_page_default() { /** * Page callback: Displays a single node. * - * @param Drupal\node\Node $node - * The node entity. + * @param $node + * The node object. * * @see node_menu() */ -function node_page_view(Node $node) { +function node_page_view($node) { // If there is a menu link to this node, the link becomes the last part // of the active trail, and the link name becomes the page title. // Thus, we must explicitly set the page title to be the node title. @@ -2571,12 +2764,8 @@ function node_update_index() { $limit = (int)variable_get('search_cron_limit', 100); $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(), array('target' => 'slave')); - $nids = $result->fetchCol(); - if (!$nids) { - return; - } - foreach (node_load_multiple($nids) as $node) { + foreach ($result as $node) { _node_index_node($node); } } @@ -2584,10 +2773,11 @@ function node_update_index() { /** * Indexes a single node. * - * @param Drupal\node\Node $node + * @param $node * The node to index. */ -function _node_index_node(Node $node) { +function _node_index_node($node) { + $node = node_load($node->nid); // Save the changed time of the most recent indexed node, for the search // results half-life calculation. @@ -2667,7 +2857,7 @@ function node_form_search_form_alter(&$form, $form_state) { // Languages: $language_options = array(); - foreach (language_list() as $langcode => $language) { + foreach (language_list(TRUE) as $langcode => $language) { $language_options[$langcode] = $language->name; } if (count($language_options) > 1) { @@ -2805,8 +2995,8 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { * - "update" * - "delete" * - "create" - * @param Drupal\node\Node|string $node - * The node entity on which the operation is to be performed, or the node type + * @param $node + * The node object on which the operation is to be performed, or the node type * (e.g., 'forum') for the 'create' operation. * @param $account * (optional) A user object representing the user for whom the operation is to @@ -3295,13 +3485,13 @@ function _node_query_node_access_alter($query, $type) { * via hook_node_access_records_alter() implementations, and saves the collected * and altered grants to the database. * - * @param Drupal\node\Node $node + * @param $node * The $node to acquire grants for. * @param $delete * (optional) Whether to delete existing node access records before inserting * new ones. Defaults to TRUE. */ -function node_access_acquire_grants(Node $node, $delete = TRUE) { +function node_access_acquire_grants($node, $delete = TRUE) { $grants = module_invoke_all('node_access_records', $node); // Let modules alter the grants. drupal_alter('node_access_records', $grants, $node); @@ -3323,7 +3513,7 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) { * Note: Don't call this function directly from a contributed module. Call * node_access_acquire_grants() instead. * - * @param Drupal\node\Node $node + * @param $node * The $node being written to. All that is necessary is that it contains a * nid. * @param $grants @@ -3339,7 +3529,7 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) { * purposes, and assumes the caller has already performed a mass delete of * some form. */ -function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete = TRUE) { +function _node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { if ($delete) { $query = db_delete('node_access')->condition('nid', $node->nid); if ($realm) { @@ -3528,7 +3718,7 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { /** * Implements hook_form(). */ -function node_content_form(Node $node, $form_state) { +function node_content_form($node, $form_state) { // It is impossible to define a content type without implementing hook_form() // @todo: remove this requirement. $form = array(); @@ -3639,15 +3829,15 @@ function node_action_info() { /** * Sets the status of a node to 1 (published). * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_publish_action(Node $node, $context = array()) { +function node_publish_action($node, $context = array()) { $node->status = NODE_PUBLISHED; watchdog('action', 'Set @type %title to published.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3655,15 +3845,15 @@ function node_publish_action(Node $node, $context = array()) { /** * Sets the status of a node to 0 (unpublished). * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_unpublish_action(Node $node, $context = array()) { +function node_unpublish_action($node, $context = array()) { $node->status = NODE_NOT_PUBLISHED; watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3671,15 +3861,15 @@ function node_unpublish_action(Node $node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 1. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_make_sticky_action(Node $node, $context = array()) { +function node_make_sticky_action($node, $context = array()) { $node->sticky = NODE_STICKY; watchdog('action', 'Set @type %title to sticky.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3687,15 +3877,15 @@ function node_make_sticky_action(Node $node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 0. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_make_unsticky_action(Node $node, $context = array()) { +function node_make_unsticky_action($node, $context = array()) { $node->sticky = NODE_NOT_STICKY; watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3703,15 +3893,15 @@ function node_make_unsticky_action(Node $node, $context = array()) { /** * Sets the promote property of a node to 1. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_promote_action(Node $node, $context = array()) { +function node_promote_action($node, $context = array()) { $node->promote = NODE_PROMOTED; watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3719,15 +3909,15 @@ function node_promote_action(Node $node, $context = array()) { /** * Sets the promote property of a node to 0. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * @param $context * (optional) Array of additional information about what triggered the action. * Not used for this action. * * @ingroup actions */ -function node_unpromote_action(Node $node, $context = array()) { +function node_unpromote_action($node, $context = array()) { $node->promote = NODE_NOT_PROMOTED; watchdog('action', 'Removed @type %title from front page.', array('@type' => node_type_get_name($node), '%title' => $node->title)); } @@ -3735,21 +3925,21 @@ function node_unpromote_action(Node $node, $context = array()) { /** * Saves a node. * - * @param Drupal\node\Node $node + * @param $node * The node to be saved. * * @ingroup actions */ -function node_save_action(Node $node) { - $node->save(); +function node_save_action($node) { + node_save($node); watchdog('action', 'Saved @type %title', array('@type' => node_type_get_name($node), '%title' => $node->title)); } /** * Assigns ownership of a node to a user. * - * @param Drupal\node\Node $node - * A node entity to modify. + * @param $node + * A node object to modify. * @param $context * Array of additional information about what triggered the action. Includes * the following elements: @@ -3760,7 +3950,7 @@ function node_save_action(Node $node) { * @see node_assign_owner_action_submit() * @ingroup actions */ -function node_assign_owner_action(Node $node, $context) { +function node_assign_owner_action($node, $context) { $node->uid = $context['owner_uid']; $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField(); watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_type_get_name($node), '%title' => $node->title, '%name' => $owner_name)); @@ -3861,8 +4051,8 @@ function node_unpublish_by_keyword_action_submit($form, $form_state) { /** * Unpublishes a node containing certain keywords. * - * @param Drupal\node\Node $node - * A node entity to modify. + * @param $node + * A node object to modify. * @param $context * Array of additional information about what triggered the action. Includes * the following elements: @@ -3874,7 +4064,7 @@ function node_unpublish_by_keyword_action_submit($form, $form_state) { * * @ingroup actions */ -function node_unpublish_by_keyword_action(Node $node, $context) { +function node_unpublish_by_keyword_action($node, $context) { foreach ($context['keywords'] as $keyword) { $elements = node_view(clone $node); if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->title, $keyword) !== FALSE) { @@ -3945,6 +4135,55 @@ function node_modules_disabled($modules) { } /** + * Controller class for nodes. + * + * This extends the DrupalDefaultEntityController class, adding required + * special handling for node objects. + */ +class NodeController extends DrupalDefaultEntityController { + + /** + * Overrides DrupalDefaultEntityController::attachLoad(). + */ + protected function attachLoad(&$nodes, $revision_id = FALSE) { + // Create an array of nodes for each content type and pass this to the + // object type specific callback. + $typed_nodes = array(); + foreach ($nodes as $id => $entity) { + $typed_nodes[$entity->type][$id] = $entity; + } + + // Call object type specific callbacks on each typed array of nodes. + foreach ($typed_nodes as $node_type => $nodes_of_type) { + if (node_hook($node_type, 'load')) { + $function = node_type_get_base($node_type) . '_load'; + $function($nodes_of_type); + } + } + // Besides the list of nodes, pass one additional argument to + // hook_node_load(), containing a list of node types that were loaded. + $argument = array_keys($typed_nodes); + $this->hookLoadArguments = array($argument); + parent::attachLoad($nodes, $revision_id); + } + + /** + * Overrides DrupalDefaultEntityController::buildQuery(). + */ + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + // Ensure that uid is taken from the {node} table, + // alias timestamp to revision_timestamp and add revision_uid. + $query = parent::buildQuery($ids, $conditions, $revision_id); + $fields =& $query->getFields(); + unset($fields['timestamp']); + $query->addField('revision', 'timestamp', 'revision_timestamp'); + $fields['uid']['table'] = 'base'; + $query->addField('revision', 'uid', 'revision_uid'); + return $query; + } +} + +/** * Implements hook_file_download_access(). */ function node_file_download_access($field, $entity_type, $entity) { diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 868acbd..4e94b26 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -9,7 +9,6 @@ * @see node_menu() */ -use Drupal\node\Node; /** * Page callback: Presents the node editing form. @@ -82,12 +81,7 @@ function node_add($type) { global $user; $types = node_type_get_types(); - $node = entity_create('node', array( - 'uid' => $user->uid, - 'name' => (isset($user->name) ? $user->name : ''), - 'type' => $type, - 'langcode' => LANGUAGE_NOT_SPECIFIED, - )); + $node = (object) array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'langcode' => LANGUAGE_NOT_SPECIFIED); drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH); $output = drupal_get_form($type . '_node_form', $node); @@ -106,10 +100,7 @@ function node_form_validate($form, &$form_state) { // $form_state['node'] contains the actual entity being edited, but we must // not update it with form values that have not yet been validated, so we // create a pseudo-entity to use during validation. - $node = clone $form_state['node']; - foreach ($form_state['values'] as $key => $value) { - $node->{$key} = $value; - } + $node = (object) $form_state['values']; node_validate($node, $form, $form_state); entity_form_field_validate('node', $form, $form_state); } @@ -124,13 +115,16 @@ function node_form_validate($form, &$form_state) { * @see node_form_submit_build_node() * @ingroup forms */ -function node_form($form, &$form_state, Node $node) { +function node_form($form, &$form_state, $node) { global $user; // During initial form build, add the node entity to the form state for use // during form building and processing. During a rebuild, use what is in the // form state. if (!isset($form_state['node'])) { + if (!isset($node->title)) { + $node->title = NULL; + } node_object_prepare($node); $form_state['node'] = $node; } @@ -182,7 +176,7 @@ function node_form($form, &$form_state, Node $node) { $form['#node'] = $node; if (variable_get('node_type_language_' . $node->type, 0) && module_exists('language')) { - $languages = language_list(); + $languages = language_list(TRUE); $language_options = array(); foreach ($languages as $langcode => $language) { $language_options[$langcode] = $language->name; @@ -397,7 +391,7 @@ function node_form_build_preview($form, &$form_state) { /** * Generates a node preview. * - * @param Drupal\node\Node $node + * @param $node * The node to preview. * * @return @@ -405,7 +399,7 @@ function node_form_build_preview($form, &$form_state) { * * @see node_form_build_preview() */ -function node_preview(Node $node) { +function node_preview($node) { if (node_access('create', $node) || node_access('update', $node)) { _field_invoke_multiple('load', 'node', array($node->nid => $node)); // Load the user's name when needed. @@ -447,7 +441,7 @@ function node_preview(Node $node) { * * @param $variables * An associative array containing: - * - node: The node entity which is being previewed. + * - node: The node object which is being previewed. * * @see node_preview() * @ingroup themeable @@ -490,7 +484,7 @@ function theme_node_preview($variables) { function node_form_submit($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); - $node->save(); + node_save($node); $node_link = l(t('view'), 'node/' . $node->nid); $watchdog_args = array('@type' => $node->type, '%title' => $node->title); $t_args = array('@type' => node_type_get_name($node), '%title' => $node->title); @@ -662,17 +656,12 @@ function node_revision_revert_confirm($form, $form_state, $node_revision) { function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; $node_revision->revision = 1; + $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp))); - // The revision timestamp will be updated when the revision is saved. Keep the - // original one for the confirmation message. - $original_revision_timestamp = $node_revision->revision_timestamp; - - $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($original_revision_timestamp))); - - $node_revision->save(); + node_save($node_revision); watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid)); - drupal_set_message(t('@type %title has been reverted back to the revision from %revision-date.', array('@type' => node_type_get_name($node_revision), '%title' => $node_revision->title, '%revision-date' => format_date($original_revision_timestamp)))); + drupal_set_message(t('@type %title has been reverted back to the revision from %revision-date.', array('@type' => node_type_get_name($node_revision), '%title' => $node_revision->title, '%revision-date' => format_date($node_revision->revision_timestamp)))); $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions'; } diff --git a/core/modules/node/node.test b/core/modules/node/node.test index 655bc0b..058e0e7 100644 --- a/core/modules/node/node.test +++ b/core/modules/node/node.test @@ -60,7 +60,7 @@ class NodeLoadMultipleUnitTest extends NodeWebTestCase { $this->assertNoText($node4->title, t('Node title does not appear in the default listing.')); // Load nodes with only a condition. Nodes 3 and 4 will be loaded. - $nodes = node_load_multiple(FALSE, array('promote' => 0)); + $nodes = node_load_multiple(NULL, array('promote' => 0)); $this->assertEqual($node3->title, $nodes[$node3->nid]->title, t('Node was loaded.')); $this->assertEqual($node4->title, $nodes[$node4->nid]->title, t('Node was loaded.')); $count = count($nodes); @@ -245,13 +245,15 @@ class NodeRevisionsTestCase extends NodeWebTestCase { // the "log message" field), and check that the original log message is // preserved. $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage1'; - - $node = clone $node; - $node->title = $new_title; - $node->log = ''; - $node->revision = FALSE; - - $node->save(); + $updated_node = (object) array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'uid' => $node->uid, + 'type' => $node->type, + 'title' => $new_title, + 'log' => '', + ); + node_save($updated_node); $this->drupalGet('node/' . $node->nid); $this->assertText($new_title, t('New node title appears on the page.')); $node_revision = node_load($node->nid, NULL, TRUE); @@ -263,13 +265,15 @@ class NodeRevisionsTestCase extends NodeWebTestCase { // Save a new node revision without providing a log message, and check that // this revision has an empty log message. $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage2'; - - $node = clone $node; - $node->title = $new_title; - $node->revision = TRUE; - $node->log = NULL; - - $node->save(); + $updated_node = (object) array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'uid' => $node->uid, + 'type' => $node->type, + 'title' => $new_title, + 'revision' => 1, + ); + node_save($updated_node); $this->drupalGet('node/' . $node->nid); $this->assertText($new_title, 'New node title appears on the page.'); $node_revision = node_load($node->nid, NULL, TRUE); @@ -530,7 +534,7 @@ class NodeCreationTestCase extends NodeWebTestCase { ); try { - entity_create('node', $edit)->save(); + node_save((object) $edit); $this->fail(t('Expected exception has not been thrown.')); } catch (Exception $e) { @@ -1152,7 +1156,7 @@ class NodeSaveTestCase extends NodeWebTestCase { public static function getInfo() { return array( 'name' => 'Node save', - 'description' => 'Test $node->save() for saving content.', + 'description' => 'Test node_save() for saving content.', 'group' => 'Node', ); } @@ -1183,14 +1187,14 @@ class NodeSaveTestCase extends NodeWebTestCase { 'uid' => $this->web_user->uid, 'type' => 'article', 'nid' => $test_nid, - 'enforceIsNew' => TRUE, + 'is_new' => TRUE, ); - $node = node_submit(entity_create('node', $node)); + $node = node_submit((object) $node); // Verify that node_submit did not overwrite the user ID. $this->assertEqual($node->uid, $this->web_user->uid, t('Function node_submit() preserves user ID')); - $node->save(); + node_save($node); // Test the import. $node_by_nid = node_load($test_nid); $this->assertTrue($node_by_nid, t('Node load by node ID.')); @@ -1211,7 +1215,7 @@ class NodeSaveTestCase extends NodeWebTestCase { 'title' => $this->randomName(8), ); - entity_create('node', $edit)->save(); + node_save((object) $edit); $node = $this->drupalGetNodeByTitle($edit['title']); $this->assertEqual($node->created, REQUEST_TIME, t('Creating a node sets default "created" timestamp.')); $this->assertEqual($node->changed, REQUEST_TIME, t('Creating a node sets default "changed" timestamp.')); @@ -1220,14 +1224,14 @@ class NodeSaveTestCase extends NodeWebTestCase { $created = $node->created; $changed = $node->changed; - $node->save(); + node_save($node); $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); $this->assertEqual($node->created, $created, t('Updating a node preserves "created" timestamp.')); // Programmatically set the timestamps using hook_node_presave. $node->title = 'testing_node_presave'; - $node->save(); + node_save($node); $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE); $this->assertEqual($node->created, 280299600, t('Saving a node uses "created" timestamp set in presave hook.')); $this->assertEqual($node->changed, 979534800, t('Saving a node uses "changed" timestamp set in presave hook.')); @@ -1241,7 +1245,7 @@ class NodeSaveTestCase extends NodeWebTestCase { 'changed' => 979534800, // Drupal 1.0 release. ); - entity_create('node', $edit)->save(); + node_save((object) $edit); $node = $this->drupalGetNodeByTitle($edit['title']); $this->assertEqual($node->created, 280299600, t('Creating a node uses user-set "created" timestamp.')); $this->assertNotEqual($node->changed, 979534800, t('Creating a node doesn\'t use user-set "changed" timestamp.')); @@ -1250,7 +1254,7 @@ class NodeSaveTestCase extends NodeWebTestCase { $node->created = 979534800; $node->changed = 280299600; - $node->save(); + node_save($node); $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); $this->assertEqual($node->created, 979534800, t('Updating a node uses user-set "created" timestamp.')); $this->assertNotEqual($node->changed, 280299600, t('Updating a node doesn\'t use user-set "changed" timestamp.')); @@ -1262,20 +1266,20 @@ class NodeSaveTestCase extends NodeWebTestCase { */ function testDeterminingChanges() { // Initial creation. - $node = entity_create('node', array( + $node = (object) array( 'uid' => $this->web_user->uid, 'type' => 'article', 'title' => 'test_changes', - )); - $node->save(); + ); + node_save($node); // Update the node without applying changes. - $node->save(); + node_save($node); $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.'); // Apply changes. $node->title = 'updated'; - $node->save(); + node_save($node); // The hook implementations node_test_node_presave() and // node_test_node_update() determine changes and change the title. @@ -2410,6 +2414,7 @@ class NodeRevisionPermissionsTestCase extends NodeWebTestCase { // Test that access is FALSE for a node administrator with an invalid $node // or $op parameters. $admin_account = $this->accounts['admin']; + $this->assertFalse(_node_revision_access(FALSE, 'view', $admin_account), '_node_revision_access() returns FALSE with an invalid node.'); $this->assertFalse(_node_revision_access($revision, 'invalid-op', $admin_account), '_node_revision_access() returns FALSE with an invalid op.'); // Test that the $account parameter defaults to the "logged in" user. diff --git a/core/modules/node/node.tpl.php b/core/modules/node/node.tpl.php index c3db5ae..a251ecc 100644 --- a/core/modules/node/node.tpl.php +++ b/core/modules/node/node.tpl.php @@ -41,7 +41,7 @@ * the template. * * Other variables: - * - $node: Full node entity. Contains data that may not be safe. + * - $node: Full node object. Contains data that may not be safe. * - $type: Node type, i.e. page, article, etc. * - $comment_count: Number of comments attached to the node. * - $uid: User ID of the node author. diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index cda5fdf..d9e6e2f 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -7,8 +7,6 @@ * a special 'node test view' permission. */ -use Drupal\node\Node; - /** * Implements hook_node_grants(). */ @@ -28,7 +26,7 @@ function node_access_test_node_grants($account, $op) { /** * Implements hook_node_access_records(). */ -function node_access_test_node_access_records(Node $node) { +function node_access_test_node_access_records($node) { $grants = array(); // For NodeAccessBaseTableTestCase, only set records for private nodes. if (!variable_get('node_access_test_private') || $node->private) { @@ -201,28 +199,28 @@ function node_access_test_node_load($nodes, $types) { * Implements hook_node_predelete(). */ -function node_access_test_node_predelete(Node $node) { +function node_access_test_node_predelete($node) { db_delete('node_access_test')->condition('nid', $node->nid)->execute(); } /** * Implements hook_node_insert(). */ -function node_access_test_node_insert(Node $node) { +function node_access_test_node_insert($node) { _node_access_test_node_write($node); } /** * Implements hook_nodeapi_update(). */ -function node_access_test_node_update(Node $node) { +function node_access_test_node_update($node) { _node_access_test_node_write($node); } /** * Helper for node insert/update. */ -function _node_access_test_node_write(Node $node) { +function _node_access_test_node_write($node) { if (isset($node->private)) { db_merge('node_access_test') ->key(array('nid' => $node->nid)) diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module index f541d10..b0ebc14 100644 --- a/core/modules/node/tests/modules/node_test/node_test.module +++ b/core/modules/node/tests/modules/node_test/node_test.module @@ -6,8 +6,6 @@ * the Node module. */ -use Drupal\node\Node; - /** * Implements hook_node_load(). */ @@ -27,7 +25,7 @@ function node_test_node_load($nodes, $types) { /** * Implements hook_node_view(). */ -function node_test_node_view(Node $node, $view_mode) { +function node_test_node_view($node, $view_mode) { if ($view_mode == 'rss') { // Add RSS elements and namespaces when building the RSS feed. $node->rss_elements[] = array( @@ -68,7 +66,7 @@ function node_test_node_grants($account, $op) { /** * Implements hook_node_access_records(). */ -function node_test_node_access_records(Node $node) { +function node_test_node_access_records($node) { // Return nothing when testing for empty responses. if (!empty($node->disable_node_access)) { return; @@ -102,7 +100,7 @@ function node_test_node_access_records(Node $node) { /** * Implements hook_node_access_records_alter(). */ -function node_test_node_access_records_alter(&$grants, Node $node) { +function node_test_node_access_records_alter(&$grants, $node) { if (!empty($grants)) { foreach ($grants as $key => $grant) { // Alter grant from test_page_realm to test_alter_realm and modify the gid. @@ -125,7 +123,7 @@ function node_test_node_grants_alter(&$grants, $account, $op) { /** * Implements hook_node_presave(). */ -function node_test_node_presave(Node $node) { +function node_test_node_presave($node) { if ($node->title == 'testing_node_presave') { // Sun, 19 Nov 1978 05:00:00 GMT $node->created = 280299600; @@ -143,7 +141,7 @@ function node_test_node_presave(Node $node) { /** * Implements hook_node_update(). */ -function node_test_node_update(Node $node) { +function node_test_node_update($node) { // Determine changes on update. if (!empty($node->original) && $node->original->title == 'test_changes') { if ($node->original->title != $node->title) { diff --git a/core/modules/node/tests/modules/node_test_exception/node_test_exception.module b/core/modules/node/tests/modules/node_test_exception/node_test_exception.module index 570236b..0fe9f35 100644 --- a/core/modules/node/tests/modules/node_test_exception/node_test_exception.module +++ b/core/modules/node/tests/modules/node_test_exception/node_test_exception.module @@ -6,12 +6,10 @@ * the Node module. */ -use Drupal\node\Node; - /** * Implements hook_node_insert(). */ -function node_test_exception_node_insert(Node $node) { +function node_test_exception_node_insert($node) { if ($node->title == 'testing_transaction_exception') { throw new Exception('Test exception for rollback.'); } diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module index 3881320..141a3ab 100644 --- a/core/modules/openid/openid.module +++ b/core/modules/openid/openid.module @@ -83,15 +83,15 @@ function openid_help($path, $arg) { /** * Implements hook_user_insert(). */ -function openid_user_insert($account) { - if (!empty($account->openid_claimed_id)) { +function openid_user_insert(&$edit, $account) { + if (!empty($edit['openid_claimed_id'])) { // The user has registered after trying to log in via OpenID. if (variable_get('user_email_verification', TRUE)) { drupal_set_message(t('Once you have verified your e-mail address, you may log in via OpenID.')); } - user_set_authmaps($account, array('authname_openid' => $account->openid_claimed_id)); + user_set_authmaps($account, array('authname_openid' => $edit['openid_claimed_id'])); unset($_SESSION['openid']); - unset($account->openid_claimed_id); + unset($edit['openid_claimed_id']); } } @@ -263,13 +263,13 @@ function openid_form_user_register_form_alter(&$form, &$form_state) { $candidate_langcodes[] = $parts[0] . '-' . $parts[2]; $candidate_langcodes[] = $parts[0] . '-' . $parts[1] . '-' . $parts[2]; } - $enabled_languages = language_list(); + $enabled_languages = language_list(TRUE); // Iterate over the generated permutations starting with the longest (most // specific) strings. foreach (array_reverse($candidate_langcodes) as $candidate_langcode) { if (isset($enabled_languages[$candidate_langcode])) { - $form['language']['preferred_langcode']['#type'] = 'hidden'; - $form['language']['preferred_langcode']['#value'] = $candidate_langcode; + $form['locale']['preferred_langcode']['#type'] = 'hidden'; + $form['locale']['preferred_langcode']['#value'] = $candidate_langcode; // Skip the rest of the foreach to not overwrite the specific // language we found. break; diff --git a/core/modules/openid/openid.test b/core/modules/openid/openid.test index 1beea00..24e643f 100644 --- a/core/modules/openid/openid.test +++ b/core/modules/openid/openid.test @@ -592,7 +592,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase { $user = user_load_by_name('john'); $this->assertTrue($user, t('User was registered with right username.')); - $this->assertEqual($user->preferred_langcode, language_default()->langcode, t('User language is site default.')); + $this->assertFalse($user->preferred_langcode, t('No user language was saved.')); $this->assertFalse($user->data, t('No additional user info was saved.')); // Follow the one-time login that was sent in the welcome e-mail. @@ -632,7 +632,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase { $user = user_load_by_name('john'); $this->assertTrue($user, t('User was registered with right username.')); - $this->assertEqual($user->preferred_langcode, language_default()->langcode, t('User language is site default.')); + $this->assertFalse($user->preferred_langcode, t('No user language was saved.')); $this->assertFalse($user->data, t('No additional user info was saved.')); // Follow the one-time login that was sent in the welcome e-mail. diff --git a/core/modules/openid/tests/openid_test.module b/core/modules/openid/tests/openid_test.module index 5bd2f4d..ac49dbd 100644 --- a/core/modules/openid/tests/openid_test.module +++ b/core/modules/openid/tests/openid_test.module @@ -94,19 +94,8 @@ function openid_test_yadis_xrds() { // Only respond to XRI requests for one specific XRI. The is used to verify // that the XRI has been properly encoded. The "+" sign in the _xrd_r query // parameter is decoded to a space by PHP. - if (arg(3) == 'xri') { - if (variable_get('clean_url', 0)) { - if (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml') { - drupal_not_found(); - } - } - else { - // Drupal cannot properly emulate an XRI proxy resolver using unclean - // URLs, so the arguments gets messed up. - if (arg(4) . '/' . arg(5) != '@example*résumé;%25?_xrd_r=application/xrds xml') { - drupal_not_found(); - } - } + if (arg(3) == 'xri' && (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml')) { + drupal_not_found(); } drupal_add_http_header('Content-Type', 'application/xrds+xml'); print ' diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js index 19d2d64..aa90d9b 100644 --- a/core/modules/overlay/overlay-parent.js +++ b/core/modules/overlay/overlay-parent.js @@ -569,7 +569,7 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) { // If the link contains the overlay-restore class and the overlay-context // state is set, also update the parent window's location. var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string') - ? Drupal.settings.basePath + $.bbq.getState('overlay-context') + ? Drupal.url($.bbq.getState('overlay-context')) : null; href = this.fragmentizeLink($target.get(0), parentLocation); // Only override default behavior when left-clicking and user is not @@ -653,10 +653,10 @@ Drupal.overlay.eventhandlerOperateByURLFragment = function (event) { if (state) { // Append render variable, so the server side can choose the right // rendering and add child frame code to the page if needed. - var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' }); + var url = $.param.querystring(Drupal.url(state), { render: 'overlay' }); this.open(url); - this.resetActiveClass(this.getPath(Drupal.settings.basePath + state)); + this.resetActiveClass(this.getPath(url)); } // If there is no overlay URL in the fragment and the overlay is (still) // open, close the overlay. @@ -688,7 +688,7 @@ Drupal.overlay.eventhandlerSyncURLFragment = function (event) { if (this.isOpen) { var expected = $.bbq.getState('overlay'); // This is just a sanity check, so we're comparing paths, not query strings. - if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) { + if (this.getPath(Drupal.url(expected)) != this.getPath(this.iframeWindow.document.location)) { // There may have been a redirect inside the child overlay window that the // parent wasn't aware of. Update the parent URL fragment appropriately. var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location); @@ -752,10 +752,8 @@ Drupal.overlay.fragmentizeLink = function (link, parentLocation) { return link.href; } - // Determine the link's original destination. Set ignorePathFromQueryString to - // true to prevent transforming this link into a clean URL while clean URLs - // may be disabled. - var path = this.getPath(link, true); + // Determine the link's original destination. + var path = this.getPath(link); // Preserve existing query and fragment parameters in the URL, except for // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment. var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash; @@ -783,7 +781,7 @@ Drupal.overlay.refreshRegions = function (data) { (function (regionName, regionSelector) { var $region = $(regionSelector); Drupal.detachBehaviors($region); - $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) { + $.get(Drupal.url(Drupal.settings.overlay.ajaxCallback + '/' + regionName), function (newElement) { $region.replaceWith($(newElement)); Drupal.attachBehaviors($region, Drupal.settings); }); @@ -827,13 +825,11 @@ Drupal.overlay.resetActiveClass = function(activePath) { * * @param link * Link object or string to get the Drupal path from. - * @param ignorePathFromQueryString - * Boolean whether to ignore path from query string if path appears empty. * * @return * The Drupal path. */ -Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { +Drupal.overlay.getPath = function (link) { if (typeof link == 'string') { // Create a native Link object, so we can use its object methods. link = $(link.link(link)).get(0); @@ -844,15 +840,7 @@ Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { if (path.charAt(0) != '/') { path = '/' + path; } - path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), ''); - if (path == '' && !ignorePathFromQueryString) { - // If the path appears empty, it might mean the path is represented in the - // query string (clean URLs are not used). - var match = new RegExp('([?&])q=(.+)([&#]|$)').exec(link.search); - if (match && match.length == 4) { - path = match[2]; - } - } + path = path.replace(new RegExp(Drupal.settings.basePath + Drupal.settings.scriptPath), ''); return path; }; diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index e2ecd19..6d1d8cb 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -5,6 +5,8 @@ * Displays the Drupal administration interface in an overlay. */ +use Symfony\Component\HttpFoundation\Response; + /** * Implements hook_help(). */ @@ -19,7 +21,7 @@ function overlay_help($path, $arg) { } /** - * Implements hook_menu(). + * Implements hook_menu() */ function overlay_menu() { $items['overlay-ajax/%'] = array( @@ -32,7 +34,7 @@ function overlay_menu() { $items['overlay/dismiss-message'] = array( 'title' => '', 'page callback' => 'overlay_user_dismiss_message', - 'access arguments' => array('access overlay'), + 'access callback' => 'overlay_user_dismiss_message_access', 'type' => MENU_CALLBACK, ); return $items; @@ -102,9 +104,9 @@ function overlay_form_user_profile_form_alter(&$form, &$form_state) { /** * Implements hook_user_presave(). */ -function overlay_user_presave($account) { - if (isset($account->overlay)) { - $account->data['overlay'] = $account->overlay; +function overlay_user_presave(&$edit, $account) { + if (isset($edit['overlay'])) { + $edit['data']['overlay'] = $edit['overlay']; } } @@ -143,7 +145,7 @@ function overlay_init() { // If this page shouldn't be rendered inside the overlay, redirect to the // parent. elseif (!path_is_admin($current_path)) { - overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('q', 'render')))); + overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('render')))); } // Indicate that we are viewing an overlay child page. @@ -300,24 +302,41 @@ function overlay_page_alter(&$page) { /** * Menu callback; dismisses the overlay accessibility message for this user. + * + * @see overlay_user_dismiss_message_access() + * @see overlay_menu() */ function overlay_user_dismiss_message() { global $user; + user_save(user_load($user->uid), array('data' => array('overlay_message_dismissed' => 1))); + drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.')); + // Destination is normally given. Go to the user profile as a fallback. + drupal_goto('user/' . $user->uid . '/edit'); +} + +/** + * Access callback; determines access to dismiss the overlay accessibility message. + * + * @see overlay_user_dismiss_message() + * @see overlay_menu() + */ +function overlay_user_dismiss_message_access() { + global $user; + if (!user_access('access overlay')) { + return FALSE; + } // It's unlikely, but possible that "access overlay" permission is granted to // the anonymous role. In this case, we do not display the message to disable - // the overlay, so there is nothing to dismiss. Also, protect against - // cross-site request forgeries by validating a token. - if (empty($user->uid) || !isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'overlay')) { - return MENU_ACCESS_DENIED; + // the overlay, so there is nothing to dismiss. + if (empty($user->uid)) { + return FALSE; } - else { - $account = user_load($user->uid); - $account->data['overlay_message_dismissed'] = 1; - $account->save(); - drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.')); - // Destination is normally given. Go to the user profile as a fallback. - drupal_goto('user/' . $user->uid . '/edit'); + // Protect against cross-site request forgeries by validating a token. + $token = request()->query->get('token'); + if (!isset($token) || !drupal_valid_token($token, 'overlay')) { + return FALSE; } + return TRUE; } /** @@ -669,7 +688,8 @@ function overlay_overlay_child_initialize() { // it to the same content rendered in overlay_exit(), at the end of the page // request. This allows us to check if anything actually did change, and, if // so, trigger an immediate Ajax refresh of the parent window. - if (!empty($_POST) || isset($_GET['token'])) { + $token = request()->query->get('token'); + if (!empty($_POST) || isset($token)) { foreach (overlay_supplemental_regions() as $region) { overlay_store_rendered_content($region, overlay_render_region($region)); } @@ -981,5 +1001,5 @@ function overlay_trigger_refresh() { * @see Drupal.overlay.refreshRegions() */ function overlay_ajax_render_region($region) { - print overlay_render_region($region); + return new Response(overlay_render_region($region)); } diff --git a/core/modules/path/path.admin.inc b/core/modules/path/path.admin.inc index 9e22cff..e69589d 100644 --- a/core/modules/path/path.admin.inc +++ b/core/modules/path/path.admin.inc @@ -129,7 +129,7 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali '#maxlength' => 255, '#size' => 45, '#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#required' => TRUE, ); $form['alias'] = array( @@ -139,13 +139,13 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali '#maxlength' => 255, '#size' => 45, '#description' => t('Specify an alternative path by which this data can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#required' => TRUE, ); // A hidden value unless language.module is enabled. if (module_exists('language')) { - $languages = language_list(); + $languages = language_list(TRUE); foreach ($languages as $langcode => $language) { $language_options[$langcode] = $language->name; } diff --git a/core/modules/path/path.module b/core/modules/path/path.module index 1d638ad..10eb127 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -5,8 +5,6 @@ * Enables users to rename URLs. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -187,7 +185,7 @@ function path_form_element_validate($element, &$form_state, $complete_form) { /** * Implements hook_node_insert(). */ -function path_node_insert(Node $node) { +function path_node_insert($node) { if (isset($node->path)) { $path = $node->path; $path['alias'] = trim($path['alias']); @@ -204,7 +202,7 @@ function path_node_insert(Node $node) { /** * Implements hook_node_update(). */ -function path_node_update(Node $node) { +function path_node_update($node) { if (isset($node->path)) { $path = $node->path; $path['alias'] = trim($path['alias']); @@ -225,7 +223,7 @@ function path_node_update(Node $node) { /** * Implements hook_node_predelete(). */ -function path_node_predelete(Node $node) { +function path_node_predelete($node) { // Delete all aliases associated with this node. path_delete(array('source' => 'node/' . $node->nid)); } diff --git a/core/modules/path/path.test b/core/modules/path/path.test index 0066071..4dcd611 100644 --- a/core/modules/path/path.test +++ b/core/modules/path/path.test @@ -532,8 +532,9 @@ class PathMonolingualTestCase extends PathTestCase { $edit = array('site_default' => 'fr'); $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); - // Delete English. - $this->drupalPost('admin/config/regional/language/delete/en', array(), t('Delete')); + // Disable English. + $edit = array('languages[en][enabled]' => FALSE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); // Verify that French is the only language. $this->assertFalse(language_multilingual(), t('Site is mono-lingual')); diff --git a/core/modules/poll/poll.module b/core/modules/poll/poll.module index beba2ed..c801579 100644 --- a/core/modules/poll/poll.module +++ b/core/modules/poll/poll.module @@ -6,8 +6,6 @@ * choice questions. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -608,10 +606,10 @@ function poll_delete($node) { /** * Return content for 'latest poll' block. * - * @param Drupal\node\Node $node - * The node entity to load. + * @param $node + * The node object to load. */ -function poll_block_latest_poll_view(Node $node) { +function poll_block_latest_poll_view($node) { global $user; $output = ''; diff --git a/core/modules/poll/poll.test b/core/modules/poll/poll.test index cba67ba..3ab570c 100644 --- a/core/modules/poll/poll.test +++ b/core/modules/poll/poll.test @@ -227,7 +227,7 @@ class PollCreateTestCase extends PollWebTestCase { 'weight' => 1000, ); - $node->save(); + node_save($node); $this->drupalGet('poll'); $this->clickLink($title); diff --git a/core/modules/rdf/rdf.test b/core/modules/rdf/rdf.test index c160ccb..6c7635f 100644 --- a/core/modules/rdf/rdf.test +++ b/core/modules/rdf/rdf.test @@ -5,8 +5,6 @@ * Tests for rdf.module. */ -use Drupal\node\Node; - class RdfMappingHookTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -622,10 +620,10 @@ class RdfTrackerAttributesTestCase extends DrupalWebTestCase { * * Tests the tracker page for RDFa markup. * - * @param Node $node + * @param $node * The node just created. */ - function _testBasicTrackerRdfaMarkup(Node $node) { + function _testBasicTrackerRdfaMarkup($node) { $url = url('node/' . $node->nid); $user = ($node->uid == 0) ? 'Anonymous user' : 'Registered user'; diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 9a6a11a..3f54577 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -5,8 +5,6 @@ * Enables site-wide keyword searching. */ -use Drupal\node\Node; - /** * Matches all 'N' Unicode character classes (numbers) */ @@ -239,7 +237,7 @@ function search_menu() { } /** - * Determines access for the ?q=search path. + * Determines access for the 'search' path. */ function search_is_active() { // This path cannot be accessed if there are no active modules. @@ -800,7 +798,7 @@ function search_touch_node($nid) { /** * Implements hook_node_update_index(). */ -function search_node_update_index(Node $node) { +function search_node_update_index($node) { // Transplant links to a node into the target node. $result = db_query("SELECT caption FROM {search_node_links} WHERE nid = :nid", array(':nid' => $node->nid), array('target' => 'slave')); $output = array(); @@ -815,7 +813,7 @@ function search_node_update_index(Node $node) { /** * Implements hook_node_update(). */ -function search_node_update(Node $node) { +function search_node_update($node) { // Reindex the node when it is updated. The node is automatically indexed // when it is added, simply by being added to the node table. search_touch_node($node->nid); diff --git a/core/modules/search/search.test b/core/modules/search/search.test index dd63ec4..3fcfb0c 100644 --- a/core/modules/search/search.test +++ b/core/modules/search/search.test @@ -450,14 +450,8 @@ class SearchRankingTestCase extends SearchWebTestCase { variable_set('statistics_count_content_views', 1); // Then View one of the nodes a bunch of times. - // Manually calling statistics.php, simulating ajax behavior. - $nid = $nodes['views'][1]->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; for ($i = 0; $i < 5; $i ++) { - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->drupalGet('node/' . $nodes['views'][1]->nid); } // Test each of the possible rankings. @@ -828,7 +822,7 @@ class SearchCommentTestCase extends SearchWebTestCase { // Hide comments. $this->drupalLogin($this->admin_user); $node->comment = 0; - $node->save(); + node_save($node); // Invoke search index update. $this->drupalLogout(); @@ -1992,14 +1986,16 @@ class SearchLanguageTestCase extends SearchWebTestCase { $this->drupalPost('search/node', $edit, t('Advanced search')); $this->assertFieldByXPath('//input[@name="keys"]', 'language:fr', t('Language filter added to query.')); - // Change the default language and delete English. + // Change the default language and disable English. $path = 'admin/config/regional/language'; $this->drupalGet($path); $this->assertFieldChecked('edit-site-default-en', t('English is the default language.')); $edit = array('site_default' => 'fr'); $this->drupalPost(NULL, $edit, t('Save configuration')); $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.')); - $this->drupalPost('admin/config/regional/language/delete/en', array(), t('Delete')); + $edit = array('languages[en][enabled]' => FALSE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-languages-en-enabled', t('Language disabled.')); // Check that there are again no languages displayed. $this->drupalGet('search/node'); diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index 75c12b4..9010f90 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -489,7 +489,7 @@ function _shortcut_link_form_elements($shortcut_link = NULL) { '#title' => t('Path'), '#size' => 40, '#maxlength' => 255, - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#default_value' => $shortcut_link['link_path'], ); diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index aa43503..73b6385 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -657,7 +657,7 @@ function shortcut_preprocess_page(&$variables) { // we do not want to display it on "access denied" or "page not found" // pages). if (shortcut_set_edit_access() && ($item = menu_get_item()) && $item['access']) { - $link = $_GET['q']; + $link = current_path(); $query_parameters = drupal_get_query_parameters(); if (!empty($query_parameters)) { $link .= '?' . drupal_http_build_query($query_parameters); diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php index 540dc0e..e87c196 100644 --- a/core/modules/simpletest/drupal_web_test_case.php +++ b/core/modules/simpletest/drupal_web_test_case.php @@ -902,7 +902,7 @@ class DrupalWebTestCase extends DrupalTestCase { * (optional) Whether to reset the internal node_load() cache. * * @return - * A node entity matching $title. + * A node object matching $title. */ function drupalGetNodeByTitle($title, $reset = FALSE) { $nodes = node_load_multiple(array(), array('title' => $title), $reset); @@ -918,7 +918,7 @@ class DrupalWebTestCase extends DrupalTestCase { * An associative array of settings to change from the defaults, keys are * node properties, for example 'title' => 'Hello, world!'. * @return - * Created node entity. + * Created node object. */ protected function drupalCreateNode($settings = array()) { // Populate defaults array. @@ -962,8 +962,8 @@ class DrupalWebTestCase extends DrupalTestCase { ); $settings['body'][$settings['langcode']][0] += $body; - $node = entity_create('node', $settings); - $node->save(); + $node = (object) $settings; + node_save($node); // Small hack to link revisions to our test user. db_update('node_revision') @@ -1126,8 +1126,7 @@ class DrupalWebTestCase extends DrupalTestCase { $edit['roles'] = array($rid => $rid); } - $account = entity_create('user', $edit); - $account->save(); + $account = user_save(drupal_anonymous_user(), $edit); $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login')); if (empty($account->uid)) { @@ -1231,7 +1230,7 @@ class DrupalWebTestCase extends DrupalTestCase { * * @see drupalCreateUser() */ - protected function drupalLogin($user) { + protected function drupalLogin(stdClass $user) { if ($this->loggedInUser) { $this->drupalLogout(); } @@ -1318,7 +1317,6 @@ class DrupalWebTestCase extends DrupalTestCase { $this->originalConfigSignatureKey = $GLOBALS['config_signature_key']; $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); $this->originalProfile = drupal_get_profile(); - $clean_url_original = variable_get('clean_url', 0); // Set to English to prevent exceptions from utf8_truncate() from t() // during install if the current language is not 'en'. @@ -1435,7 +1433,6 @@ class DrupalWebTestCase extends DrupalTestCase { // Restore necessary variables. variable_set('install_task', 'done'); - variable_set('clean_url', $clean_url_original); variable_set('site_mail', 'simpletest@example.com'); variable_set('date_default_timezone', date_default_timezone_get()); // Set up English language. @@ -1836,6 +1833,7 @@ class DrupalWebTestCase extends DrupalTestCase { * Retrieve a Drupal path or an absolute path and JSON decode the result. */ protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) { + $headers[] = 'X-Requested-With: XMLHttpRequest'; return drupal_json_decode($this->drupalGet($path, $options, $headers)); } @@ -2043,6 +2041,7 @@ class DrupalWebTestCase extends DrupalTestCase { } $content = $this->content; $drupal_settings = $this->drupalSettings; + $headers[] = 'X-Requested-With: XMLHttpRequest'; // Get the Ajax settings bound to the triggering element. if (!isset($ajax_settings)) { diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 9bd735e..cbc946c 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -159,7 +159,6 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') { * Batch operation callback. */ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { - simpletest_classloader_register(); // Get working values. if (!isset($context['sandbox']['max'])) { // First iteration: initialize working values. @@ -292,9 +291,6 @@ function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALS * a static variable. In order to list tests provided by disabled modules * hook_registry_files_alter() is used to forcefully add them to the registry. * - * PSR-0 classes are found by searching the designated directory for each module - * for files matching the PSR-0 standard. - * * @return * An array of tests keyed with the groups specified in each of the tests * getInfo() method and then keyed by the test class. An example of the array @@ -315,10 +311,6 @@ function simpletest_test_get_all() { $groups = &drupal_static(__FUNCTION__); if (!$groups) { - // Make sure that namespaces for disabled modules are registered so that the - // checks below will find them. - simpletest_classloader_register(); - // Load test information from cache if available, otherwise retrieve the // information from each tests getInfo() method. if ($cache = cache()->get('simpletest')) { @@ -326,33 +318,8 @@ function simpletest_test_get_all() { } else { // Select all clases in files ending with .test. - // @todo: Remove this once all tests have been ported to PSR-0. $classes = db_query("SELECT name FROM {registry} WHERE type = :type AND filename LIKE :name", array(':type' => 'class', ':name' => '%.test'))->fetchCol(); - // Select all PSR-0 classes in the Tests namespace of all modules. - $system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed(); - - foreach ($system_list as $name => $filename) { - // Build directory in which the test files would reside. - $tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests'; - // Scan it for test files if it exists. - if (is_dir($tests_dir)) { - $files = file_scan_directory($tests_dir, '/.*\.php/'); - if (!empty($files)) { - $basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/'; - foreach ($files as $file) { - // Convert the file name into the namespaced class name. - $replacements = array( - '/' => '\\', - $basedir => '', - '.php' => '', - ); - $classes[] = strtr($file->uri, $replacements); - } - } - } - } - // Check that each class has a getInfo() method and store the information // in an array keyed with the group specified in the test information. $groups = array(); @@ -389,19 +356,6 @@ function simpletest_test_get_all() { } /** - * Registers namespaces for disabled modules. - */ -function simpletest_classloader_register() { - // Get the cached test modules list and register a test namespace for each. - $disabled_modules = db_query("SELECT name, filename FROM {system} WHERE status = 0")->fetchAllKeyed(); - if ($disabled_modules) { - foreach ($disabled_modules as $name => $filename) { - drupal_classloader_register($name, dirname($filename)); - } - } -} - -/** * Implements hook_registry_files_alter(). * * Add the test files for disabled modules so that we get a list containing diff --git a/core/modules/simpletest/simpletest.pages.inc b/core/modules/simpletest/simpletest.pages.inc index a98b445..8ac1ee2 100644 --- a/core/modules/simpletest/simpletest.pages.inc +++ b/core/modules/simpletest/simpletest.pages.inc @@ -183,7 +183,6 @@ function theme_simpletest_test_table($variables) { function simpletest_test_form_submit($form, &$form_state) { // Get list of tests. $tests_list = array(); - simpletest_classloader_register(); foreach ($form_state['values'] as $class_name => $value) { // Since class_exists() will likely trigger an autoload lookup, // we do the fast check first. @@ -234,8 +233,6 @@ function simpletest_result_form($form, &$form_state, $test_id) { '#debug' => 0, ); - simpletest_classloader_register(); - // Cycle through each test group. $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status'))); $form['result']['results'] = array(); diff --git a/core/modules/simpletest/simpletest.test b/core/modules/simpletest/simpletest.test index 4cecbe6..185a71a 100644 --- a/core/modules/simpletest/simpletest.test +++ b/core/modules/simpletest/simpletest.test @@ -341,8 +341,6 @@ class SimpleTestBrowserTestCase extends DrupalWebTestCase { * Test DrupalWebTestCase::getAbsoluteUrl(). */ function testGetAbsoluteUrl() { - // Testbed runs with Clean URLs disabled, so disable it here. - variable_set('clean_url', 0); $url = 'user/login'; $this->drupalGet($url); diff --git a/core/modules/statistics/statistics.js b/core/modules/statistics/statistics.js deleted file mode 100644 index 65793d3..0000000 --- a/core/modules/statistics/statistics.js +++ /dev/null @@ -1,12 +0,0 @@ -(function ($) { - $(document).ready(function() { - var nid = Drupal.settings.statistics.nid; - var basePath = Drupal.settings.basePath - $.ajax({ - type: "POST", - cache: false, - url: basePath+"core/modules/statistics/statistics.php", - data: "nid="+nid - }); - }); -})(jQuery); diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 69d5f5b..40b9b36 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -5,8 +5,6 @@ * Logs and displays access statistics for a site. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -43,6 +41,7 @@ function statistics_help($path, $arg) { } } + /** * Implements hook_exit(). * @@ -58,6 +57,22 @@ function statistics_exit() { // in which case we need to bootstrap to the session phase anyway. drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); + if (variable_get('statistics_count_content_views', 0)) { + // We are counting content views. + if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == NULL) { + // A node has been viewed, so update the node's counters. + db_merge('node_counter') + ->key(array('nid' => arg(1))) + ->fields(array( + 'daycount' => 1, + 'totalcount' => 1, + 'timestamp' => REQUEST_TIME, + )) + ->expression('daycount', 'daycount + 1') + ->expression('totalcount', 'totalcount + 1') + ->execute(); + } + } if (variable_get('statistics_enable_access_log', 0)) { drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); @@ -67,7 +82,9 @@ function statistics_exit() { db_insert('accesslog') ->fields(array( 'title' => truncate_utf8(strip_tags(drupal_get_title()), 255), - 'path' => truncate_utf8($_GET['q'], 255), + // @todo The public function current_path() is not available on a cache + // hit. + 'path' => truncate_utf8(_current_path(), 255), 'url' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', 'hostname' => ip_address(), 'uid' => $user->uid, @@ -99,20 +116,7 @@ function statistics_permission() { /** * Implements hook_node_view(). */ -function statistics_node_view(Node $node, $view_mode) { - if (!empty($node->nid) && $view_mode == 'full') { - $node->content['#attached']['js'] = array( - drupal_get_path('module', 'statistics') . '/statistics.js' => array( - 'scope' => 'footer' - ), - ); - $settings = array('nid' => $node->nid); - $node->content['#attached']['js'][] = array( - 'data' => array('statistics' => $settings), - 'type' => 'setting', - ); - } - +function statistics_node_view($node, $view_mode) { if ($view_mode != 'rss') { if (user_access('view post access counter')) { $statistics = statistics_get($node->nid); @@ -410,7 +414,7 @@ function _statistics_format_item($title, $path) { /** * Implements hook_node_predelete(). */ -function statistics_node_predelete(Node $node) { +function statistics_node_predelete($node) { // clean up statistics table when node is deleted db_delete('node_counter') ->condition('nid', $node->nid) diff --git a/core/modules/statistics/statistics.php b/core/modules/statistics/statistics.php deleted file mode 100644 index 040dc19..0000000 --- a/core/modules/statistics/statistics.php +++ /dev/null @@ -1,33 +0,0 @@ -key(array('nid' => $nid)) - ->fields(array( - 'daycount' => 1, - 'totalcount' => 1, - 'timestamp' => REQUEST_TIME, - )) - ->expression('daycount', 'daycount + 1') - ->expression('totalcount', 'totalcount + 1') - ->execute(); - } -} - diff --git a/core/modules/statistics/statistics.test b/core/modules/statistics/statistics.test index 6f19e37..6748ad4 100644 --- a/core/modules/statistics/statistics.test +++ b/core/modules/statistics/statistics.test @@ -104,13 +104,6 @@ class StatisticsLoggingTestCase extends DrupalWebTestCase { // Verify logging of an uncached page. $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Testing an uncached page.')); $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); $this->assertTrue(is_array($log) && count($log) == 1, t('Page request was logged.')); @@ -120,8 +113,6 @@ class StatisticsLoggingTestCase extends DrupalWebTestCase { // Verify logging of a cached page. $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Testing a cached page.')); $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); $this->assertTrue(is_array($log) && count($log) == 2, t('Page request was logged.')); @@ -132,8 +123,6 @@ class StatisticsLoggingTestCase extends DrupalWebTestCase { // Test logging from authenticated users $this->drupalLogin($this->auth_user); $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); // Check the 6th item since login and account pages are also logged $this->assertTrue(is_array($log) && count($log) == 6, t('Page request was logged.')); @@ -152,8 +141,13 @@ class StatisticsLoggingTestCase extends DrupalWebTestCase { $this->assertTrue(is_array($log) && count($log) == 7, t('Page request was logged.')); $this->assertEqual(array_intersect_key($log[6], $expected), $expected); - // Create a path longer than 255 characters. - $long_path = $this->randomName(256); + // Create a path longer than 255 characters. Drupal's .htaccess file + // instructs Apache to test paths against the file system before routing to + // index.php. Many file systems restrict file names to 255 characters + // (http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits), and + // Apache returns a 403 when testing longer file names, but the total path + // length is not restricted. + $long_path = $this->randomName(127) . '/' . $this->randomName(128); // Test that the long path is properly truncated when logged. $this->drupalGet($long_path); @@ -230,13 +224,6 @@ class StatisticsReportsTestCase extends StatisticsTestCase { // Visit a node to have something show up in the block. $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->blocking_user->uid)); $this->drupalGet('node/' . $node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); // Configure and save the block. $block = block_load('statistics', 'popular'); @@ -371,13 +358,6 @@ class StatisticsAdminTestCase extends DrupalWebTestCase { // Hit the node. $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->drupalGet('admin/reports/pages'); $this->assertText('node/1', t('Test node found.')); @@ -385,11 +365,9 @@ class StatisticsAdminTestCase extends DrupalWebTestCase { // Hit the node again (the counter is incremented after the hit, so // "1 view" will actually be shown when the node is hit the second time). $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->assertText('1 view', t('Node is viewed once.')); $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->assertText('2 views', t('Node is viewed 2 times.')); } @@ -400,13 +378,6 @@ class StatisticsAdminTestCase extends DrupalWebTestCase { variable_set('statistics_count_content_views', 1); $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $result = db_select('node_counter', 'n') ->fields('n', array('nid')) @@ -466,15 +437,7 @@ class StatisticsAdminTestCase extends DrupalWebTestCase { variable_set('statistics_flush_accesslog_timer', 1); $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $this->assertText('1 view', t('Node is viewed once.')); $this->drupalGet('admin/reports/pages'); @@ -523,13 +486,6 @@ class StatisticsTokenReplaceTestCase extends StatisticsTestCase { // Hit the node. $this->drupalGet('node/' . $node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); $statistics = statistics_get($node->nid); // Generate and test tokens. diff --git a/core/modules/system/html.tpl.php b/core/modules/system/html.tpl.php index 59a52b9..960eefb 100644 --- a/core/modules/system/html.tpl.php +++ b/core/modules/system/html.tpl.php @@ -22,8 +22,6 @@ * - slogan: The slogan of the site, if any, and if there is no title. * - $head: Markup for the HEAD section (including meta tags, keyword tags, and * so on). - * - $default_mobile_metatags: TRUE if default mobile metatags for responsive - * design should be displayed. * - $styles: Style tags necessary to import all CSS files for the page. * - $scripts: Script tags necessary to load the JavaScript files and settings * for the page. @@ -45,12 +43,6 @@ > - - - - - - <?php print $head_title; ?> diff --git a/core/modules/system/page.tpl.php b/core/modules/system/page.tpl.php index 5c53d2b..f77fdad 100644 --- a/core/modules/system/page.tpl.php +++ b/core/modules/system/page.tpl.php @@ -50,7 +50,7 @@ * - $action_links (array): Actions local to the page, such as 'Add menu' on the * menu administration interface. * - $feed_icons: A string of all feed icons for the current page. - * - $node: The node entity, if there is an automatically-loaded node + * - $node: The node object, if there is an automatically-loaded node * associated with the page, and the node ID is the second argument * in the page's path (e.g. node/12345 and node/12345/revisions, but not * comment/reply/12345). diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index aac0c3d..3bd3b89 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -5,6 +5,8 @@ * Admin page callbacks for the system module. */ +use Symfony\Component\HttpFoundation\Response; + /** * Menu callback; Provide the administration overview page. */ @@ -1494,7 +1496,7 @@ function system_site_information_settings() { '#default_value' => (variable_get('site_frontpage') != 'user' ? drupal_get_path_alias(variable_get('site_frontpage', 'user')) : ''), '#size' => 40, '#description' => t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default content feed.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['front_page']['default_nodes_main'] = array( '#type' => 'select', '#title' => t('Number of posts on front page'), @@ -1513,7 +1515,7 @@ function system_site_information_settings() { '#default_value' => variable_get('site_403', ''), '#size' => 40, '#description' => t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['error_page']['site_404'] = array( '#type' => 'textfield', @@ -1521,7 +1523,7 @@ function system_site_information_settings() { '#default_value' => variable_get('site_404', ''), '#size' => 40, '#description' => t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['#validate'][] = 'system_site_information_settings_validate'; @@ -2206,96 +2208,6 @@ function system_site_maintenance_mode() { } /** - * Form builder; Configure clean URL settings. - * - * @ingroup forms - * @see system_settings_form() - */ -function system_clean_url_settings($form, &$form_state) { - $available = FALSE; - $conflict = FALSE; - - // If the request URI is a clean URL, clean URLs must be available. - // Otherwise, run a test. - if (strpos(request_uri(), '?q=') === FALSE && strpos(request_uri(), '&q=') === FALSE) { - $available = TRUE; - } - else { - $request = drupal_http_request($GLOBALS['base_url'] . '/admin/config/search/clean-urls/check'); - // If the request returns HTTP 200, clean URLs are available. - if (isset($request->code) && $request->code == 200) { - $available = TRUE; - // If the user started the clean URL test, provide explicit feedback. - if (isset($form_state['input']['clean_url_test_execute'])) { - drupal_set_message(t('The clean URL test passed.')); - } - } - else { - // If the test failed while clean URLs are enabled, make sure clean URLs - // can be disabled. - if (variable_get('clean_url', 0)) { - $conflict = TRUE; - // Warn the user of a conflicting situation, unless after processing - // a submitted form. - if (!isset($form_state['input']['op'])) { - drupal_set_message(t('Clean URLs are enabled, but the clean URL test failed. Uncheck the box below to disable clean URLs.'), 'warning'); - } - } - // If the user started the clean URL test, provide explicit feedback. - elseif (isset($form_state['input']['clean_url_test_execute'])) { - drupal_set_message(t('The clean URL test failed.'), 'warning'); - } - } - } - - // Show the enable/disable form if clean URLs are available or if the user - // must be able to resolve a conflicting setting. - if ($available || $conflict) { - $form['clean_url'] = array( - '#type' => 'checkbox', - '#title' => t('Enable clean URLs'), - '#default_value' => variable_get('clean_url', 0), - '#description' => t('Use URLs like example.com/user instead of example.com/?q=user.'), - ); - $form = system_settings_form($form); - if ($conflict) { - // $form_state['redirect'] needs to be set to the non-clean URL, - // otherwise the setting is not saved. - $form_state['redirect'] = url('', array('query' => array('q' => '/admin/config/search/clean-urls'))); - } - } - // Show the clean URLs test form. - else { - drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); - - $form_state['redirect'] = url('admin/config/search/clean-urls'); - $form['clean_url_description'] = array( - '#type' => 'markup', - '#markup' => '

' . t('Use URLs like example.com/user instead of example.com/?q=user.'), - ); - // Explain why the user is seeing this page and what to expect after - // clicking the 'Run the clean URL test' button. - $form['clean_url_test_result'] = array( - '#type' => 'markup', - '#markup' => '

' . t('Clean URLs cannot be enabled. If you are directed to this page or to a Page not found (404) error after testing for clean URLs, see the online handbook.', array('@handbook' => 'http://drupal.org/node/15365')) . '

', - ); - $form['actions'] = array( - '#type' => 'actions', - 'clean_url_test' => array( - '#type' => 'submit', - '#value' => t('Run the clean URL test'), - ), - ); - $form['clean_url_test_execute'] = array( - '#type' => 'hidden', - '#value' => 1, - ); - } - - return $form; -} - -/** * Menu callback: displays the site status report. Can also be used as a pure check. * * @param $check @@ -2357,6 +2269,9 @@ function system_batch_page() { if ($output === FALSE) { drupal_access_denied(); } + elseif ($output instanceof Response) { + return $output; + } elseif (isset($output)) { // Force a page without blocks or messages to // display a list of collected messages later. diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index c6e1288..7c9c49b 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -497,7 +497,7 @@ function hook_page_build(&$page) { */ function hook_menu_get_item_alter(&$router_item, $path, $original_map) { // When retrieving the router item for the current path... - if ($path == $_GET['q']) { + if ($path == current_path()) { // ...call a function that prepares something for this request. mymodule_prepare_something(); } @@ -1153,7 +1153,7 @@ function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) { * $page['content']['system_main']['nodes'][$nid]['body'] * // Array of links attached to the node (add comments, read more). * $page['content']['system_main']['nodes'][$nid]['links'] - * // The node entity itself. + * // The node object itself. * $page['content']['system_main']['nodes'][$nid]['#node'] * // The results pager. * $page['content']['system_main']['pager'] @@ -1183,7 +1183,7 @@ function hook_page_alter(&$page) { * Perform alterations before a form is rendered. * * One popular use of this hook is to add form elements to the node form. When - * altering a node form, the node entity can be accessed at $form['#node']. + * altering a node form, the node object can be accessed at $form['#node']. * * In addition to hook_form_alter(), which is called for all forms, there are * two more specific form hooks available. The first, @@ -1934,40 +1934,31 @@ function hook_xmlrpc_alter(&$methods) { } /** - * Log an event message. + * Log an event message * * This hook allows modules to route log events to custom destinations, such as * SMS, Email, pager, syslog, ...etc. * - * @param array $log_entry + * @param $log_entry * An associative array containing the following keys: - * - type: The type of message for this entry. - * - user: The user object for the user who was logged in when the event - * happened. - * - request_uri: The request URI for the page the event happened in. - * - referer: The page that referred the user to the page where the event - * occurred. + * - type: The type of message for this entry. For contributed modules, this is + * normally the module name. Do not use 'debug', use severity WATCHDOG_DEBUG instead. + * - user: The user object for the user who was logged in when the event happened. + * - request_uri: The Request URI for the page the event happened in. + * - referer: The page that referred the use to the page where the event occurred. * - ip: The IP address where the request for the page came from. - * - timestamp: The UNIX timestamp of the date/time the event occurred. - * - severity: The severity of the message; one of the following values as - * defined in @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink - * - WATCHDOG_EMERGENCY: Emergency, system is unusable. - * - WATCHDOG_ALERT: Alert, action must be taken immediately. - * - WATCHDOG_CRITICAL: Critical conditions. - * - WATCHDOG_ERROR: Error conditions. - * - WATCHDOG_WARNING: Warning conditions. - * - WATCHDOG_NOTICE: Normal but significant conditions. - * - WATCHDOG_INFO: Informational messages. - * - WATCHDOG_DEBUG: Debug-level messages. - * - link: An optional link provided by the module that called the watchdog() - * function. - * - message: The text of the message to be logged. Variables in the message - * are indicated by using placeholder strings alongside the variables - * argument to declare the value of the placeholders. See t() for - * documentation on how the message and variable parameters interact. - * - variables: An array of variables to be inserted into the message on - * display. Will be NULL or missing if a message is already translated or if - * the message is not possible to translate. + * - timestamp: The UNIX timestamp of the date/time the event occurred + * - severity: One of the following values as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html + * WATCHDOG_EMERGENCY Emergency: system is unusable + * WATCHDOG_ALERT Alert: action must be taken immediately + * WATCHDOG_CRITICAL Critical: critical conditions + * WATCHDOG_ERROR Error: error conditions + * WATCHDOG_WARNING Warning: warning conditions + * WATCHDOG_NOTICE Notice: normal but significant condition + * WATCHDOG_INFO Informational: informational messages + * WATCHDOG_DEBUG Debug: debug-level messages + * - link: an optional link provided by the module that called the watchdog() function. + * - message: The text of the message to be logged. */ function hook_watchdog(array $log_entry) { global $base_url, $language_interface; diff --git a/core/modules/system/system.cron.js b/core/modules/system/system.cron.js index af17dab..aa5b351 100644 --- a/core/modules/system/system.cron.js +++ b/core/modules/system/system.cron.js @@ -9,7 +9,7 @@ Drupal.behaviors.cronCheck = { $('body').once('cron-check', function() { // Only execute the cron check if its the right time. if (Math.round(new Date().getTime() / 1000.0) > settings.cronCheck) { - $.get(settings.basePath + 'system/run-cron-check'); + $.get(Drupal.url('system/run-cron-check')); } }); } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 51603ff..acd0e2d 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -862,7 +862,6 @@ function system_schema() { 'length' => 255, 'not null' => TRUE, 'default' => '', - 'binary' => TRUE, ), 'uri' => array( 'description' => 'The URI to access the file (either local or remote).', @@ -870,7 +869,6 @@ function system_schema() { 'length' => 255, 'not null' => TRUE, 'default' => '', - 'binary' => TRUE, ), 'langcode' => array( 'description' => 'The {language}.langcode of this file.', @@ -1849,6 +1847,13 @@ function system_update_8007() { } /** + * Remove the 'clean_url' configuration variable. + */ +function system_update_8008() { + variable_del('clean_url'); +} + +/** * @} End of "defgroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/system/system.js b/core/modules/system/system.js index 98da1a6..ab00426 100644 --- a/core/modules/system/system.js +++ b/core/modules/system/system.js @@ -20,55 +20,6 @@ Drupal.hideEmailAdministratorCheckbox = function () { }; /** - * Internal function to check using Ajax if clean URLs can be enabled on the - * settings page. - * - * This function is not used to verify whether or not clean URLs - * are currently enabled. - */ -Drupal.behaviors.cleanURLsSettingsCheck = { - attach: function (context, settings) { - // This behavior attaches by ID, so is only valid once on a page. - // Also skip if we are on an install page, as Drupal.cleanURLsInstallCheck will handle - // the processing. - if (!($('#edit-clean-url').length) || $('#edit-clean-url.install').once('clean-url').length) { - return; - } - var url = settings.basePath + 'admin/config/search/clean-urls/check'; - $.ajax({ - url: location.protocol + '//' + location.host + url, - dataType: 'json', - success: function () { - // Check was successful. Redirect using a "clean URL". This will force the form that allows enabling clean URLs. - location = settings.basePath +"admin/config/search/clean-urls"; - } - }); - } -}; - -/** - * Internal function to check using Ajax if clean URLs can be enabled on the - * install page. - * - * This function is not used to verify whether or not clean URLs - * are currently enabled. - */ -Drupal.cleanURLsInstallCheck = function () { - var url = location.protocol + '//' + location.host + Drupal.settings.basePath + 'admin/config/search/clean-urls/check'; - // Submit a synchronous request to avoid database errors associated with - // concurrent requests during install. - $.ajax({ - async: false, - url: url, - dataType: 'json', - success: function () { - // Check was successful. - $('#edit-clean-url').attr('value', 1); - } - }); -}; - -/** * When a field is filled out, apply its value to other fields that will likely * use the same value. In the installer this is used to populate the * administrator e-mail address with the same value as the site e-mail address. diff --git a/core/modules/system/system.maintenance.css b/core/modules/system/system.maintenance.css index 5543c2d..16a2f35 100644 --- a/core/modules/system/system.maintenance.css +++ b/core/modules/system/system.maintenance.css @@ -1,4 +1,3 @@ - /** * Update styles */ @@ -46,10 +45,3 @@ ol.task-list li.active { font-weight: bold; } - -/** - * Installation clean URLs - */ -#clean-url.install { - display: none; -} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index dfb8936..4650469 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -88,7 +88,7 @@ function system_help($path, $arg) { $output .= '
' . t('Performing system maintenance') . '
'; $output .= '
' . t('In order for the site and its modules to continue to operate well, a set of routine administrative operations must run on a regular basis. The System module manages this task by making use of a system cron job. You can verify the status of cron tasks by visiting the Status report page. For more information, see the online handbook entry for configuring cron jobs. You can set up cron job by visiting Cron configuration page', array('@status' => url('admin/reports/status'), '@handbook' => 'http://drupal.org/cron', '@cron' => url('admin/config/system/cron'))) . '
'; $output .= '
' . t('Configuring basic site settings') . '
'; - $output .= '
' . t('The System module also handles basic configuration options for your site, including Date and time settings, File system settings, Clean URL support, Site name and other information, and a Maintenance mode for taking your site temporarily offline.', array('@date-time-settings' => url('admin/config/regional/date-time'), '@file-system' => url('admin/config/media/file-system'), '@clean-url' => url('admin/config/search/clean-urls'), '@site-info' => url('admin/config/system/site-information'), '@maintenance-mode' => url('admin/config/development/maintenance'))) . '
'; + $output .= '
' . t('The System module also handles basic configuration options for your site, including Date and time settings, File system settings, Site name and other information, and a Maintenance mode for taking your site temporarily offline.', array('@date-time-settings' => url('admin/config/regional/date-time'), '@file-system' => url('admin/config/media/file-system'), '@site-info' => url('admin/config/system/site-information'), '@maintenance-mode' => url('admin/config/development/maintenance'))) . '
'; $output .= '
' . t('Configuring actions') . '
'; $output .= '
' . t('Actions are individual tasks that the system can do, such as unpublishing a piece of content or banning a user. Other modules can fire these actions when certain system events happen; for example, when a new post is added or when a user logs in. Modules may also provide additional actions. Visit the Actions page to configure actions.', array('@actions' => url('admin/config/system/actions'))) . '
'; $output .= ''; @@ -971,23 +971,6 @@ function system_menu() { 'access arguments' => array('access administration pages'), 'file' => 'system.admin.inc', ); - $items['admin/config/search/clean-urls'] = array( - 'title' => 'Clean URLs', - 'description' => 'Enable or disable clean URLs for your site.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('system_clean_url_settings'), - 'access arguments' => array('administer site configuration'), - 'file' => 'system.admin.inc', - 'weight' => 5, - ); - $items['admin/config/search/clean-urls/check'] = array( - 'title' => 'Clean URL check', - 'page callback' => 'drupal_json_output', - 'page arguments' => array(array('status' => TRUE)), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - 'file' => 'system.admin.inc', - ); // System settings. $items['admin/config/system'] = array( @@ -1850,8 +1833,7 @@ function system_authorized_get_url(array $options = array()) { global $base_url; // Force https if available, regardless of what the caller specifies. $options['https'] = TRUE; - // We prefix with $base_url so we get a full path even if clean URLs are - // disabled. + // Prefix with $base_url so url() treats it as an external link. return url($base_url . '/core/authorize.php', $options); } @@ -1995,8 +1977,11 @@ function system_add_module_assets() { * Implements hook_custom_theme(). */ function system_custom_theme() { - if (user_access('view the administration theme') && path_is_admin(current_path())) { - return variable_get('admin_theme'); + if ($request = request()) { + $path = ltrim($request->getPathInfo(), '/'); + if (user_access('view the administration theme') && path_is_admin($path)) { + return variable_get('admin_theme'); + } } } @@ -2021,14 +2006,15 @@ function system_form_user_register_form_alter(&$form, &$form_state) { } /** - * Implements hook_user_presave(). + * Implements hook_user_insert(). */ -function system_user_presave($account) { +function system_user_presave(&$edit, $account) { if (variable_get('configurable_timezones', 1) && empty($account->timezone) && !variable_get('user_default_timezone', DRUPAL_USER_TIMEZONE_DEFAULT)) { $account->timezone = variable_get('date_default_timezone', ''); } } + /** * Implements hook_user_login(). */ @@ -3212,7 +3198,7 @@ function system_send_email_action_submit($form, $form_state) { * Sends an e-mail message. * * @param object $entity - * An optional node entity, which will be added as $context['node'] if + * An optional node object, which will be added as $context['node'] if * provided. * @param array $context * Array with the following elements: @@ -3286,7 +3272,7 @@ function system_message_action_submit($form, $form_state) { * Sends a message to the current user's screen. * * @param object $entity - * An optional node entity, which will be added as $context['node'] if + * An optional node object, which will be added as $context['node'] if * provided. * @param array $context * Array with the following elements: @@ -3920,15 +3906,17 @@ function system_date_format_save($date_format, $dfid = 0) { drupal_write_record('date_formats', $info, $keys); } - $languages = language_list(); + // Retrieve an array of language objects for enabled languages. + $languages = language_list(TRUE); $locale_format = array(); $locale_format['type'] = $date_format['type']; $locale_format['format'] = $date_format['format']; - // Check if the suggested language codes are configured. + // Check if the suggested language codes are configured and enabled. if (!empty($date_format['locales'])) { foreach ($date_format['locales'] as $langcode) { + // Only proceed if language is enabled. if (isset($languages[$langcode])) { $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE type = :type AND language = :language', 0, 1, array(':type' => $date_format['type'], ':language' => $langcode))->fetchField(); if (!$is_existing) { diff --git a/core/modules/system/system.test b/core/modules/system/system.test index 16348cc..5f4594a 100644 --- a/core/modules/system/system.test +++ b/core/modules/system/system.test @@ -946,47 +946,6 @@ class AdminMetaTagTestCase extends DrupalWebTestCase { } } -class DefaultMobileMetaTagsTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Default mobile meta tags', - 'description' => 'Confirm that the default mobile meta tags appear as expected.', - 'group' => 'System' - ); - } - - function setUp() { - parent::setUp(); - $this->default_metatags = array( - 'MobileOptimized' => '', - 'HandheldFriendly' => '', - 'viewport' => '', - 'cleartype' => '' - ); - } - - /** - * Verifies that the default mobile meta tags are added. - */ - public function testDefaultMetaTagsExist() { - $this->drupalGet(''); - foreach ($this->default_metatags as $name => $metatag) { - $this->assertRaw($metatag, 'Default Mobile meta tag "' . $name . '" displayed properly.', t('System')); - } - } - - /** - * Verifies that the default mobile meta tags can be removed. - */ - public function testRemovingDefaultMetaTags() { - module_enable(array('system_module_test')); - $this->drupalGet(''); - foreach ($this->default_metatags as $name => $metatag) { - $this->assertNoRaw($metatag, 'Default Mobile meta tag "' . $name . '" removed properly.', t('System')); - } - } -} - /** * Tests custom access denied functionality. */ @@ -2900,6 +2859,6 @@ class SystemIndexPhpTest extends DrupalWebTestCase { $this->assertResponse(200, t('Make sure index.php?q=user returns a valid page.')); $this->drupalGet($index_php .'/user', array('external' => TRUE)); - $this->assertResponse(404, t("Make sure index.php/user returns a 'page not found'.")); + $this->assertResponse(200, t("Make sure index.php/user returns a valid page.")); } } diff --git a/core/modules/system/tests/ajax.test b/core/modules/system/tests/ajax.test index 4f254f8..49f1632 100644 --- a/core/modules/system/tests/ajax.test +++ b/core/modules/system/tests/ajax.test @@ -80,7 +80,7 @@ class AJAXFrameworkTestCase extends AJAXTestCase { // drupal_add_js(). $expected = array( 'command' => 'settings', - 'settings' => array('basePath' => base_path(), 'ajax' => 'test'), + 'settings' => array('ajax' => 'test'), ); $this->assertCommand($commands, $expected, t('ajax_render() loads settings added with drupal_add_js().')); diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test index f0ac957..15d565a 100644 --- a/core/modules/system/tests/common.test +++ b/core/modules/system/tests/common.test @@ -97,7 +97,7 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * Tests for active class in l() function. */ function testLActiveClass() { - $link = l($this->randomName(), $_GET['q']); + $link = l($this->randomName(), current_path()); $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); } @@ -106,7 +106,7 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { */ function testLCustomClass() { $class = $this->randomName(); - $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); + $link = l($this->randomName(), current_path(), array('attributes' => array('class' => array($class)))); $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class))); $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); } @@ -128,19 +128,8 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { ), ), 'c' => 3, - 'q' => 'foo/bar', ); - // Default arguments. - $result = $_GET; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters(), $result, t("\$_GET['q'] was removed.")); - - // Default exclusion. - $result = $original; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters($original), $result, t("'q' was removed.")); - // First-level exclusion. $result = $original; unset($result['b']); @@ -176,14 +165,21 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * Test drupal_parse_url(). */ function testDrupalParseUrl() { - // Relative URL. - $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => 'foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); + // Relative, absolute, and external URLs, without/with explicit script path, + // without/with Drupal path. + foreach (array('', '/', 'http://drupal.org/') as $absolute) { + foreach (array('', 'index.php/') as $script) { + foreach (array('', 'foo/bar') as $path) { + $url = $absolute . $script . $path . '?foo=bar&bar=baz&baz#foo'; + $expected = array( + 'path' => $absolute . $script . $path, + 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), + 'fragment' => 'foo', + ); + $this->assertEqual(drupal_parse_url($url), $expected, t('URL parsed correctly.')); + } + } + } // Relative URL that is known to confuse parse_url(). $url = 'foo/bar:1'; @@ -194,47 +190,10 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { ); $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); - // Absolute URL. - $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => '/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.')); - - // External URL testing. - $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; - // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. + $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.')); - // Test the parsing of absolute URLs. - $result = array( - 'path' => 'http://drupal.org/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('External URL parsed correctly.')); - - // Verify proper parsing of URLs when clean URLs are disabled. - $result = array( - 'path' => 'foo/bar', - 'query' => array('bar' => 'baz'), - 'fragment' => 'foo', - ); - // Non-clean URLs #1: Absolute URL generated by url(). - $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #2: Relative URL generated by url(). - $url = '?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #3: URL generated by url() on non-Apache webserver. - $url = 'index.php?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.')); - // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.')); @@ -245,75 +204,41 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * assert all that works when clean URLs are on and off. */ function testUrl() { - global $base_url; - - foreach (array(FALSE, TRUE) as $absolute) { - // Get the expected start of the path string. - $base = $absolute ? $base_url . '/' : base_path(); - $absolute_string = $absolute ? 'absolute' : NULL; - - // Disable Clean URLs. - $GLOBALS['conf']['clean_url'] = 0; - - $url = $base . '?q=node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo#0'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '0', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - // Enable Clean URLs. - $GLOBALS['conf']['clean_url'] = 1; - - $url = $base . 'node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); + global $base_url, $script_path; + + $script_path_original = $script_path; + foreach (array('', 'index.php/') as $script_path) { + foreach (array(FALSE, TRUE) as $absolute) { + // Get the expected start of the path string. + $base = ($absolute ? $base_url . '/' : base_path()) . $script_path; + $absolute_string = $absolute ? 'absolute' : NULL; + + $url = $base . 'node/123'; + $result = url('node/123', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123#foo'; + $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo'; + $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo=bar&bar=baz'; + $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo#bar'; + $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base; + $result = url('', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + } } + $script_path = $script_path_original; } /** @@ -1230,8 +1155,8 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { $this->assertTrue(array_key_exists('core/misc/html5.js', $javascript), t('html5.js is added when file is added.')); $this->assertTrue(array_key_exists('core/misc/collapse.js', $javascript), t('JavaScript files are correctly added.')); $this->assertEqual(base_path(), $javascript['settings']['data'][0]['basePath'], t('Base path JavaScript setting is correctly set.')); - url('', array('prefix' => &$prefix)); - $this->assertEqual(empty($prefix) ? '' : $prefix, $javascript['settings']['data'][1]['pathPrefix'], t('Path prefix JavaScript setting is correctly set.')); + $this->assertIdentical($GLOBALS['script_path'], $javascript['settings']['data'][1]['scriptPath'], t('Script path JavaScript setting is correctly set.')); + $this->assertIdentical('', $javascript['settings']['data'][2]['pathPrefix'], t('Path prefix JavaScript setting is correctly set.')); } /** @@ -1239,8 +1164,8 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { */ function testAddSetting() { $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); - $this->assertEqual(280342800, $javascript['settings']['data'][2]['dries'], t('JavaScript setting is set correctly.')); - $this->assertEqual('rocks', $javascript['settings']['data'][2]['drupal'], t('The other JavaScript setting is set correctly.')); + $this->assertEqual(280342800, $javascript['settings']['data'][3]['dries'], t('JavaScript setting is set correctly.')); + $this->assertEqual('rocks', $javascript['settings']['data'][3]['drupal'], t('The other JavaScript setting is set correctly.')); } /** @@ -1269,8 +1194,9 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { $javascript = drupal_get_js('header'); $this->assertTrue(strpos($javascript, 'basePath') > 0, t('Rendered JavaScript header returns basePath setting.')); - $this->assertTrue(strpos($javascript, 'core/misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.')); + $this->assertTrue(strpos($javascript, 'scriptPath') > 0, t('Rendered JavaScript header returns scriptPath setting.')); $this->assertTrue(strpos($javascript, 'pathPrefix') > 0, t('Rendered JavaScript header returns pathPrefix setting.')); + $this->assertTrue(strpos($javascript, 'core/misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.')); // Test whether drupal_add_js can be used to override a previous setting. $this->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, t('Rendered JavaScript header returns custom setting.')); @@ -1424,48 +1350,6 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { } /** - * Tests JavaScript aggregation when files are added to a different scope. - */ - function testAggregationOrder() { - // Enable JavaScript aggregation. - config('system.performance')->set('preprocess_js', 1)->save(); - drupal_static_reset('drupal_add_js'); - - // Add two JavaScript files to the current request and build the cache. - drupal_add_js('core/misc/ajax.js'); - drupal_add_js('core/misc/autocomplete.js'); - - $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); - - // Store the expected key for the first item in the cache. - $cache = array_keys(variable_get('drupal_js_cache_files', array())); - $expected_key = $cache[0]; - - // Reset variables and add a file in a different scope first. - variable_del('drupal_js_cache_files'); - drupal_static_reset('drupal_add_js'); - drupal_add_js('some/custom/javascript_file.js', array('scope' => 'footer')); - drupal_add_js('core/misc/ajax.js'); - drupal_add_js('core/misc/autocomplete.js'); - - // Rebuild the cache. - $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); - - // Compare the expected key for the first file to the current one. - $cache = array_keys(variable_get('drupal_js_cache_files', array())); - $key = $cache[0]; - $this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.'); - } - - /** * Test JavaScript ordering. */ function testRenderOrder() { @@ -1863,15 +1747,6 @@ class CommonDrupalRenderTestCase extends DrupalWebTestCase { ':title' => $element['#title'], )); - $element = array( - '#type' => 'fieldset', - '#title' => $this->randomName(), - '#collapsible' => TRUE, - ); - $this->assertRenderedElement($element, '//fieldset[contains(@class, :class)]', array( - ':class' => 'collapsible', - )); - $element['item'] = array( '#type' => 'item', '#title' => $this->randomName(), @@ -1974,8 +1849,8 @@ class CommonValidUrlUnitTestCase extends DrupalUnitTestCase { 'example.com/index.html#pagetop', 'example.com:8080', 'subdomain.example.com', - 'example.com/index.php?q=node', - 'example.com/index.php?q=node¶m=false', + 'example.com/index.php/node', + 'example.com/index.php/node?param=false', 'user@www.example.com', 'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop', '127.0.0.1', @@ -2022,8 +1897,8 @@ class CommonValidUrlUnitTestCase extends DrupalUnitTestCase { $valid_relative_urls = array( 'paren(the)sis', 'index.html#pagetop', - 'index.php?q=node', - 'index.php?q=node¶m=false', + 'index.php/node', + 'index.php/node?param=false', 'login.php?do=login&style=%23#pagetop', ); diff --git a/core/modules/system/tests/file.test b/core/modules/system/tests/file.test index 173d05a..00bda25 100644 --- a/core/modules/system/tests/file.test +++ b/core/modules/system/tests/file.test @@ -2042,19 +2042,6 @@ class FileSaveTest extends FileHookTestCase { $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File'); $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly.")); $this->assertEqual($loaded_file->langcode, 'en', t("Langcode was saved correctly.")); - - // Try to insert a second file with the same name apart from case insensitivity - // to ensure the 'uri' index allows for filenames with different cases. - $file = (object) array( - 'uid' => 1, - 'filename' => 'DRUPLICON.txt', - 'uri' => 'public://DRUPLICON.txt', - 'filemime' => 'text/plain', - 'timestamp' => 1, - 'status' => FILE_STATUS_PERMANENT, - ); - file_put_contents($file->uri, 'hello world'); - file_save($file); } } @@ -2436,7 +2423,7 @@ class FileDownloadTest extends FileTestCase { $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded); - $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0'); + $this->checkUrl('private', '', $basename, $base_url . '/index.php/system/files/' . $basename_encoded, '0'); } /** diff --git a/core/modules/system/tests/menu.test b/core/modules/system/tests/menu.test index 0a10ec8..65f4443 100644 --- a/core/modules/system/tests/menu.test +++ b/core/modules/system/tests/menu.test @@ -262,11 +262,11 @@ class MenuRouterTestCase extends DrupalWebTestCase { $this->drupalGet('user/login'); // Check that we got to 'user'. - $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to q=user on accessing q=user/login")); + $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to user on accessing user/login")); // user/register should redirect to user/UID/edit. $this->drupalGet('user/register'); - $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to q=user/UID/edit on accessing q=user/register")); + $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to user/UID/edit on accessing user/register")); } /** @@ -1321,7 +1321,7 @@ class MenuBreadcrumbTestCase extends MenuWebTestCase { // the breadcrumb based on taxonomy term hierarchy. $parent_tid = 0; foreach ($tags as $name => $null) { - $terms = taxonomy_term_load_multiple(FALSE, array('name' => $name)); + $terms = taxonomy_term_load_multiple(NULL, array('name' => $name)); $term = reset($terms); $tags[$name]['term'] = $term; if ($parent_tid) { diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index f42b5f5..c727c51 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -1932,9 +1932,7 @@ function form_test_form_user_register_form_alter(&$form, &$form_state) { ); // If requested, add the test field by attaching the node page form. if (!empty($_REQUEST['field'])) { - $node = entity_create('node', array( - 'type' => 'page', - )); + $node = (object)array('type' => 'page'); field_attach_form('node', $node, $form, $form_state); } } @@ -1952,12 +1950,12 @@ function form_test_user_register_form_rebuild($form, &$form_state) { */ function form_test_two_instances() { global $user; - $node1 = entity_create('node', array( + $node1 = (object) array( 'uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => 'page', 'langcode' => LANGUAGE_NOT_SPECIFIED, - )); + ); $node2 = clone($node1); $return['node_form_1'] = drupal_get_form('page_node_form', $node1); $return['node_form_2'] = drupal_get_form('page_node_form', $node2); diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index 0b954ae..6e6bb4d 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -537,7 +537,7 @@ function menu_test_static_variable($value = NULL) { * Implements hook_menu_site_status_alter(). */ function menu_test_menu_site_status_alter(&$menu_site_status, $path) { - // Allow access to ?q=menu_login_callback even if in maintenance mode. + // Allow access to menu_login_callback even if in maintenance mode. if ($menu_site_status == MENU_SITE_OFFLINE && $path == 'menu_login_callback') { $menu_site_status = MENU_SITE_ONLINE; } diff --git a/core/modules/system/tests/modules/system_module_test/system_module_test.info b/core/modules/system/tests/modules/system_module_test/system_module_test.info deleted file mode 100644 index 5184a33..0000000 --- a/core/modules/system/tests/modules/system_module_test/system_module_test.info +++ /dev/null @@ -1,6 +0,0 @@ -name = "System test" -description = "Provides hook implementations for testing System module functionality." -package = Testing -version = VERSION -core = 8.x -hidden = TRUE diff --git a/core/modules/system/tests/modules/system_module_test/system_module_test.module b/core/modules/system/tests/modules/system_module_test/system_module_test.module deleted file mode 100644 index ec90b15..0000000 --- a/core/modules/system/tests/modules/system_module_test/system_module_test.module +++ /dev/null @@ -1,10 +0,0 @@ - 'theme_test.template_test', ); - $items['theme_test_foo'] = array( - 'variables' => array('foo' => NULL), - ); return $items; } @@ -145,10 +142,3 @@ function theme_test_preprocess_html(&$variables) { $variables['html_attributes_array']['theme_test_html_attribute'] = 'theme test html attribute value'; $variables['body_attributes_array']['theme_test_body_attribute'] = 'theme test body attribute value'; } - -/** - * Theme function for testing theme('theme_test_foo'). - */ -function theme_theme_test_foo($variables) { - return $variables['foo']; -} diff --git a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module index 9287ff5..a5ca13d 100644 --- a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module +++ b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module @@ -30,8 +30,8 @@ function url_alter_test_foo() { * Implements hook_url_inbound_alter(). */ function url_alter_test_url_inbound_alter(&$path, $original_path, $path_language) { - if (!request_path() && !empty($_GET['q'])) { - drupal_set_message("\$_GET['q'] is non-empty with an empty request path."); + if (!request_path() && current_path()) { + drupal_set_message("current_path() is non-empty with an empty request path."); } // Rewrite user/username to user/uid. diff --git a/core/modules/system/tests/path.test b/core/modules/system/tests/path.test index fe6f734..f02a9b4 100644 --- a/core/modules/system/tests/path.test +++ b/core/modules/system/tests/path.test @@ -201,11 +201,11 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase { } /** - * Tests that $_GET['q'] is initialized when the request path is empty. + * Tests that current_path() is initialized when the request path is empty. */ function testGetQInitialized() { $this->drupalGet(''); - $this->assertText("\$_GET['q'] is non-empty with an empty request path.", "\$_GET['q'] is initialized with an empty request path."); + $this->assertText("current_path() is non-empty with an empty request path.", "current_path() is initialized with an empty request path."); } /** @@ -221,7 +221,7 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase { protected function assertUrlOutboundAlter($original, $final) { // Test outbound altering. $result = url($original); - $base_path = base_path() . (variable_get('clean_url', '0') ? '' : '?q='); + $base_path = base_path() . $GLOBALS['script_path']; $result = substr($result, strlen($base_path)); $this->assertIdentical($result, $final, t('Altered outbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result))); } diff --git a/core/modules/system/tests/session.test b/core/modules/system/tests/session.test index 5cc8fe9..0e6a074 100644 --- a/core/modules/system/tests/session.test +++ b/core/modules/system/tests/session.test @@ -41,8 +41,8 @@ class SessionTestCase extends DrupalWebTestCase { // Verify that the session is regenerated if a module calls exit // in hook_user_login(). + user_save($user, array('name' => 'session_test_user')); $user->name = 'session_test_user'; - $user->save(); $this->drupalGet('session-test/id'); $matches = array(); preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); @@ -513,7 +513,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { */ protected function httpsUrl($url) { global $base_url; - return $base_url . '/core/modules/system/tests/https.php?q=' . $url; + return $base_url . '/core/modules/system/tests/https.php/' . $url; } /** @@ -527,7 +527,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { */ protected function httpUrl($url) { global $base_url; - return $base_url . '/core/modules/system/tests/http.php?q=' . $url; + return $base_url . '/core/modules/system/tests/http.php/' . $url; } } diff --git a/core/modules/system/tests/tablesort.test b/core/modules/system/tests/tablesort.test index 9c068f8..aaea505 100644 --- a/core/modules/system/tests/tablesort.test +++ b/core/modules/system/tests/tablesort.test @@ -49,7 +49,7 @@ class TableSortTest extends DrupalUnitTestCase { $headers = array('foo', 'bar', 'baz'); // Reset $_GET to prevent parameters from Simpletest and Batch API ending // up in $ts['query']. - $_GET = array('q' => 'jahwohl'); + $_GET = array(); $expected_ts = array( 'name' => 'foo', 'sql' => '', @@ -64,7 +64,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', // This should not override the table order because only complex // headers are overridable. 'order' => 'bar', @@ -77,7 +76,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', 'sort' => 'DESC', // Add an unrelated parameter to ensure that tablesort will include // it in the links that it creates. @@ -107,7 +105,6 @@ class TableSortTest extends DrupalUnitTestCase { ); // Reset $_GET from previous assertion. $_GET = array( - 'q' => 'jahwohl', 'order' => '2', ); $ts = tablesort_init($headers); @@ -124,7 +121,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', // This should not override the table order because this header does not // exist. 'order' => 'bar', @@ -144,7 +140,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', 'order' => '1', 'sort' => 'ASC', // Add an unrelated parameter to ensure that tablesort will include diff --git a/core/modules/system/tests/theme.test b/core/modules/system/tests/theme.test index c1ba08f..70d3656 100644 --- a/core/modules/system/tests/theme.test +++ b/core/modules/system/tests/theme.test @@ -69,14 +69,14 @@ class ThemeUnitTest extends DrupalWebTestCase { * Ensure page-front template suggestion is added when on front page. */ function testFrontPageThemeSuggestion() { - $q = $_GET['q']; - // Set $_GET['q'] to node because theme_get_suggestions() will query it to - // see if we are on the front page. + $original_path = _current_path(); + // Set the current path to node because theme_get_suggestions() will query + // it to see if we are on the front page. variable_set('site_frontpage', 'node'); - $_GET['q'] = 'node'; - $suggestions = theme_get_suggestions(explode('/', $_GET['q']), 'page'); + _current_path('node'); + $suggestions = theme_get_suggestions(array('node'), 'page'); // Set it back to not annoy the batch runner. - $_GET['q'] = $q; + _current_path($original_path); $this->assertTrue(in_array('page__front', $suggestions), t('Front page template was suggested.')); } @@ -154,19 +154,6 @@ class ThemeUnitTest extends DrupalWebTestCase { $this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), t('Base theme\'s default settings values can be overridden by subtheme.')); $this->assertIdentical(theme_get_setting('basetheme_only', 'test_subtheme'), 'base theme value', t('Base theme\'s default settings values are inherited by subtheme.')); } - - /** - * Ensures the theme registry is rebuilt when modules are disabled/enabled. - */ - function testRegistryRebuild() { - $this->assertIdentical(theme('theme_test_foo', array('foo' => 'a')), 'a', 'The theme registry contains theme_test_foo.'); - - module_disable(array('theme_test'), FALSE); - $this->assertIdentical(theme('theme_test_foo', array('foo' => 'b')), '', 'The theme registry does not contain theme_test_foo, because the module is disabled.'); - - module_enable(array('theme_test'), FALSE); - $this->assertIdentical(theme('theme_test_foo', array('foo' => 'c')), 'c', 'The theme registry contains theme_test_foo again after re-enabling the module.'); - } } /** @@ -317,7 +304,7 @@ class ThemeFunctionsTestCase extends DrupalWebTestCase { // Required to verify the "active" class in expected links below, and // because the current path is different when running tests manually via // simpletest.module ('batch') and via the testing framework (''). - $_GET['q'] = variable_get('site_frontpage', 'user'); + _current_path(variable_get('site_frontpage', 'user')); // Verify that a list of links is properly rendered. $variables = array(); diff --git a/core/modules/system/tests/upgrade/upgrade.test b/core/modules/system/tests/upgrade/upgrade.test index e4045be..52d7800 100644 --- a/core/modules/system/tests/upgrade/upgrade.test +++ b/core/modules/system/tests/upgrade/upgrade.test @@ -78,7 +78,6 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); $this->originalProfile = drupal_get_profile(); - $clean_url_original = variable_get('clean_url', 0); // Unregister the registry. // This is required to make sure that the database layer works properly. @@ -138,7 +137,6 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { drupal_save_session(FALSE); // Restore necessary variables. - $this->variable_set('clean_url', $clean_url_original); $this->variable_set('site_mail', 'simpletest@example.com'); drupal_set_time_limit($this->timeLimit); diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc index a95b856..9b23005 100644 --- a/core/modules/taxonomy/taxonomy.admin.inc +++ b/core/modules/taxonomy/taxonomy.admin.inc @@ -422,7 +422,7 @@ function taxonomy_overview_terms($form, &$form_state, TaxonomyVocabulary $vocabu '#type' => 'submit', '#value' => t('Reset to alphabetical') ); - $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array())); + $form_state['redirect'] = array(current_path(), (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array())); } return $form; @@ -784,7 +784,7 @@ function taxonomy_form_term($form, &$form_state, TaxonomyTerm $term = NULL, Taxo ); } else { - $form_state['redirect'] = $_GET['q']; + $form_state['redirect'] = current_path(); } return $form; diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 024f3ed..e82edfc 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -5,8 +5,6 @@ * Enables the organization of content into categories. */ -use Drupal\node\Node; - /** * Denotes that no term in the vocabulary has a parent. */ @@ -940,53 +938,53 @@ function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) { * from the database. Terms are loaded into memory and will not require * database access if loaded again during the same page request. * - * @see entity_load_multiple() + * @see entity_load() * @see EntityFieldQuery * - * @param array|bool $tids - * An array of taxonomy term IDs, or FALSE to load all terms. - * @param array $conditions + * @param $tids + * An array of taxonomy term IDs. + * @param $conditions * (deprecated) An associative array of conditions on the {taxonomy_term} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. * - * @return array + * @return * An array of taxonomy term entities, indexed by tid. When no results are * found, an empty array is returned. * * @todo Remove $conditions in Drupal 8. */ -function taxonomy_term_load_multiple($tids = array(), array $conditions = array()) { - return entity_load_multiple('taxonomy_term', $tids, $conditions); +function taxonomy_term_load_multiple($tids = array(), $conditions = array()) { + return entity_load('taxonomy_term', $tids, $conditions); } /** - * Loads multiple taxonomy vocabularies based on certain conditions. + * Load multiple taxonomy vocabularies based on certain conditions. * * This function should be used whenever you need to load more than one * vocabulary from the database. Terms are loaded into memory and will not * require database access if loaded again during the same page request. * - * @see entity_load_multiple() + * @see entity_load() * - * @param array|bool $vids + * @param $vids * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies. - * @param array $conditions + * @param $conditions * An array of conditions to add to the query. * - * @return array + * @return * An array of vocabulary objects, indexed by vid. */ -function taxonomy_vocabulary_load_multiple($vids = array(), array $conditions = array()) { - return entity_load_multiple('taxonomy_vocabulary', $vids, $conditions); +function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) { + return entity_load('taxonomy_vocabulary', $vids, $conditions); } /** * Return the taxonomy vocabulary entity matching a vocabulary ID. * - * @param int $vid + * @param $vid * The vocabulary's ID. * * @return TaxonomyVocabulary|false @@ -996,7 +994,8 @@ function taxonomy_vocabulary_load_multiple($vids = array(), array $conditions = * @see taxonomy_vocabulary_machine_name_load() */ function taxonomy_vocabulary_load($vid) { - return entity_load('taxonomy_vocabulary', $vid); + $vocabularies = taxonomy_vocabulary_load_multiple(array($vid)); + return reset($vocabularies); } /** @@ -1012,8 +1011,8 @@ function taxonomy_vocabulary_load($vid) { * @see taxonomy_vocabulary_load() */ function taxonomy_vocabulary_machine_name_load($name) { - $result = entity_load_multiple('taxonomy_vocabulary', FALSE, array('machine_name' => $name)); - return reset($result); + $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array('machine_name' => $name)); + return reset($vocabularies); } /** @@ -1029,7 +1028,8 @@ function taxonomy_term_load($tid) { if (!is_numeric($tid)) { return FALSE; } - return entity_load('taxonomy_term', $tid); + $term = taxonomy_term_load_multiple(array($tid), array()); + return $term ? $term[$tid] : FALSE; } /** @@ -1124,7 +1124,7 @@ function taxonomy_field_widget_info_alter(&$info) { /** * Implements hook_options_list(). */ -function taxonomy_options_list($field, $instance, $entity_type, $entity) { +function taxonomy_options_list($field, $instance) { $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values'; return $function($field); } @@ -1328,7 +1328,7 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; } // Terms to be created are not in $terms, but are still legitimate. - elseif ($item['tid'] == 'autocreate') { + else if ($item['tid'] == 'autocreate') { // Leave the item in place. } // Otherwise, unset the instance value, since the term does not exist. @@ -1543,7 +1543,7 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc /** * Implements hook_node_insert(). */ -function taxonomy_node_insert(Node $node) { +function taxonomy_node_insert($node) { // Add taxonomy index entries for the node. taxonomy_build_node_index($node); } @@ -1554,8 +1554,8 @@ function taxonomy_node_insert(Node $node) { * The index lists all terms that are related to a given node entity, and is * therefore maintained at the entity level. * - * @param Drupal\node\Node $node - * The node entity. + * @param $node + * The node object. */ function taxonomy_build_node_index($node) { // We maintain a denormalized table of term/node relationships, containing @@ -1620,7 +1620,7 @@ function taxonomy_build_node_index($node) { /** * Implements hook_node_update(). */ -function taxonomy_node_update(Node $node) { +function taxonomy_node_update($node) { // Always rebuild the node's taxonomy index entries on node save. taxonomy_delete_node_index($node); taxonomy_build_node_index($node); @@ -1629,7 +1629,7 @@ function taxonomy_node_update(Node $node) { /** * Implements hook_node_predelete(). */ -function taxonomy_node_predelete(Node $node) { +function taxonomy_node_predelete($node) { // Clean up the {taxonomy_index} table when nodes are deleted. taxonomy_delete_node_index($node); } @@ -1637,10 +1637,10 @@ function taxonomy_node_predelete(Node $node) { /** * Deletes taxonomy index entries for a given node. * - * @param Drupal\node\Node $node - * The node entity. + * @param $node + * The node object. */ -function taxonomy_delete_node_index(Node $node) { +function taxonomy_delete_node_index($node) { if (variable_get('taxonomy_maintain_index_table', TRUE)) { db_delete('taxonomy_index')->condition('nid', $node->nid)->execute(); } diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test index 5c16884..d62b429 100644 --- a/core/modules/taxonomy/taxonomy.test +++ b/core/modules/taxonomy/taxonomy.test @@ -765,9 +765,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { $path = 'taxonomy/autocomplete/taxonomy_'; $path .= $this->vocabulary->machine_name . '/' . $input; // The result order is not guaranteed, so check each term separately. - $url = url($path, array('absolute' => TRUE)); - $result = drupal_http_request($url); - $data = drupal_json_decode($result->data); + $result = $this->drupalGet($path); + $data = drupal_json_decode($result); $this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term'); $this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term'); @@ -1252,12 +1251,17 @@ class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase { $this->assertEqual(1, $index_count, 'Term 2 is indexed once.'); // Redo the above tests without interface. - $node->title = $this->randomName(); - unset($node->{$this->field_name_1}); - unset($node->{$this->field_name_2}); + $update_node = array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'uid' => $node->uid, + 'type' => $node->type, + 'title' => $this->randomName(), + ); // Update the article with no term changed. - $node->save(); + $updated_node = (object) $update_node; + node_save($updated_node); // Check that the index was not changed. $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array( @@ -1272,8 +1276,9 @@ class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase { $this->assertEqual(1, $index_count, 'Term 2 is indexed once.'); // Update the article to change one term. - $node->{$this->field_name_1}[$langcode] = array(array('tid' => $term_1->tid)); - $node->save(); + $update_node[$this->field_name_1][$langcode] = array(array('tid' => $term_1->tid)); + $updated_node = (object) $update_node; + node_save($updated_node); // Check that both terms are indexed. $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array( @@ -1288,8 +1293,9 @@ class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase { $this->assertEqual(1, $index_count, 'Term 2 is indexed.'); // Update the article to change another term. - $node->{$this->field_name_2}[$langcode] = array(array('tid' => $term_1->tid)); - $node->save(); + $update_node[$this->field_name_2][$langcode] = array(array('tid' => $term_1->tid)); + $updated_node = (object) $update_node; + node_save($updated_node); // Check that only one term is indexed. $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array( @@ -1355,7 +1361,7 @@ class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase { $this->createTerm($vocabulary); } // Load the terms from the vocabulary. - $terms = taxonomy_term_load_multiple(FALSE, array('vid' => $vocabulary->vid)); + $terms = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid)); $count = count($terms); $this->assertTrue($count == 5, format_string('Correct number of terms were loaded. !count terms.', array('!count' => $count))); @@ -1375,7 +1381,7 @@ class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase { $this->assertFalse($deleted_term); // Load terms from the vocabulary by vid. - $terms4 = taxonomy_term_load_multiple(FALSE, array('vid' => $vocabulary->vid)); + $terms4 = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid)); $this->assertTrue(count($terms4 == 4), 'Correct number of terms were loaded.'); $this->assertFalse(isset($terms4[$deleted->tid])); diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php deleted file mode 100644 index 5e689ff..0000000 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php +++ /dev/null @@ -1,274 +0,0 @@ - 'Tracker', - 'description' => 'Create and delete nodes and check for their display in the tracker listings.', - 'group' => 'Tracker' - ); - } - - function setUp() { - parent::setUp('comment', 'tracker'); - - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - $permissions = array('access comments', 'create page content', 'post comments', 'skip comment approval'); - $this->user = $this->drupalCreateUser($permissions); - $this->other_user = $this->drupalCreateUser($permissions); - - // Make node preview optional. - variable_set('comment_preview_page', 0); - } - - /** - * Tests for the presence of nodes on the global tracker listing. - */ - function testTrackerAll() { - $this->drupalLogin($this->user); - - $unpublished = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'status' => 0, - )); - $published = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'status' => 1, - )); - - $this->drupalGet('tracker'); - $this->assertNoText($unpublished->title, t('Unpublished node do not show up in the tracker listing.')); - $this->assertText($published->title, t('Published node show up in the tracker listing.')); - $this->assertLink(t('My recent content'), 0, t('User tab shows up on the global tracker page.')); - - // Delete a node and ensure it no longer appears on the tracker. - node_delete($published->nid); - $this->drupalGet('tracker'); - $this->assertNoText($published->title, t('Deleted node do not show up in the tracker listing.')); - } - - /** - * Tests for the presence of nodes on a user's tracker listing. - */ - function testTrackerUser() { - $this->drupalLogin($this->user); - - $unpublished = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'uid' => $this->user->uid, - 'status' => 0, - )); - $my_published = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'uid' => $this->user->uid, - 'status' => 1, - )); - $other_published_no_comment = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'uid' => $this->other_user->uid, - 'status' => 1, - )); - $other_published_my_comment = $this->drupalCreateNode(array( - 'title' => $this->randomName(8), - 'uid' => $this->other_user->uid, - 'status' => 1, - )); - $comment = array( - 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), - ); - $this->drupalPost('comment/reply/' . $other_published_my_comment->nid, $comment, t('Save')); - - $this->drupalGet('user/' . $this->user->uid . '/track'); - $this->assertNoText($unpublished->title, t("Unpublished nodes do not show up in the users's tracker listing.")); - $this->assertText($my_published->title, t("Published nodes show up in the user's tracker listing.")); - $this->assertNoText($other_published_no_comment->title, t("Other user's nodes do not show up in the user's tracker listing.")); - $this->assertText($other_published_my_comment->title, t("Nodes that the user has commented on appear in the user's tracker listing.")); - - // Verify that unpublished comments are removed from the tracker. - $admin_user = $this->drupalCreateUser(array('post comments', 'administer comments', 'access user profiles')); - $this->drupalLogin($admin_user); - $this->drupalPost('comment/1/edit', array('status' => COMMENT_NOT_PUBLISHED), t('Save')); - $this->drupalGet('user/' . $this->user->uid . '/track'); - $this->assertNoText($other_published_my_comment->title, 'Unpublished comments are not counted on the tracker listing.'); - } - - /** - * Tests for the presence of the "new" flag for nodes. - */ - function testTrackerNewNodes() { - $this->drupalLogin($this->user); - - $edit = array( - 'title' => $this->randomName(8), - ); - - $node = $this->drupalCreateNode($edit); - $title = $edit['title']; - $this->drupalGet('tracker'); - $this->assertPattern('/' . $title . '.*new/', t('New nodes are flagged as such in the tracker listing.')); - - $this->drupalGet('node/' . $node->nid); - $this->drupalGet('tracker'); - $this->assertNoPattern('/' . $title . '.*new/', t('Visited nodes are not flagged as new.')); - - $this->drupalLogin($this->other_user); - $this->drupalGet('tracker'); - $this->assertPattern('/' . $title . '.*new/', t('For another user, new nodes are flagged as such in the tracker listing.')); - - $this->drupalGet('node/' . $node->nid); - $this->drupalGet('tracker'); - $this->assertNoPattern('/' . $title . '.*new/', t('For another user, visited nodes are not flagged as new.')); - } - - /** - * Tests for comment counters on the tracker listing. - */ - function testTrackerNewComments() { - $this->drupalLogin($this->user); - - $node = $this->drupalCreateNode(array( - 'comment' => 2, - 'title' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(8)))), - )); - - // Add a comment to the page. - $comment = array( - 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), - ); - // The new comment is automatically viewed by the current user. - $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); - - $this->drupalLogin($this->other_user); - $this->drupalGet('tracker'); - $this->assertText('1 new', t('New comments are counted on the tracker listing pages.')); - $this->drupalGet('node/' . $node->nid); - - // Add another comment as other_user. - $comment = array( - 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), - ); - // If the comment is posted in the same second as the last one then Drupal - // can't tell the difference, so we wait one second here. - sleep(1); - $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); - - $this->drupalLogin($this->user); - $this->drupalGet('tracker'); - $this->assertText('1 new', t('New comments are counted on the tracker listing pages.')); - } - - /** - * Tests that existing nodes are indexed by cron. - */ - function testTrackerCronIndexing() { - $this->drupalLogin($this->user); - - // Create 3 nodes. - $edits = array(); - $nodes = array(); - for ($i = 1; $i <= 3; $i++) { - $edits[$i] = array( - 'comment' => 2, - 'title' => $this->randomName(), - ); - $nodes[$i] = $this->drupalCreateNode($edits[$i]); - } - - // Add a comment to the last node as other user. - $this->drupalLogin($this->other_user); - $comment = array( - 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), - ); - $this->drupalPost('comment/reply/' . $nodes[3]->nid, $comment, t('Save')); - - // Start indexing backwards from node 3. - variable_set('tracker_index_nid', 3); - - // Clear the current tracker tables and rebuild them. - db_delete('tracker_node') - ->execute(); - db_delete('tracker_user') - ->execute(); - tracker_cron(); - - $this->drupalLogin($this->user); - - // Fetch the user's tracker. - $this->drupalGet('tracker/' . $this->user->uid); - - // Assert that all node titles are displayed. - foreach ($nodes as $i => $node) { - $this->assertText($node->title, t('Node @i is displayed on the tracker listing pages.', array('@i' => $i))); - } - $this->assertText('1 new', t('New comment is counted on the tracker listing pages.')); - $this->assertText('updated', t('Node is listed as updated')); - - // Fetch the site-wide tracker. - $this->drupalGet('tracker'); - - // Assert that all node titles are displayed. - foreach ($nodes as $i => $node) { - $this->assertText($node->title, t('Node @i is displayed on the tracker listing pages.', array('@i' => $i))); - } - $this->assertText('1 new', t('New comment is counted on the tracker listing pages.')); - } - - /** - * Tests that publish/unpublish works at admin/content/node. - */ - function testTrackerAdminUnpublish() { - $admin_user = $this->drupalCreateUser(array('access content overview', 'administer nodes', 'bypass node access')); - $this->drupalLogin($admin_user); - - $node = $this->drupalCreateNode(array( - 'comment' => 2, - 'title' => $this->randomName(), - )); - - // Assert that the node is displayed. - $this->drupalGet('tracker'); - $this->assertText($node->title, t('Node is displayed on the tracker listing pages.')); - - // Unpublish the node and ensure that it's no longer displayed. - $edit = array( - 'operation' => 'unpublish', - 'nodes[' . $node->nid . ']' => $node->nid, - ); - $this->drupalPost('admin/content', $edit, t('Update')); - - $this->drupalGet('tracker'); - $this->assertText(t('No content available.'), t('Node is displayed on the tracker listing pages.')); - } -} diff --git a/core/modules/tracker/tracker.info b/core/modules/tracker/tracker.info index b39eb3f..a765504 100644 --- a/core/modules/tracker/tracker.info +++ b/core/modules/tracker/tracker.info @@ -4,3 +4,4 @@ dependencies[] = comment package = Core version = VERSION core = 8.x +files[] = tracker.test diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module index dbedf12..a5887f2 100644 --- a/core/modules/tracker/tracker.module +++ b/core/modules/tracker/tracker.module @@ -5,8 +5,6 @@ * Tracks recent content posted by a user or users. */ -use Drupal\node\Node; - /** * Implements hook_help(). */ @@ -192,7 +190,7 @@ function _tracker_user_access($account) { * * Adds new tracking information for this node since it's new. */ -function tracker_node_insert(Node $node, $arg = 0) { +function tracker_node_insert($node, $arg = 0) { _tracker_add($node->nid, $node->uid, $node->changed); } @@ -201,7 +199,7 @@ function tracker_node_insert(Node $node, $arg = 0) { * * Adds tracking information for this node since it's been updated. */ -function tracker_node_update(Node $node, $arg = 0) { +function tracker_node_update($node, $arg = 0) { _tracker_add($node->nid, $node->uid, $node->changed); } @@ -210,7 +208,7 @@ function tracker_node_update(Node $node, $arg = 0) { * * Deletes tracking information for a node. */ -function tracker_node_predelete(Node $node, $arg = 0) { +function tracker_node_predelete($node, $arg = 0) { db_delete('tracker_node') ->condition('nid', $node->nid) ->execute(); diff --git a/core/modules/tracker/tracker.test b/core/modules/tracker/tracker.test new file mode 100644 index 0000000..c6d4a29 --- /dev/null +++ b/core/modules/tracker/tracker.test @@ -0,0 +1,270 @@ + 'Tracker', + 'description' => 'Create and delete nodes and check for their display in the tracker listings.', + 'group' => 'Tracker' + ); + } + + function setUp() { + parent::setUp('comment', 'tracker'); + + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + $permissions = array('access comments', 'create page content', 'post comments', 'skip comment approval'); + $this->user = $this->drupalCreateUser($permissions); + $this->other_user = $this->drupalCreateUser($permissions); + + // Make node preview optional. + variable_set('comment_preview_page', 0); + } + + /** + * Tests for the presence of nodes on the global tracker listing. + */ + function testTrackerAll() { + $this->drupalLogin($this->user); + + $unpublished = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'status' => 0, + )); + $published = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'status' => 1, + )); + + $this->drupalGet('tracker'); + $this->assertNoText($unpublished->title, t('Unpublished node do not show up in the tracker listing.')); + $this->assertText($published->title, t('Published node show up in the tracker listing.')); + $this->assertLink(t('My recent content'), 0, t('User tab shows up on the global tracker page.')); + + // Delete a node and ensure it no longer appears on the tracker. + node_delete($published->nid); + $this->drupalGet('tracker'); + $this->assertNoText($published->title, t('Deleted node do not show up in the tracker listing.')); + } + + /** + * Tests for the presence of nodes on a user's tracker listing. + */ + function testTrackerUser() { + $this->drupalLogin($this->user); + + $unpublished = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'uid' => $this->user->uid, + 'status' => 0, + )); + $my_published = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'uid' => $this->user->uid, + 'status' => 1, + )); + $other_published_no_comment = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'uid' => $this->other_user->uid, + 'status' => 1, + )); + $other_published_my_comment = $this->drupalCreateNode(array( + 'title' => $this->randomName(8), + 'uid' => $this->other_user->uid, + 'status' => 1, + )); + $comment = array( + 'subject' => $this->randomName(), + 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), + ); + $this->drupalPost('comment/reply/' . $other_published_my_comment->nid, $comment, t('Save')); + + $this->drupalGet('user/' . $this->user->uid . '/track'); + $this->assertNoText($unpublished->title, t("Unpublished nodes do not show up in the users's tracker listing.")); + $this->assertText($my_published->title, t("Published nodes show up in the user's tracker listing.")); + $this->assertNoText($other_published_no_comment->title, t("Other user's nodes do not show up in the user's tracker listing.")); + $this->assertText($other_published_my_comment->title, t("Nodes that the user has commented on appear in the user's tracker listing.")); + + // Verify that unpublished comments are removed from the tracker. + $admin_user = $this->drupalCreateUser(array('post comments', 'administer comments', 'access user profiles')); + $this->drupalLogin($admin_user); + $this->drupalPost('comment/1/edit', array('status' => COMMENT_NOT_PUBLISHED), t('Save')); + $this->drupalGet('user/' . $this->user->uid . '/track'); + $this->assertNoText($other_published_my_comment->title, 'Unpublished comments are not counted on the tracker listing.'); + } + + /** + * Tests for the presence of the "new" flag for nodes. + */ + function testTrackerNewNodes() { + $this->drupalLogin($this->user); + + $edit = array( + 'title' => $this->randomName(8), + ); + + $node = $this->drupalCreateNode($edit); + $title = $edit['title']; + $this->drupalGet('tracker'); + $this->assertPattern('/' . $title . '.*new/', t('New nodes are flagged as such in the tracker listing.')); + + $this->drupalGet('node/' . $node->nid); + $this->drupalGet('tracker'); + $this->assertNoPattern('/' . $title . '.*new/', t('Visited nodes are not flagged as new.')); + + $this->drupalLogin($this->other_user); + $this->drupalGet('tracker'); + $this->assertPattern('/' . $title . '.*new/', t('For another user, new nodes are flagged as such in the tracker listing.')); + + $this->drupalGet('node/' . $node->nid); + $this->drupalGet('tracker'); + $this->assertNoPattern('/' . $title . '.*new/', t('For another user, visited nodes are not flagged as new.')); + } + + /** + * Tests for comment counters on the tracker listing. + */ + function testTrackerNewComments() { + $this->drupalLogin($this->user); + + $node = $this->drupalCreateNode(array( + 'comment' => 2, + 'title' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(8)))), + )); + + // Add a comment to the page. + $comment = array( + 'subject' => $this->randomName(), + 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), + ); + // The new comment is automatically viewed by the current user. + $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); + + $this->drupalLogin($this->other_user); + $this->drupalGet('tracker'); + $this->assertText('1 new', t('New comments are counted on the tracker listing pages.')); + $this->drupalGet('node/' . $node->nid); + + // Add another comment as other_user. + $comment = array( + 'subject' => $this->randomName(), + 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), + ); + // If the comment is posted in the same second as the last one then Drupal + // can't tell the difference, so we wait one second here. + sleep(1); + $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); + + $this->drupalLogin($this->user); + $this->drupalGet('tracker'); + $this->assertText('1 new', t('New comments are counted on the tracker listing pages.')); + } + + /** + * Tests that existing nodes are indexed by cron. + */ + function testTrackerCronIndexing() { + $this->drupalLogin($this->user); + + // Create 3 nodes. + $edits = array(); + $nodes = array(); + for ($i = 1; $i <= 3; $i++) { + $edits[$i] = array( + 'comment' => 2, + 'title' => $this->randomName(), + ); + $nodes[$i] = $this->drupalCreateNode($edits[$i]); + } + + // Add a comment to the last node as other user. + $this->drupalLogin($this->other_user); + $comment = array( + 'subject' => $this->randomName(), + 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), + ); + $this->drupalPost('comment/reply/' . $nodes[3]->nid, $comment, t('Save')); + + // Start indexing backwards from node 3. + variable_set('tracker_index_nid', 3); + + // Clear the current tracker tables and rebuild them. + db_delete('tracker_node') + ->execute(); + db_delete('tracker_user') + ->execute(); + tracker_cron(); + + $this->drupalLogin($this->user); + + // Fetch the user's tracker. + $this->drupalGet('tracker/' . $this->user->uid); + + // Assert that all node titles are displayed. + foreach ($nodes as $i => $node) { + $this->assertText($node->title, t('Node @i is displayed on the tracker listing pages.', array('@i' => $i))); + } + $this->assertText('1 new', t('New comment is counted on the tracker listing pages.')); + $this->assertText('updated', t('Node is listed as updated')); + + // Fetch the site-wide tracker. + $this->drupalGet('tracker'); + + // Assert that all node titles are displayed. + foreach ($nodes as $i => $node) { + $this->assertText($node->title, t('Node @i is displayed on the tracker listing pages.', array('@i' => $i))); + } + $this->assertText('1 new', t('New comment is counted on the tracker listing pages.')); + } + + /** + * Tests that publish/unpublish works at admin/content/node. + */ + function testTrackerAdminUnpublish() { + $admin_user = $this->drupalCreateUser(array('access content overview', 'administer nodes', 'bypass node access')); + $this->drupalLogin($admin_user); + + $node = $this->drupalCreateNode(array( + 'comment' => 2, + 'title' => $this->randomName(), + )); + + // Assert that the node is displayed. + $this->drupalGet('tracker'); + $this->assertText($node->title, t('Node is displayed on the tracker listing pages.')); + + // Unpublish the node and ensure that it's no longer displayed. + $edit = array( + 'operation' => 'unpublish', + 'nodes[' . $node->nid . ']' => $node->nid, + ); + $this->drupalPost('admin/content', $edit, t('Update')); + + $this->drupalGet('tracker'); + $this->assertText(t('No content available.'), t('Node is displayed on the tracker listing pages.')); + } +} diff --git a/core/modules/translation/tests/translation_test.module b/core/modules/translation/tests/translation_test.module index 1bd0659..e3bb4b5 100644 --- a/core/modules/translation/tests/translation_test.module +++ b/core/modules/translation/tests/translation_test.module @@ -5,20 +5,9 @@ * Mock module for content translation tests. */ -use Drupal\node\Node; - /** * Implements hook_node_insert(). */ -function translation_test_node_insert(Node $node) { +function translation_test_node_insert($node) { drupal_write_record('node', $node, 'nid'); } - -/** - * Implements hook_boot(). - */ -function translation_test_boot() { - // We run the t() function during hook_boot() to make sure it doesn't break - // the boot process. - $translation = t("Calling the t() process during @boot.", array('@boot' => 'hook_boot()')); -} diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module index b2c0635..fa73df5 100644 --- a/core/modules/translation/translation.module +++ b/core/modules/translation/translation.module @@ -19,8 +19,6 @@ * date (0) or needs to be updated (1). */ -use Drupal\node\Node; - /** * Identifies a content type which has translation support enabled. */ @@ -78,7 +76,7 @@ function translation_menu() { * of types that have translation enabled. * * @param $node - * A node entity. + * A node object. * * @return * TRUE if the translation tab should be displayed, FALSE otherwise. @@ -138,6 +136,28 @@ function translation_form_node_form_alter(&$form, &$form_state) { if (translation_supported_type($form['#node']->type)) { $node = $form['#node']; + // Build two lists with the disabled and enabled languages. + $languages = language_list(); + $grouped_languages = array(); + foreach ($languages as $langcode => $language) { + $grouped_languages[(int) $language->enabled][$langcode] = $language; + } + + $translator_widget = !empty($grouped_languages[0]) && user_access('translate content'); + $groups = array(t('Disabled'), t('Enabled')); + // Allow translators to enter content in disabled languages. Translators + // might need to distinguish between enabled and disabled languages, hence + // we divide them in two option groups. + if ($translator_widget) { + $options = array($groups[1] => array(LANGUAGE_NOT_SPECIFIED => t('- None -'))); + foreach (array(1, 0) as $status) { + $group = $groups[$status]; + foreach ($grouped_languages[$status] as $langcode => $language) { + $options[$group][$langcode] = $language->name; + } + } + $form['langcode']['#options'] = $options; + } if (!empty($node->translation_source)) { // We are creating a translation. Add values and lock language field. $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source); @@ -150,7 +170,13 @@ function translation_form_node_form_alter(&$form, &$form_state) { unset($form['langcode']['#options'][LANGUAGE_NOT_SPECIFIED]); foreach (translation_node_get_translations($node->tnid) as $langcode => $translation) { if ($translation->nid != $node->nid) { - unset($form['langcode']['#options'][$langcode]); + if ($translator_widget) { + $group = $groups[(int)!isset($disabled_languages[$langcode])]; + unset($form['langcode']['#options'][$group][$langcode]); + } + else { + unset($form['langcode']['#options'][$langcode]); + } } } // Add translation values and workflow options. @@ -193,11 +219,11 @@ function translation_form_node_form_alter(&$form, &$form_state) { * translation set. If no language provider is enabled, "fall back" to simple * links built through the result of translation_node_get_translations(). */ -function translation_node_view(Node $node, $view_mode) { +function translation_node_view($node, $view_mode) { // If the site has no translations or is not multilingual we have no content // translation links to display. if (isset($node->tnid) && language_multilingual() && $translations = translation_node_get_translations($node->tnid)) { - $languages = language_list(); + $languages = language_list(TRUE); // There might be a language provider enabled defining custom language // switch links which need to be taken into account while generating the @@ -210,7 +236,8 @@ function translation_node_view(Node $node, $view_mode) { $links = array(); foreach ($translations as $langcode => $translation) { - // Do not show links to the same node or to unpublished translations. + // Do not show links to the same node, to unpublished translations or to + // translations in disabled languages. if ($translation->status && isset($languages[$langcode]) && $langcode != $node->langcode) { $language = $languages[$langcode]; $key = "translation_$langcode"; @@ -248,7 +275,7 @@ function translation_node_view(Node $node, $view_mode) { /** * Implements hook_node_prepare(). */ -function translation_node_prepare(Node $node) { +function translation_node_prepare($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type) && // And it's a new node. @@ -298,7 +325,7 @@ function translation_node_prepare(Node $node) { /** * Implements hook_node_insert(). */ -function translation_node_insert(Node $node) { +function translation_node_insert($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { if (!empty($node->translation_source)) { @@ -333,7 +360,7 @@ function translation_node_insert(Node $node) { /** * Implements hook_node_update(). */ -function translation_node_update(Node $node) { +function translation_node_update($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { if (isset($node->translation) && $node->translation && !empty($node->langcode) && $node->tnid) { @@ -362,7 +389,7 @@ function translation_node_update(Node $node) { * * Ensures that duplicate translations can't be created for the same source. */ -function translation_node_validate(Node $node, $form) { +function translation_node_validate($node, $form) { // Only act on translatable nodes with a tnid or translation_source. if (translation_supported_type($node->type) && (!empty($node->tnid) || !empty($form['#node']->translation_source->nid))) { $tnid = !empty($node->tnid) ? $node->tnid : $form['#node']->translation_source->nid; @@ -376,7 +403,7 @@ function translation_node_validate(Node $node, $form) { /** * Implements hook_node_predelete(). */ -function translation_node_predelete(Node $node) { +function translation_node_predelete($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { translation_remove_from_set($node); @@ -387,7 +414,7 @@ function translation_node_predelete(Node $node) { * Removes a node from its translation set and updates accordingly. * * @param $node - * A node entity. + * A node object. */ function translation_remove_from_set($node) { if (isset($node->tnid)) { diff --git a/core/modules/translation/translation.pages.inc b/core/modules/translation/translation.pages.inc index d85b5e2..66dfc42 100644 --- a/core/modules/translation/translation.pages.inc +++ b/core/modules/translation/translation.pages.inc @@ -5,20 +5,18 @@ * User page callbacks for the Translation module. */ -use Drupal\node\Node; - /** * Page callback: Displays a list of a node's translations. * - * @param Drupal\node\Node $node - * A node entity. + * @param $node + * A node object. * * @return * A render array for a page containing a list of content. * * @see translation_menu() */ -function translation_node_overview(Node $node) { +function translation_node_overview($node) { include_once DRUPAL_ROOT . '/core/includes/language.inc'; if ($node->tnid) { diff --git a/core/modules/translation/translation.test b/core/modules/translation/translation.test index 15a324e..c0e158c 100644 --- a/core/modules/translation/translation.test +++ b/core/modules/translation/translation.test @@ -5,8 +5,6 @@ * Tests for the Translation module. */ -use Drupal\node\Node; - /** * Functional tests for the Translation module. */ @@ -35,6 +33,11 @@ class TranslationTestCase extends DrupalWebTestCase { // Add languages. $this->addLanguage('en'); $this->addLanguage('es'); + $this->addLanguage('it'); + + // Disable Italian to test the translation behavior with disabled languages. + $edit = array('languages[it][enabled]' => FALSE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); // Set "Basic page" content type to use multilingual support with // translation. @@ -123,6 +126,13 @@ class TranslationTestCase extends DrupalWebTestCase { $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.')); + // Confirm that disabled languages are an option for translators when + // creating nodes. + $this->drupalGet('node/add/page'); + $this->assertFieldByXPath('//select[@name="langcode"]//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[LANGUAGE_NOT_SPECIFIED][0]['value'], t('Content created in Italian (disabled).')); + // Confirm that language neutral is an option for translators when there are // disabled languages. $this->drupalGet('node/add/page'); @@ -130,10 +140,11 @@ class TranslationTestCase extends DrupalWebTestCase { $node2 = $this->createPage($this->randomName(), $this->randomName(), LANGUAGE_NOT_SPECIFIED); $this->assertRaw($node2->body[LANGUAGE_NOT_SPECIFIED][0]['value'], t('Language neutral content created with disabled languages available.')); - // Leave just one language installed and check that the translation overview + // Leave just one language enabled and check that the translation overview // page is still accessible. $this->drupalLogin($this->admin_user); - $this->drupalPost('admin/config/regional/language/delete/es', array(), t('Delete')); + $edit = array('languages[es][enabled]' => FALSE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); $this->drupalLogin($this->translator); $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertRaw(t('Translations of %title', array('%title' => $node->title)), t('Translation overview page available with only one language enabled.')); @@ -143,14 +154,17 @@ class TranslationTestCase extends DrupalWebTestCase { * Checks that the language switch links behave properly. */ function testLanguageSwitchLinks() { - // Create a Basic page in English and its translation in Spanish. + // Create a Basic page in English and its translations in Spanish and + // Italian. $node = $this->createPage($this->randomName(), $this->randomName(), 'en'); $translation_es = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'es'); + $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it'); - // Check that language switch links are correctly shown for languages - // we have translations for. + // Check that language switch links are correctly shown only for enabled + // languages. $this->assertLanguageSwitchLinks($node, $translation_es); $this->assertLanguageSwitchLinks($translation_es, $node); + $this->assertLanguageSwitchLinks($node, $translation_it, FALSE); // Check that links to the displayed translation appear only in the language // switcher block. @@ -181,9 +195,10 @@ class TranslationTestCase extends DrupalWebTestCase { * Tests that the language switcher block alterations work as intended. */ function testLanguageSwitcherBlockIntegration() { - // Add Italian to have three items in the language switcher block. + // Enable Italian to have three items in the language switcher block. $this->drupalLogin($this->admin_user); - $this->addLanguage('it'); + $edit = array('languages[it][enabled]' => TRUE); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); $this->resetCaches(); $this->drupalLogin($this->translator); @@ -271,7 +286,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Check to make sure that language has not already been installed. $this->drupalGet('admin/config/regional/language'); - if (strpos($this->drupalGetContent(), 'languages[' . $language_code . ']') === FALSE) { + if (strpos($this->drupalGetContent(), 'languages[' . $language_code . '][enabled]') === FALSE) { // Doesn't have language installed so add it. $edit = array(); $edit['predefined_langcode'] = $language_code; @@ -286,9 +301,15 @@ class TranslationTestCase extends DrupalWebTestCase { $this->assertRaw(t('The language %language has been created and can now be used.', array('%language' => $languages[$language_code]->name)), t('Language has been created.')); } } + elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'languages[' . $language_code . '][enabled]'))) { + // It's installed and enabled. No need to do anything. + $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.'); + } else { - // It's installed. No need to do anything. + // It's installed but not enabled. Enable it. $this->assertTrue(true, 'Language [' . $language_code . '] already installed.'); + $this->drupalPost(NULL, array('languages[' . $language_code . '][enabled]' => TRUE), t('Save configuration')); + $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.')); } } @@ -326,7 +347,7 @@ class TranslationTestCase extends DrupalWebTestCase { /** * Creates a translation for a basic page in the specified language. * - * @param Node $node + * @param $node * The basic page to create the translation for. * @param $title * The title of a basic page in the specified language. @@ -338,7 +359,7 @@ class TranslationTestCase extends DrupalWebTestCase { * @return * Translation object. */ - function createTranslation(Node $node, $title, $body, $langcode) { + function createTranslation($node, $title, $body, $langcode) { $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => $langcode))); $field_langcode = LANGUAGE_NOT_SPECIFIED; diff --git a/core/modules/update/update.api.php b/core/modules/update/update.api.php index 5f68f0e..83b93b2 100644 --- a/core/modules/update/update.api.php +++ b/core/modules/update/update.api.php @@ -29,11 +29,11 @@ * includes all the metadata documented in the comments below for each * project (either module or theme) that is currently enabled. The array is * initially populated inside update_get_projects() with the help of - * update_process_info_list(), so look there for examples of how to + * _update_process_info_list(), so look there for examples of how to * populate the array with real values. * * @see update_get_projects() - * @see update_process_info_list() + * @see _update_process_info_list() */ function hook_update_projects_alter(&$projects) { // Hide a site-specific module from the list. diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index 3b1de48..3338162 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -37,11 +37,11 @@ function update_get_projects() { // Still empty, so we have to rebuild the cache. $module_data = system_rebuild_module_data(); $theme_data = system_rebuild_theme_data(); - update_process_info_list($projects, $module_data, 'module', TRUE); - update_process_info_list($projects, $theme_data, 'theme', TRUE); + _update_process_info_list($projects, $module_data, 'module', TRUE); + _update_process_info_list($projects, $theme_data, 'theme', TRUE); if (variable_get('update_check_disabled', FALSE)) { - update_process_info_list($projects, $module_data, 'module', FALSE); - update_process_info_list($projects, $theme_data, 'theme', FALSE); + _update_process_info_list($projects, $module_data, 'module', FALSE); + _update_process_info_list($projects, $theme_data, 'theme', FALSE); } // Allow other modules to alter projects before fetching and comparing. drupal_alter('update_projects', $projects); @@ -75,12 +75,10 @@ function update_get_projects() { * @param $status * Boolean that controls what status (enabled or disabled) to process out of * the $list and add to the $projects array. - * @param $additional_whitelist - * Array of additional elements to be collected from the .info file. * * @see update_get_projects() */ -function update_process_info_list(&$projects, $list, $project_type, $status, $additional_whitelist = array()) { +function _update_process_info_list(&$projects, $list, $project_type, $status) { foreach ($list as $file) { // A disabled base theme of an enabled sub-theme still has all of its code // run by the sub-theme, so we include it in our "enabled" projects list. @@ -177,7 +175,7 @@ function update_process_info_list(&$projects, $list, $project_type, $status, $ad 'name' => $project_name, // Only save attributes from the .info file we care about so we do not // bloat our RAM usage needlessly. - 'info' => update_filter_project_info($file->info, $additional_whitelist), + 'info' => update_filter_project_info($file->info), 'datestamp' => $file->info['datestamp'], 'includes' => array($file->name => $file->info['name']), 'project_type' => $project_display_type, @@ -741,7 +739,6 @@ function update_project_cache($cid) { // On certain paths, we should clear the cache and recompute the projects for // update status of the site to avoid presenting stale information. - $q = $_GET['q']; $paths = array( 'admin/modules', 'admin/modules/update', @@ -753,7 +750,7 @@ function update_project_cache($cid) { 'admin/reports/status', 'admin/reports/updates/check', ); - if (in_array($q, $paths)) { + if (in_array(current_path(), $paths)) { _update_cache_clear($cid); } else { @@ -770,15 +767,13 @@ function update_project_cache($cid) { * * @param array $info * Array of .info file data as returned by drupal_parse_info_file(). - * @param $additional_whitelist - * Array of additional elements to be collected from the .info file. * * @return * Array of .info file data we need for the Update manager. * - * @see update_process_info_list() + * @see _update_process_info_list() */ -function update_filter_project_info($info, $additional_whitelist = array()) { +function update_filter_project_info($info) { $whitelist = array( '_info_file_ctime', 'datestamp', @@ -789,6 +784,5 @@ function update_filter_project_info($info, $additional_whitelist = array()) { 'project status url', 'version', ); - $whitelist = array_merge($whitelist, $additional_whitelist); return array_intersect_key($info, drupal_map_assoc($whitelist)); } diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 6142a20..65c431d 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -107,7 +107,7 @@ function update_help($path, $arg) { */ function update_init() { if (arg(0) == 'admin' && user_access('administer site configuration')) { - switch ($_GET['q']) { + switch (current_path()) { // These pages don't need additional nagging. case 'admin/appearance/update': case 'admin/appearance/install': @@ -809,19 +809,14 @@ function _update_get_cache_multiple($cid_prefix) { * * @param $cid * Optional cache ID of the record to clear from the private update module - * cache. If empty, all records will be cleared from the table except - * fetch tasks. + * cache. If empty, all records will be cleared from the table. * @param $wildcard * If $wildcard is TRUE, cache IDs starting with $cid are deleted in * addition to the exact cache ID specified by $cid. */ function _update_cache_clear($cid = NULL, $wildcard = FALSE) { if (empty($cid)) { - db_delete('cache_update') - // Clear everything except fetch task information because these are used - // to ensure that the fetch task queue items are not added multiple times. - ->condition('cid', 'fetch_task::%', 'NOT LIKE') - ->execute(); + db_truncate('cache_update')->execute(); } else { $query = db_delete('cache_update'); diff --git a/core/modules/update/update.test b/core/modules/update/update.test index c8cfdd4..f91e41a 100644 --- a/core/modules/update/update.test +++ b/core/modules/update/update.test @@ -225,33 +225,6 @@ class UpdateCoreTestCase extends UpdateTestHelper { $this->assertUniqueText(t('Failed to get available update data for one project.')); } - /** - * Tests that exactly one fetch task per project is created and not more. - */ - function testFetchTasks() { - $projecta = array( - 'name' => 'aaa_update_test', - ); - $projectb = array( - 'name' => 'bbb_update_test', - ); - $queue = queue('update_fetch_tasks'); - $this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty'); - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 1, 'Queue contains one item'); - update_create_fetch_task($projectb); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); - // Try to add project a again. - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue still contains two items'); - - // Clear cache and try again. - _update_cache_clear(); - drupal_static_reset('_update_create_fetch_task'); - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); - } - protected function setSystemInfo7_0() { $setting = array( '#all' => array( diff --git a/core/modules/user/lib/Drupal/user/User.php b/core/modules/user/lib/Drupal/user/User.php deleted file mode 100644 index b39d88d..0000000 --- a/core/modules/user/lib/Drupal/user/User.php +++ /dev/null @@ -1,147 +0,0 @@ -uid; - } -} diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php deleted file mode 100644 index bd0d9b9..0000000 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ /dev/null @@ -1,227 +0,0 @@ - $record) { - if ($record->picture) { - $picture_fids[] = $record->picture; - } - $queried_users[$key]->data = unserialize($record->data); - $queried_users[$key]->roles = array(); - if ($record->uid) { - $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - } - else { - $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user'; - } - } - - // Add any additional roles from the database. - $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users))); - foreach ($result as $record) { - $queried_users[$record->uid]->roles[$record->rid] = $record->name; - } - - // Add the full file objects for user pictures if enabled. - if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) { - $pictures = file_load_multiple($picture_fids); - foreach ($queried_users as $entity) { - if (!empty($entity->picture) && isset($pictures[$entity->picture])) { - $entity->picture = $pictures[$entity->picture]; - } - } - } - // Call the default attachLoad() method. This will add fields and call - // hook_user_load(). - parent::attachLoad($queried_users, $revision_id); - } - - /** - * Overrides EntityDatabaseStorageController::create(). - */ - public function create(array $values) { - if (!isset($values['created'])) { - $values['created'] = REQUEST_TIME; - } - // Users always have the authenticated user role. - $values['roles'][DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - - return parent::create($values); - } - - /** - * Overrides EntityDatabaseStorageController::save(). - */ - public function save(EntityInterface $entity) { - if (empty($entity->uid)) { - $entity->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField()); - $entity->enforceIsNew(); - } - parent::save($entity); - } - - /** - * Overrides EntityDatabaseStorageController::preSave(). - */ - protected function preSave(EntityInterface $entity) { - // Update the user password if it has changed. - if ($entity->isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) { - // Allow alternate password hashing schemes. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $entity->pass = user_hash_password(trim($entity->pass)); - // Abort if the hashing failed and returned FALSE. - if (!$entity->pass) { - throw new EntityMalformedException('The entity does not have a password.'); - } - } - - if (!empty($entity->picture_upload)) { - $entity->picture = $entity->picture_upload; - } - // Delete the picture if the submission indicates that it should be deleted - // and no replacement was submitted. - elseif (!empty($entity->picture_delete)) { - $entity->picture = 0; - file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid); - file_delete($entity->original->picture); - } - - if (!$entity->isNew()) { - // Process picture uploads. - if (!empty($entity->picture->fid) && (!isset($entity->original->picture->fid) || $entity->picture->fid != $entity->original->picture->fid)) { - $picture = $entity->picture; - // If the picture is a temporary file, move it to its final location - // and make it permanent. - if (!$picture->status) { - $info = image_get_info($picture->uri); - $picture_directory = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures'); - - // Prepare the pictures directory. - file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY); - $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $entity->uid . '-' . REQUEST_TIME . '.' . $info['extension']); - - // Move the temporary file into the final location. - if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) { - $picture->status = FILE_STATUS_PERMANENT; - $entity->picture = file_save($picture); - file_usage_add($picture, 'user', 'user', $entity->uid); - } - } - // Delete the previous picture if it was deleted or replaced. - if (!empty($entity->original->picture->fid)) { - file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid); - file_delete($entity->original->picture); - } - } - $entity->picture = empty($entity->picture->fid) ? 0 : $entity->picture->fid; - - // If the password is empty, that means it was not changed, so use the - // original password. - if (empty($entity->pass)) { - $entity->pass = $entity->original->pass; - } - } - - // Prepare user roles. - if (isset($entity->roles)) { - $entity->roles = array_filter($entity->roles); - } - - // Move account cancellation information into $entity->data. - foreach (array('user_cancel_method', 'user_cancel_notify') as $key) { - if (isset($entity->{$key})) { - $entity->data[$key] = $entity->{$key}; - } - } - } - - /** - * Overrides EntityDatabaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - - if ($update) { - // If the password has been changed, delete all open sessions for the - // user and recreate the current one. - if ($entity->pass != $entity->original->pass) { - drupal_session_destroy_uid($entity->uid); - if ($entity->uid == $GLOBALS['user']->uid) { - drupal_session_regenerate(); - } - } - - // Remove roles that are no longer enabled for the user. - $entity->roles = array_filter($entity->roles); - - // Reload user roles if provided. - if ($entity->roles != $entity->original->roles) { - db_delete('users_roles') - ->condition('uid', $entity->uid) - ->execute(); - - $query = db_insert('users_roles')->fields(array('uid', 'rid')); - foreach (array_keys($entity->roles) as $rid) { - if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { - $query->values(array( - 'uid' => $entity->uid, - 'rid' => $rid, - )); - } - } - $query->execute(); - } - - // If the user was blocked, delete the user's sessions to force a logout. - if ($entity->original->status != $entity->status && $entity->status == 0) { - drupal_session_destroy_uid($entity->uid); - } - - // Send emails after we have the new user object. - if ($entity->status != $entity->original->status) { - // The user's status is changing; conditionally send notification email. - $op = $entity->status == 1 ? 'status_activated' : 'status_blocked'; - _user_mail_notify($op, $entity); - } - } - else { - // Save user roles. - if (count($entity->roles) > 1) { - $query = db_insert('users_roles')->fields(array('uid', 'rid')); - foreach (array_keys($entity->roles) as $rid) { - if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { - $query->values(array( - 'uid' => $entity->uid, - 'rid' => $rid, - )); - } - } - $query->execute(); - } - } - } -} diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php index 1b8a253..dc16906 100644 --- a/core/modules/user/user.api.php +++ b/core/modules/user/user.api.php @@ -227,57 +227,67 @@ function hook_user_operations() { } /** - * Act on a user account being inserted or updated. + * A user account is about to be created or updated. * - * This hook is invoked before the user account is saved to the database. - * - * Modules that want to store properties in the serialized {users}.data column, - * which is automatically loaded whenever a user account object is loaded, may - * add their properties to $account->data in order to have their data serialized - * on save. + * This hook is primarily intended for modules that want to store properties in + * the serialized {users}.data column, which is automatically loaded whenever a + * user account object is loaded, modules may add to $edit['data'] in order + * to have their data serialized on save. * + * @param $edit + * The array of form values submitted by the user. * @param $account - * The user account object. + * The user object on which the operation is performed. * * @see hook_user_insert() * @see hook_user_update() */ -function hook_user_presave($account) { +function hook_user_presave(&$edit, $account) { // Make sure that our form value 'mymodule_foo' is stored as // 'mymodule_bar' in the 'data' (serialized) column. - if (isset($account->mymodule_foo)) { - $account->data['mymodule_bar'] = $account->mymodule_foo; + if (isset($edit['mymodule_foo'])) { + $edit['data']['mymodule_bar'] = $edit['mymodule_foo']; } } /** - * Respond to creation of a new user account. + * A user account was created. * + * The module should save its custom additions to the user object into the + * database. + * + * @param $edit + * The array of form values submitted by the user. * @param $account - * The user account object. + * The user object on which the operation is being performed. * * @see hook_user_presave() * @see hook_user_update() */ -function hook_user_insert($account) { - db_insert('user_changes') +function hook_user_insert(&$edit, $account) { + db_insert('mytable') ->fields(array( + 'myfield' => $edit['myfield'], 'uid' => $account->uid, - 'created' => time(), )) ->execute(); } /** - * Respond to updates to a user account. + * A user account was updated. + * + * Modules may use this hook to update their user data in a custom storage + * after a user account has been updated. * + * @param $edit + * The array of form values submitted by the user. * @param $account - * The user account object. + * The user object on which the operation is performed. * * @see hook_user_presave() * @see hook_user_insert() */ -function hook_user_update($account) { +function hook_user_update(&$edit, $account) { db_insert('user_changes') ->fields(array( 'uid' => $account->uid, diff --git a/core/modules/user/user.entity.inc b/core/modules/user/user.entity.inc new file mode 100644 index 0000000..5549c77 --- /dev/null +++ b/core/modules/user/user.entity.inc @@ -0,0 +1,52 @@ + $record) { + $picture_fids[] = $record->picture; + $queried_users[$key]->data = unserialize($record->data); + $queried_users[$key]->roles = array(); + if ($record->uid) { + $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + } + else { + $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user'; + } + } + + // Add any additional roles from the database. + $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users))); + foreach ($result as $record) { + $queried_users[$record->uid]->roles[$record->rid] = $record->name; + } + + // Add the full file objects for user pictures if enabled. + if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) { + $pictures = file_load_multiple($picture_fids); + foreach ($queried_users as $account) { + if (!empty($account->picture) && isset($pictures[$account->picture])) { + $account->picture = $pictures[$account->picture]; + } + else { + $account->picture = NULL; + } + } + } + // Call the default attachLoad() method. This will add fields and call + // hook_user_load(). + parent::attachLoad($queried_users, $revision_id); + } +} diff --git a/core/modules/user/user.info b/core/modules/user/user.info index 8dad5a3..d887352 100644 --- a/core/modules/user/user.info +++ b/core/modules/user/user.info @@ -3,6 +3,7 @@ description = Manages the user registration and login system. package = Core version = VERSION core = 8.x +files[] = user.entity.inc files[] = user.test required = TRUE configure = admin/config/people diff --git a/core/modules/user/user.module b/core/modules/user/user.module index e6fa2dd..c8daa48 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -83,8 +83,12 @@ function user_help($path, $arg) { * be passed by reference. * * @param $type - * A text string that controls which user hook to invoke. Valid choices are: + * 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. @@ -141,15 +145,14 @@ function user_theme() { * Implements hook_entity_info(). */ function user_entity_info() { - return array( + $return = array( 'user' => array( 'label' => t('User'), - 'controller class' => 'Drupal\user\UserStorageController', + 'controller class' => 'UserController', 'base table' => 'users', 'uri callback' => 'user_uri', 'label callback' => 'user_label', 'fieldable' => TRUE, - 'entity class' => 'Drupal\user\User', 'entity keys' => array( 'id' => 'uid', ), @@ -170,6 +173,7 @@ function user_entity_info() { ), ), ); + return $return; } /** @@ -261,28 +265,28 @@ function user_external_load($authname) { } /** - * Loads multiple users based on certain conditions. + * Load multiple users based on certain conditions. * * This function should be used whenever you need to load more than one user * from the database. Users are loaded into memory and will not require * database access if loaded again during the same page request. * - * @param array|bool $uids - * An array of user IDs, or FALSE to load all users. - * @param array $conditions + * @param $uids + * An array of user IDs. + * @param $conditions * (deprecated) An associative array of conditions on the {users} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. - * @param bool $reset + * @param $reset * A boolean indicating that the internal cache should be reset. Use this if * loading a user object which has been altered during the page request. * - * @return array + * @return * An array of user objects, indexed by uid. * - * @see entity_load_multiple() + * @see entity_load() * @see user_load() * @see user_load_by_mail() * @see user_load_by_name() @@ -290,8 +294,8 @@ function user_external_load($authname) { * * @todo Remove $conditions in Drupal 8. */ -function user_load_multiple($uids = array(), array $conditions = array(), $reset = FALSE) { - return entity_load_multiple('user', $uids, $conditions, $reset); +function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('user', $uids, $conditions, $reset); } /** @@ -306,55 +310,274 @@ function user_load_multiple($uids = array(), array $conditions = array(), $reset * @link http://drupal.org/node/218104 Safely impersonating another user @endlink * for more information. * - * @param int $uid + * @param $uid * Integer specifying the user ID to load. - * @param bool $reset + * @param $reset * TRUE to reset the internal cache and load from the database; FALSE * (default) to load from the internal cache, if set. * - * @return object + * @return * A fully-loaded user object upon successful user load, or FALSE if the user * cannot be loaded. * * @see user_load_multiple() */ function user_load($uid, $reset = FALSE) { - return entity_load('user', $uid, $reset); + $users = user_load_multiple(array($uid), array(), $reset); + return reset($users); } /** - * Fetches a user object by email address. + * Fetch a user object by email address. * - * @param string $mail + * @param $mail * String with the account's e-mail address. - * @return object|bool + * @return * A fully-loaded $user object upon successful user load or FALSE if user * cannot be loaded. * * @see user_load_multiple() */ function user_load_by_mail($mail) { - $users = entity_load_multiple('user', FALSE, array('mail' => $mail)); + $users = user_load_multiple(array(), array('mail' => $mail)); return reset($users); } /** - * Fetches a user object by account name. + * Fetch a user object by account name. * - * @param string $name + * @param $name * String with the account's user name. - * @return object|bool + * @return * A fully-loaded $user object upon successful user load or FALSE if user * cannot be loaded. * * @see user_load_multiple() */ function user_load_by_name($name) { - $users = entity_load_multiple('user', FALSE, array('name' => $name)); + $users = user_load_multiple(array(), array('name' => $name)); return reset($users); } /** + * Save changes to a user account or add a new user. + * + * @param $account + * (optional) The user object to modify or add. If you want to modify + * an existing user account, you will need to ensure that (a) $account + * is an object, and (b) you have set $account->uid to the numeric + * user ID of the user account you wish to modify. If you + * want to create a new user account, you can set $account->is_new to + * TRUE or omit the $account->uid field. + * @param $edit + * An array of fields and values to save. For example array('name' + * => 'My name'). Key / value pairs added to the $edit['data'] will be + * serialized and saved in the {users.data} column. + * + * @return + * A fully-loaded $user object upon successful save or FALSE if the save failed. + * + * @todo D8: Drop $edit and fix user_save() to be consistent with others. + */ +function user_save($account, $edit = array()) { + $transaction = db_transaction(); + try { + if (!empty($edit['pass'])) { + // Allow alternate password hashing schemes. + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); + $edit['pass'] = user_hash_password(trim($edit['pass'])); + // Abort if the hashing failed and returned FALSE. + if (!$edit['pass']) { + return FALSE; + } + } + else { + // Avoid overwriting an existing password with a blank password. + unset($edit['pass']); + } + + // Load the stored entity, if any. + if (!empty($account->uid) && !isset($account->original)) { + $account->original = entity_load_unchanged('user', $account->uid); + } + + if (empty($account)) { + $account = new stdClass(); + } + if (!isset($account->is_new)) { + $account->is_new = empty($account->uid); + } + // Prepopulate $edit['data'] with the current value of $account->data. + // Modules can add to or remove from this array in hook_user_presave(). + if (!empty($account->data)) { + $edit['data'] = !empty($edit['data']) ? array_merge($account->data, $edit['data']) : $account->data; + } + + // Invoke hook_user_presave() for all modules. + user_module_invoke('presave', $edit, $account); + + // Invoke presave operations of Field Attach API and Entity API. Those APIs + // require a fully-fledged and updated entity object. Therefore, we need to + // copy any new property values of $edit into it. + foreach ($edit as $key => $value) { + $account->$key = $value; + } + // Default the user entity language to the user's preferred language. + if (!isset($account->langcode) && isset($account->preferred_langcode)) { + $account->langcode = $account->preferred_langcode; + } + field_attach_presave('user', $account); + module_invoke_all('entity_presave', $account, 'user'); + + if (is_object($account) && !$account->is_new) { + // Process picture uploads. + if (!empty($account->picture->fid) && (!isset($account->original->picture->fid) || $account->picture->fid != $account->original->picture->fid)) { + $picture = $account->picture; + // If the picture is a temporary file move it to its final location and + // make it permanent. + if (!$picture->status) { + $info = image_get_info($picture->uri); + $picture_directory = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures'); + + // Prepare the pictures directory. + file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY); + $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '-' . REQUEST_TIME . '.' . $info['extension']); + + // Move the temporary file into the final location. + if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) { + $picture->status = FILE_STATUS_PERMANENT; + $account->picture = file_save($picture); + file_usage_add($picture, 'user', 'user', $account->uid); + } + } + // Delete the previous picture if it was deleted or replaced. + if (!empty($account->original->picture->fid)) { + file_usage_delete($account->original->picture, 'user', 'user', $account->uid); + file_delete($account->original->picture); + } + } + elseif (isset($edit['picture_delete']) && $edit['picture_delete']) { + file_usage_delete($account->original->picture, 'user', 'user', $account->uid); + file_delete($account->original->picture); + } + $account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid; + + // Do not allow 'uid' to be changed. + $account->uid = $account->original->uid; + // Save changes to the user table. + $success = drupal_write_record('users', $account, 'uid'); + if ($success === FALSE) { + // The query failed - better to abort the save than risk further + // data loss. + return FALSE; + } + + // Reload user roles if provided. + if ($account->roles != $account->original->roles) { + db_delete('users_roles') + ->condition('uid', $account->uid) + ->execute(); + + $query = db_insert('users_roles')->fields(array('uid', 'rid')); + foreach (array_keys($account->roles) as $rid) { + if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { + $query->values(array( + 'uid' => $account->uid, + 'rid' => $rid, + )); + } + } + $query->execute(); + } + + // Delete a blocked user's sessions to kick them if they are online. + if ($account->original->status != $account->status && $account->status == 0) { + drupal_session_destroy_uid($account->uid); + } + + // If the password changed, delete all open sessions and recreate + // the current one. + if ($account->pass != $account->original->pass) { + drupal_session_destroy_uid($account->uid); + if ($account->uid == $GLOBALS['user']->uid) { + drupal_session_regenerate(); + } + } + + // Save Field data. + field_attach_update('user', $account); + + // Send emails after we have the new user object. + if ($account->status != $account->original->status) { + // The user's status is changing; conditionally send notification email. + $op = $account->status == 1 ? 'status_activated' : 'status_blocked'; + _user_mail_notify($op, $account); + } + + // Update $edit with any interim changes to $account. + foreach ($account as $key => $value) { + if (!property_exists($account->original, $key) || $value !== $account->original->$key) { + $edit[$key] = $value; + } + } + user_module_invoke('update', $edit, $account); + module_invoke_all('entity_update', $account, 'user'); + } + else { + // Allow 'uid' to be set by the caller. There is no danger of writing an + // existing user as drupal_write_record will do an INSERT. + if (empty($account->uid)) { + $account->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField()); + } + // Allow 'created' to be set by the caller. + if (!isset($account->created)) { + $account->created = REQUEST_TIME; + } + $success = drupal_write_record('users', $account); + if ($success === FALSE) { + // On a failed INSERT some other existing user's uid may be returned. + // We must abort to avoid overwriting their account. + return FALSE; + } + + // Make sure $account is properly initialized. + $account->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + + field_attach_insert('user', $account); + $edit = (array) $account; + user_module_invoke('insert', $edit, $account); + module_invoke_all('entity_insert', $account, 'user'); + + // Save user roles. + if (count($account->roles) > 1) { + $query = db_insert('users_roles')->fields(array('uid', 'rid')); + foreach (array_keys($account->roles) as $rid) { + if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { + $query->values(array( + 'uid' => $account->uid, + 'rid' => $rid, + )); + } + } + $query->execute(); + } + } + // Clear internal properties. + unset($account->is_new); + unset($account->original); + // Clear the static loading cache. + entity_get_controller('user')->resetCache(array($account->uid)); + + return $account; + } + catch (Exception $e) { + $transaction->rollback(); + watchdog_exception('user', $e); + throw $e; + } +} + +/** * Verify the syntax of the given name. */ function user_validate_name($name) { @@ -697,7 +920,7 @@ function user_user_view($account) { * @see user_validate_mail() */ function user_account_form(&$form, &$form_state) { - global $user, $language_interface; + global $user; $account = $form['#user']; $register = ($form['#user']->uid > 0 ? FALSE : TRUE); @@ -718,7 +941,7 @@ function user_account_form(&$form, &$form_state) { '#maxlength' => USERNAME_MAX_LENGTH, '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'), '#required' => TRUE, - '#attributes' => array('class' => array('username'), 'autocomplete' => 'off'), + '#attributes' => array('class' => array('username')), '#default_value' => (!$register ? $account->name : ''), '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin), '#weight' => -10, @@ -733,7 +956,6 @@ function user_account_form(&$form, &$form_state) { '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), '#required' => !(empty($account->mail) && user_access('administer users')), '#default_value' => (!$register ? $account->mail : ''), - '#attributes' => array('autocomplete' => 'off'), ); // Display password field only for existing users or when user is allowed to @@ -869,69 +1091,6 @@ function user_account_form(&$form, &$form_state) { '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')), ); $form['#validate'][] = 'user_validate_picture'; - - if (module_exists('language') && language_multilingual()) { - $languages = language_list(); - - // If the user is being created, we set the user language to the page language. - $user_preferred_language = $register ? $language_interface : user_preferred_language($account); - - $names = array(); - foreach ($languages as $langcode => $item) { - $names[$langcode] = $item->name; - } - // Is default the interface language? - $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT; - $form['language'] = array( - '#type' => 'fieldset', - '#title' => t('Language settings'), - // Display language selector when either creating a user on the admin - // interface or editing a user account. - '#access' => !$register || user_access('administer users'), - ); - $form['language']['preferred_langcode'] = array( - '#type' => (count($names) <= 5 ? 'radios' : 'select'), - '#title' => t('Language'), - '#default_value' => $user_preferred_language->langcode, - '#options' => $names, - '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."), - ); - } - else { - $form['language'] = array( - '#type' => 'container', - ); - $form['language']['preferred_langcode'] = array( - '#type' => 'value', - '#value' => language_default()->langcode, - ); - } - - // User entities contain both a langcode property (for identifying the - // language of the entity data) and a preferred_langcode property (see above). - // Rather than provide a UI forcing the user to choose both separately, - // assume that the user profile data is in the user's preferred language. This - // element provides that synchronization. For use-cases where this - // synchronization is not desired, a module can alter or remove this element. - $form['language']['langcode'] = array( - '#type' => 'value', - '#value_callback' => '_user_language_selector_langcode_value', - // For the synchronization to work, this element must have a larger weight - // than the preferred_langcode element. Set a large weight here in case - // a module alters the weight of the other element. - '#weight' => 100, - ); -} - -/** - * Sets the value of the user register and profile forms' langcode element. - */ -function _user_language_selector_langcode_value($element, $input, &$form_state) { - // Only add to the description if the form element have a description. - if (isset($form_state['complete_form']['language']['preferred_langcode']['#description'])) { - $form_state['complete_form']['language']['preferred_langcode']['#description'] .= ' ' . t("This is also assumed to be the primary language of this account's profile information."); - } - return $form_state['values']['preferred_langcode']; } /** @@ -970,42 +1129,20 @@ function user_account_form_validate($form, &$form_state) { if ($error = user_validate_name($form_state['values']['name'])) { form_set_error('name', $error); } - // Cast the user ID as an integer. It might have been set to NULL, which - // could lead to unexpected results. - else { - $name_taken = (bool) db_select('users') - ->fields('users', array('uid')) - ->condition('uid', (int) $account->uid, '<>') - ->condition('name', db_like($form_state['values']['name']), 'LIKE') - ->range(0, 1) - ->execute() - ->fetchField(); - - if ($name_taken) { - form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name']))); - } + elseif ((bool) db_select('users')->fields('users', array('uid'))->condition('uid', $account->uid, '<>')->condition('name', db_like($form_state['values']['name']), 'LIKE')->range(0, 1)->execute()->fetchField()) { + form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name']))); } } $mail = $form_state['values']['mail']; - if (!empty($mail)) { - $mail_taken = (bool) db_select('users') - ->fields('users', array('uid')) - ->condition('uid', (int) $account->uid, '<>') - ->condition('mail', db_like($mail), 'LIKE') - ->range(0, 1) - ->execute() - ->fetchField(); - - if ($mail_taken) { - // Format error message dependent on whether the user is logged in or not. - if ($GLOBALS['user']->uid) { - form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail))); - } - else { - form_set_error('mail', t('The e-mail address %email is already registered. Have you forgotten your password?', array('%email' => $mail, '@password' => url('user/password')))); - } + if (!empty($mail) && (bool) db_select('users')->fields('users', array('uid'))->condition('uid', $account->uid, '<>')->condition('mail', db_like($mail), 'LIKE')->range(0, 1)->execute()->fetchField()) { + // Format error message dependent on whether the user is logged in or not. + if ($GLOBALS['user']->uid) { + form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail))); + } + else { + form_set_error('mail', t('The e-mail address %email is already registered. Have you forgotten your password?', array('%email' => $mail, '@password' => url('user/password')))); } } @@ -1024,8 +1161,32 @@ function user_account_form_validate($form, &$form_state) { } } +/** + * Implements hook_user_presave(). + */ +function user_user_presave(&$edit, $account) { + if (!empty($edit['picture_upload'])) { + $edit['picture'] = $edit['picture_upload']; + } + // Delete picture if requested, and if no replacement picture was given. + elseif (!empty($edit['picture_delete'])) { + $edit['picture'] = NULL; + } + // Prepare user roles. + if (isset($edit['roles'])) { + $edit['roles'] = array_filter($edit['roles']); + } + + // Move account cancellation information into $user->data. + foreach (array('user_cancel_method', 'user_cancel_notify') as $key) { + if (isset($edit[$key])) { + $edit['data'][$key] = $edit[$key]; + } + } +} + function user_login_block($form) { - $form['#action'] = url($_GET['q'], array('query' => drupal_get_destination())); + $form['#action'] = url(current_path(), array('query' => drupal_get_destination())); $form['#id'] = 'user-login-form'; $form['#validate'] = user_login_default_validators(); $form['#submit'][] = 'user_login_submit'; @@ -2023,8 +2184,7 @@ function user_authenticate($name, $password) { // Update user to new password scheme if needed. if (user_needs_new_hash($account)) { - $account->pass = $password; - $account->save(); + user_save($account, array('pass' => $password)); } } } @@ -2079,16 +2239,16 @@ function user_external_login_register($name, $module) { $account = user_external_load($name); if (!$account) { // Register this new user. - $account = entity_create('user', array( + $userinfo = array( 'name' => $name, 'pass' => user_password(), 'init' => $name, 'status' => 1, 'access' => REQUEST_TIME - )); - $status = $account->save(); - // Terminate if an error occurred while saving the account. - if ($status != SAVED_NEW) { + ); + $account = user_save(drupal_anonymous_user(), $userinfo); + // Terminate if an error occurred during user_save(). + if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); return; } @@ -2236,8 +2396,7 @@ function _user_cancel($edit, $account, $method) { if (!empty($edit['user_cancel_notify'])) { _user_mail_notify('status_blocked', $account); } - $account->status = 0; - $account->save(); + user_save($account, array('status' => 0)); drupal_set_message(t('%name has been disabled.', array('%name' => $account->name))); watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE); break; @@ -2325,9 +2484,12 @@ function user_delete_multiple(array $uids) { * Page callback wrapper for user_view(). */ function user_view_page($account) { + if (is_object($account)) { + return user_view($account); + } // An administrator may try to view a non-existent account, // so we give them a 404 (versus a 403 for non-admins). - return is_object($account) ? user_view($account) : MENU_NOT_FOUND; + drupal_not_found(); } /** @@ -2991,8 +3153,7 @@ function user_user_operations_unblock($accounts) { foreach ($accounts as $account) { // Skip unblocking user if they are already unblocked. if ($account !== FALSE && $account->status == 0) { - $account->status = 1; - $account->save(); + user_save($account, array('status' => 1)); } } } @@ -3008,8 +3169,7 @@ function user_user_operations_block($accounts) { // For efficiency manually save the original account before applying any // changes. $account->original = clone $account; - $account->status = 0; - $account->save(); + user_save($account, array('status' => 0)); } } } @@ -3018,6 +3178,8 @@ function user_user_operations_block($accounts) { * Callback function for admin mass adding/deleting a user role. */ function user_multiple_role_edit($accounts, $operation, $rid) { + // The role name is not necessary as user_save() will reload the user + // object, but some modules' hook_user() may look at this first. $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField(); switch ($operation) { @@ -3030,8 +3192,7 @@ function user_multiple_role_edit($accounts, $operation, $rid) { // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; - $account->roles = $roles; - $account->save(); + user_save($account, array('roles' => $roles)); } } break; @@ -3044,8 +3205,7 @@ function user_multiple_role_edit($accounts, $operation, $rid) { // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; - $account->roles = $roles; - $account->save(); + user_save($account, array('roles' => $roles)); } } break; @@ -3135,9 +3295,7 @@ function user_multiple_cancel_confirm_submit($form, &$form_state) { if ($uid == $user->uid) { $admin_form_state = $form_state; unset($admin_form_state['values']['user_cancel_confirm']); - // The $user global is not a complete user entity, so load the full - // entity. - $admin_form_state['values']['_account'] = user_load($user->uid); + $admin_form_state['values']['_account'] = $user; user_cancel_confirm_form_submit(array(), $admin_form_state); } else { @@ -3211,7 +3369,7 @@ function user_build_filter_query(SelectInterface $query) { // the authenticated role. If so, then all users would be listed, and we can // skip adding it to the filter query. if ($key == 'permission') { - $account = entity_create('user', array()); + $account = new stdClass(); $account->uid = 'user_filter'; $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1); if (user_access($value, $account)) { @@ -3457,8 +3615,7 @@ function user_block_user_action(&$entity, $context = array()) { $uid = $GLOBALS['user']->uid; } $account = user_load($uid); - $account->status = 0; - $account->save(); + $account = user_save($account, array('status' => 0)); watchdog('action', 'Blocked user %name.', array('%name' => $account->name)); } @@ -3537,7 +3694,7 @@ function user_register_form($form, &$form_state) { drupal_goto('user/' . $user->uid); } - $form['#user'] = entity_create('user', array()); + $form['#user'] = drupal_anonymous_user(); $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; @@ -3557,7 +3714,7 @@ function user_register_form($form, &$form_state) { if ($admin) { // Redirect back to page which initiated the create request; // usually admin/people/create. - $form_state['redirect'] = $_GET['q']; + $form_state['redirect'] = current_path(); } $form['actions'] = array('#type' => 'actions'); @@ -3608,10 +3765,14 @@ function user_register_submit($form, &$form_state) { $account = $form['#user']; entity_form_submit_build_entity('user', $account, $form, $form_state); - $status = $account->save(); - // Terminate if an error occurred while saving the account. - if ($status =! SAVED_NEW) { + // Populate $edit with the properties of $account, which have been edited on + // this form by taking over all values, which appear in the form values too. + $edit = array_intersect_key((array) $account, $form_state['values']); + $account = user_save($account, $edit); + + // Terminate if an error occurred during user_save(). + if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); $form_state['redirect'] = ''; return; diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 438fedb..f24849c 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -264,8 +264,19 @@ function user_profile_form_submit($form, &$form_state) { // Remove unneeded values. form_state_values_clean($form_state); + // Before updating the account entity, keep an unchanged copy for use with + // user_save() later. This is necessary for modules implementing the user + // hooks to be able to react on changes by comparing the values of $account + // and $edit. + $account_unchanged = clone $account; + entity_form_submit_build_entity('user', $account, $form, $form_state); - $account->save(); + + // Populate $edit with the properties of $account, which have been edited on + // this form by taking over all values, which appear in the form values too. + $edit = array_intersect_key((array) $account, $form_state['values']); + + user_save($account_unchanged, $edit); $form_state['values']['uid'] = $account->uid; if (!empty($edit['pass'])) { @@ -389,9 +400,11 @@ function user_cancel_confirm_form_submit($form, &$form_state) { else { // Store cancelling method and whether to notify the user in $account for // user_cancel_confirm(). - $account->user_cancel_method = $form_state['values']['user_cancel_method']; - $account->user_cancel_notify = $form_state['values']['user_cancel_notify']; - $account->save(); + $edit = array( + 'user_cancel_method' => $form_state['values']['user_cancel_method'], + 'user_cancel_notify' => $form_state['values']['user_cancel_notify'], + ); + $account = user_save($account, $edit); _user_mail_notify('cancel_confirm', $account); drupal_set_message(t('A confirmation request to cancel your account has been sent to your e-mail address.')); watchdog('user', 'Sent account cancellation request to %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE); diff --git a/core/modules/user/user.test b/core/modules/user/user.test index c911088..bf0e486 100644 --- a/core/modules/user/user.test +++ b/core/modules/user/user.test @@ -166,9 +166,9 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.')); $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, t('Correct status field.')); $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.')); - $this->assertEqual($new_user->langcode, language_default()->langcode, t('Correct language field.')); - $this->assertEqual($new_user->preferred_langcode, language_default()->langcode, t('Correct preferred language field.')); - $this->assertEqual($new_user->picture, 0, t('Correct picture field.')); + $this->assertEqual($new_user->langcode, '', t('Correct language field.')); + $this->assertEqual($new_user->preferred_langcode, '', t('Correct preferred language field.')); + $this->assertEqual($new_user->picture, '', t('Correct picture field.')); $this->assertEqual($new_user->init, $mail, t('Correct init field.')); } @@ -553,8 +553,7 @@ class UserCancelTestCase extends DrupalWebTestCase { 'name' => 'user1', 'pass' => user_hash_password(trim($password)), ); - // We cannot use $account->save() here, because this would result in the - // password being hashed again. + // We cannot use user_save() here or the password would be hashed again. db_update('users') ->fields($account) ->condition('uid', 1) @@ -846,8 +845,8 @@ class UserCancelTestCase extends DrupalWebTestCase { // Create a regular user. $account = $this->drupalCreateUser(array()); // This user has no e-mail address. - $account->mail = ''; - $account->save(); + $edit = array('mail' => ''); + $account = user_save($account, $edit); // Create administrative user. $admin_user = $this->drupalCreateUser(array('administer users')); @@ -1175,7 +1174,7 @@ class UserPictureTestCase extends DrupalWebTestCase { // Load actual user data from database. $account = user_load($this->user->uid, TRUE); - $pic_path = !empty($account->picture) ? $account->picture->uri : NULL; + $pic_path = isset($account->picture) ? $account->picture->uri : NULL; // Check if image is displayed in user's profile page. $this->drupalGet('user'); @@ -1189,7 +1188,7 @@ class UserPictureTestCase extends DrupalWebTestCase { // Load actual user data from database. $account1 = user_load($this->user->uid, TRUE); - $this->assertFalse($account1->picture, 'User object has no picture'); + $this->assertNull($account1->picture, 'User object has no picture'); $file = file_load($account->picture->fid); $this->assertFalse($file, 'File is removed from database'); @@ -1205,7 +1204,7 @@ class UserPictureTestCase extends DrupalWebTestCase { // Load actual user data from database. $account = user_load($this->user->uid, TRUE); - return !empty($account->picture) ? $account->picture->uri : NULL; + return isset($account->picture) ? $account->picture->uri : NULL; } /** @@ -1678,7 +1677,7 @@ class UserSaveTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'User save test', - 'description' => 'Test account saving for arbitrary new uid.', + 'description' => 'Test user_save() for arbitrary new uid.', 'group' => 'User', ); } @@ -1693,15 +1692,16 @@ class UserSaveTestCase extends DrupalWebTestCase { $test_name = $this->randomName(); // Create the base user, based on drupalCreateUser(). - $user = entity_create('user', array( + $user = array( 'name' => $test_name, 'uid' => $test_uid, 'mail' => $test_name . '@example.com', + 'is_new' => TRUE, 'pass' => user_password(), 'status' => 1, - )); - $user->enforceIsNew(); - $user->save(); + ); + $user_by_return = user_save(drupal_anonymous_user(), $user); + $this->assertTrue($user_by_return, t('Loading user by return of user_save().')); // Test if created user exists. $user_by_uid = user_load($test_uid); @@ -1843,9 +1843,9 @@ class UserEditTestCase extends DrupalWebTestCase { // Create a regular user. $user1 = $this->drupalCreateUser(array()); // This user has no e-mail address. - $user1->mail = ''; - $user1->save(); - $this->drupalPost("user/$user1->uid/edit", array('mail' => ''), t('Save')); + $edit = array('mail' => ''); + $user1 = user_save($user1, $edit); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); $this->assertRaw(t("The changes have been saved.")); } } diff --git a/core/scripts/drupal.sh b/core/scripts/drupal.sh index cf17e68..fb5e82c 100755 --- a/core/scripts/drupal.sh +++ b/core/scripts/drupal.sh @@ -119,16 +119,15 @@ while ($param = array_shift($_SERVER['argv'])) { $cmd = substr($path['path'], 1); } elseif (isset($path['path'])) { - if (!isset($_GET['q'])) { - $_REQUEST['q'] = $_GET['q'] = $path['path']; - } + $_SERVER['SCRIPT_NAME'] = '/' . $cmd; + $_SERVER['REQUEST_URI'] = $path['path']; } // display setup in verbose mode if ($_verbose_mode) { echo "Hostname set to: {$_SERVER['HTTP_HOST']}\n"; echo "Script name set to: {$cmd}\n"; - echo "Path set to: {$_GET['q']}\n"; + echo "Path set to: {$path['path']}\n"; } } break; diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 47bfd48..cc2665f 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -155,7 +155,7 @@ All arguments are long options. One or more tests to be run. By default, these are interpreted as the names of test groups as shown at - ?q=admin/config/development/testing. + admin/config/development/testing. These group names typically correspond to module names like "User" or "Profile" or "System", but there is also a group "XML-RPC". If --class is specified then these are interpreted as the names of @@ -363,8 +363,6 @@ function simpletest_script_run_one_test($test_id, $test_class) { // Bootstrap Drupal. drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); - simpletest_classloader_register(); - $test = new $test_class($test_id); $test->run(); $info = $test->getInfo(); @@ -398,7 +396,7 @@ function simpletest_script_command($test_id, $test_class) { if ($args['color']) { $command .= ' --color'; } - $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class); + $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class"; return $command; } diff --git a/core/themes/bartik/templates/comment-wrapper.tpl.php b/core/themes/bartik/templates/comment-wrapper.tpl.php index dc06cf2..4ddb639 100644 --- a/core/themes/bartik/templates/comment-wrapper.tpl.php +++ b/core/themes/bartik/templates/comment-wrapper.tpl.php @@ -20,7 +20,7 @@ * the template. * * The following variables are provided for contextual information. - * - $node: Node entity the comments are attached to. + * - $node: Node object the comments are attached to. * The constants below the variables show the possible values and should be * used for comparison. * - $display_mode diff --git a/core/themes/bartik/templates/comment.tpl.php b/core/themes/bartik/templates/comment.tpl.php index 0b70594..1648580 100644 --- a/core/themes/bartik/templates/comment.tpl.php +++ b/core/themes/bartik/templates/comment.tpl.php @@ -46,7 +46,7 @@ * * These two variables are provided for context: * - $comment: Full comment object. - * - $node: Node entity the comments are attached to. + * - $node: Node object the comments are attached to. * * Other variables: * - $classes_array: Array of html class attribute values. It is flattened diff --git a/core/themes/bartik/templates/node.tpl.php b/core/themes/bartik/templates/node.tpl.php index 318197c..68f8ec5 100644 --- a/core/themes/bartik/templates/node.tpl.php +++ b/core/themes/bartik/templates/node.tpl.php @@ -42,7 +42,7 @@ * the template. * * Other variables: - * - $node: Full node entity. Contains data that may not be safe. + * - $node: Full node object. Contains data that may not be safe. * - $type: Node type, i.e. page, article, etc. * - $comment_count: Number of comments attached to the node. * - $uid: User ID of the node author. diff --git a/core/themes/bartik/templates/page.tpl.php b/core/themes/bartik/templates/page.tpl.php index f753093..bb3e18f 100644 --- a/core/themes/bartik/templates/page.tpl.php +++ b/core/themes/bartik/templates/page.tpl.php @@ -57,7 +57,7 @@ * - $action_links (array): Actions local to the page, such as 'Add menu' on * the menu administration interface. * - $feed_icons: A string of all feed icons for the current page. - * - $node: The node entity, if there is an automatically-loaded node + * - $node: The node object, if there is an automatically-loaded node * associated with the page, and the node ID is the second argument * in the page's path (e.g. node/12345 and node/12345/revisions, but not * comment/reply/12345). diff --git a/core/update.php b/core/update.php index 9797833..3b05dd0 100644 --- a/core/update.php +++ b/core/update.php @@ -461,13 +461,15 @@ if (update_access_allowed()) { // update.php ops. case 'selection': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + $token = request()->query->get('token'); + if (isset($token) && drupal_valid_token($token, 'update')) { $output = update_selection_page(); break; } case 'Apply pending updates': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + $token = request()->query->get('token'); + if (isset($token) && drupal_valid_token($token, 'update')) { // Generate absolute URLs for the batch processing (using $base_root), // since the batch API will pass them to url() which does not handle // update.php correctly by default. diff --git a/index.php b/index.php index b91fb1e..6ebb47f 100644 --- a/index.php +++ b/index.php @@ -11,11 +11,34 @@ * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory. */ +use Drupal\Core\DrupalKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; + /** * Root directory of Drupal installation. */ define('DRUPAL_ROOT', getcwd()); - +// Bootstrap the lowest level of what we need. require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; +drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + +// A request object from the HTTPFoundation to tell us about the request. +$request = Request::createFromGlobals(); + +// Set the global $request object. This is a temporary measure to +// keep legacy utility functions working. It should be moved to a dependency +// injection container at some point. +request($request); + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); -menu_execute_active_handler(); + +$dispatcher = new EventDispatcher(); +$resolver = new ControllerResolver(); + +$kernel = new DrupalKernel($dispatcher, $resolver); +$response = $kernel->handle($request); +$response->prepare($request); +$response->send(); +$kernel->terminate($request, $response); diff --git a/robots.txt b/robots.txt index 90bec1e..d590fbf 100644 --- a/robots.txt +++ b/robots.txt @@ -35,12 +35,12 @@ Disallow: /user/password/ Disallow: /user/login/ Disallow: /user/logout/ # Paths (no clean URLs) -Disallow: /?q=admin/ -Disallow: /?q=comment/reply/ -Disallow: /?q=filter/tips/ -Disallow: /?q=node/add/ -Disallow: /?q=search/ -Disallow: /?q=user/password/ -Disallow: /?q=user/register/ -Disallow: /?q=user/login/ -Disallow: /?q=user/logout/ +Disallow: /index.php/admin/ +Disallow: /index.php/comment/reply/ +Disallow: /index.php/filter/tips/ +Disallow: /index.php/node/add/ +Disallow: /index.php/search/ +Disallow: /index.php/user/password/ +Disallow: /index.php/user/register/ +Disallow: /index.php/user/login/ +Disallow: /index.php/user/logout/ diff --git a/web.config b/web.config index e990e03..06e4cbe 100644 --- a/web.config +++ b/web.config @@ -57,7 +57,9 @@ --> - + @@ -65,9 +67,6 @@ -