diff --git a/.gitmodules b/.gitmodules index e3a8f200d8c73a5d3792614af196405e73b3d1de..9fb9be1c85b830e0ab07f91634ee0bec71eb5aac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "vendor/NmcFramework"] path = vendor/NmcFramework url = git@github.unl.edu:UNL-Information-Services/NMC-PHP-Framework.git +[submodule "sites/all/modules/diff"] + path = sites/all/modules/diff + url = http://git.drupal.org/project/diff.git diff --git a/.htaccess.sample b/.htaccess.sample index 6d5bf017531b5f65e4077b161db20a99ef835033..9d6969ac0c71c03f5e795e5c5ec95bf893d84cb6 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -3,7 +3,7 @@ # # Protect files and directories from prying eyes. -<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$"> +<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(|~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$"> Order allow,deny </FilesMatch> @@ -56,6 +56,13 @@ DirectoryIndex index.php index.html index.htm <IfModule mod_rewrite.c> RewriteEngine on + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + # THIS SECTION IS FOR UNL SUBSITES # DO NOT EDIT!!!! @@ -95,14 +102,15 @@ DirectoryIndex index.php index.html index.htm # To redirect all users to access the site WITH the 'www.' prefix, # (http://example.com/... will be redirected to http://www.example.com/...) # uncomment the following: + # RewriteCond %{HTTP_HOST} . # RewriteCond %{HTTP_HOST} !^www\. [NC] - # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # # To redirect all users to access the site WITHOUT the 'www.' prefix, # (http://www.example.com/... will be redirected to http://example.com/...) # uncomment the following: # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - # RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] # Modify the RewriteBase if you are using Drupal in a subdirectory or in a # VirtualDocumentRoot and the rewrite rules are not working properly. diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 69d94de730f3401188fafbcb1b15dcc61fb72964..5b655b8bcefc6c018a6e9fb3c29fb98097b7a82f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,71 @@ +Drupal 7.22, 2013-04-03 +----------------------- +- Allowed the drupal_http_request() function to be overridden so that + additional HTTP request capabilities can be added by contributed modules. +- Changed the Simpletest module to allow PSR-0 test classes to be used in + Drupal 7. +- Removed an unnecessary "Content-Disposition" header from private file + downloads; it prevented many private files from being viewed inline in a web + browser. +- Changed various field API functions to allow them to optionally act on a + single field within an entity (API addition: http://drupal.org/node/1825844). +- Fixed a bug which prevented Drupal's file transfer functionality from working + on some PHP 5.4 systems. +- Fixed incorrect log message when theme() is called for a theme hook that does + not exist (minor string change). +- Fixed Drupal's token-replacement system to allow spaces in the token value. +- Changed the default behavior after a user creates a node they do not have + access to view. The user will now be redirected to the front page rather than + an access denied page. +- Fixed a bug which prevented empty HTTP headers (such as "0") from being set. + (Minor behavior change: Callers of drupal_add_http_header() must now set + FALSE explicitly to prevent a header from being sent at all; this was already + indicated in the function's documentation.) +- Fixed OpenID errors when more than one module implements hook_openid(). The + behavior is now changed so that if more than one module tries to set the same + parameter, the last module's change takes effect. +- Fixed a serious documentation bug: The $name variable in the + taxonomy-term.tpl.php theme template was incorrectly documented as being + sanitized when in fact it is not. +- Fixed a bug which prevented Drupal 6 to Drupal 7 upgrades on sites which had + duplicate permission names in the User module's database tables. +- Added an empty "datatype" attribute to taxonomy term and username links to + make the RDFa markup upward compatible with RDFa 1.1 (minor markup addition). +- Fixed a bug which caused the denial-of-service protection added in Drupal + 7.20 to break certain valid image URLs that had an extra slash in them. +- Fixed a bug with update queries in the SQLite database driver that prevented + Drupal from being installed with SQLite on PHP 5.4. +- Fixed enforced dependencies errors updating to recent versions of Drupal 7 on + certain non-MySQL databases. +- Refactored the Field module's caching behavior to obtain large improvements + in memory usage for sites with many fields and instances (API addition: + http://drupal.org/node/1915646). +- Fixed entity argument not being passed to implementations of + hook_file_download_access_alter(). The fix adds an additional context + parameter that can be passed when calling drupal_alter() for any hook (API + change: http://drupal.org/node/1882722). +- Fixed broken support for translatable comment fields (API change: + http://drupal.org/node/1874724). +- Added an assertThemeOutput() method to Simpletest to allow tests to check + that themed output matches an expected HTML string (API addition). +- Added a link to "Install another module" after a module has been successfully + downloaded via the Update Manager (UI change). +- Added an optional "exclusive" flag to installation profile .info files which + allows Drupal distributions to force a profile to be selected during + installation (API addition). +- Fixed a bug which caused the database API to not properly close database + connections. +- Added a link to the URL for running cron from outside the site to the Cron + settings page (UI change). +- Fixed a bug which prevented image styles from being reverted on PHP 5.4. +- Made the default .htaccess rules protocol sensitive to improve security for + sites which use HTTPS and redirect between "www" and non-"www" versions of + the page. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + Drupal 7.21, 2013-03-06 ----------------------- - Allowed sites using the 'image_allow_insecure_derivatives' variable to still diff --git a/README.md b/README.md index fb118e8fb2519e73d4033a27d4604df6dabb02ae..03445f26502954426732a90d4687a73199f729d0 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,9 @@ In this example the web root is /Library/WebServer/Documents and Apache runs as Added an example of the $default_domains array. Added the stub record needed for creating site aliases. - * modules/image/image.field.inc - - - theme_image_formatter ignores attributes so classes can't be added to an image in a theme (needed for photo frame). See http://drupal.org/node/1025796#comment-4298698 and http://drupal.org/files/issues/1025796.patch + * modules/field/modules/text/text.module + - Add nl2br() on Plain Text processor. See http://drupal.org/node/1152216#comment-7174876 ## Hacks of Contrib modules: @@ -120,6 +119,14 @@ In this example the web root is /Library/WebServer/Documents and Apache runs as - Convert FILE_ENTITY_DEFAULT_ALLOWED_EXTENSIONS to the new variable. See http://drupal.org/node/1846674#comment-6760286 + * redirect + + - Merge global redirect functions into Redirect module. See http://drupal.org/node/905914 + + * upload_replace.module + + - Drupal 7 bug fixes. See http://drupal.org/node/1115484#comment-5646558 + * webform.module - Make Safe Key values accessible via tokens. See http://drupal.org/node/1340010#comment-6709520 Patch applied: http://drupal.org/files/webform-1340010-19.patch diff --git a/authorize.php b/authorize.php index d14fa6e5992c08682c68a69cf1ab2311f1ce0977..3ea2b20ace5146a5983dcebe6178fd5a53f00bd3 100644 --- a/authorize.php +++ b/authorize.php @@ -4,16 +4,16 @@ * @file * Administrative script for running authorized file operations. * - * Using this script, the site owner (the user actually owning the files on - * the webserver) can authorize certain file-related operations to proceed - * with elevated privileges, for example to deploy and upgrade modules or - * themes. Users should not visit this page directly, but instead use an - * administrative user interface which knows how to redirect the user to this - * script as part of a multistep process. This script actually performs the - * selected operations without loading all of Drupal, to be able to more - * gracefully recover from errors. Access to the script is controlled by a - * global killswitch in settings.php ('allow_authorize_operations') and via - * the 'administer software updates' permission. + * Using this script, the site owner (the user actually owning the files on the + * webserver) can authorize certain file-related operations to proceed with + * elevated privileges, for example to deploy and upgrade modules or themes. + * Users should not visit this page directly, but instead use an administrative + * user interface which knows how to redirect the user to this script as part of + * a multistep process. This script actually performs the selected operations + * without loading all of Drupal, to be able to more gracefully recover from + * errors. Access to the script is controlled by a global killswitch in + * settings.php ('allow_authorize_operations') and via the 'administer software + * updates' permission. * * There are helper functions for setting up an operation to run via this * system in modules/system/system.module. For more information, see: @@ -21,16 +21,17 @@ */ /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); /** - * Global flag to identify update.php and authorize.php runs, and so - * avoid various unwanted operations, such as hook_init() and - * hook_exit() invokes, css/js preprocessing and translation, and - * solve some theming issues. This flag is checked on several places - * in Drupal code (not just authorize.php). + * Global flag to identify update.php and authorize.php runs. + * + * Identifies update.php and authorize.php runs, avoiding unwanted operations + * such as hook_init() and hook_exit() invokes, css/js preprocessing and + * translation, and solves some theming issues. The flag is checked in other + * places in Drupal code (not just authorize.php). */ define('MAINTENANCE_MODE', 'update'); @@ -51,7 +52,7 @@ function authorize_access_denied_page() { * have access to the 'administer software updates' permission. * * @return - * TRUE if the current user can run authorize.php, otherwise FALSE. + * TRUE if the current user can run authorize.php, and FALSE if not. */ function authorize_access_allowed() { return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates'); diff --git a/example.robots.txt b/example.robots.txt deleted file mode 100644 index 7de84356065bf3931329d6be21063f765d8bfaf0..0000000000000000000000000000000000000000 --- a/example.robots.txt +++ /dev/null @@ -1,60 +0,0 @@ -# -# robots.txt -# -# This file is to prevent the crawling and indexing of certain parts -# of your site by web crawlers and spiders run by sites like Yahoo! -# and Google. By telling these "robots" where not to go on your site, -# you save bandwidth and server resources. -# -# This file will be ignored unless it is at the root of your host: -# Used: http://example.com/robots.txt -# Ignored: http://example.com/site/robots.txt -# -# For more information about the robots.txt standard, see: -# http://www.robotstxt.org/wc/robots.html -# -# For syntax checking, see: -# http://www.sxw.org.uk/computing/robots/check.html - -User-agent: * -Crawl-delay: 10 -# Directories -Disallow: /includes/ -Disallow: /misc/ -Disallow: /modules/ -Disallow: /profiles/ -Disallow: /scripts/ -Disallow: /themes/ -# Files -Disallow: /CHANGELOG.txt -Disallow: /cron.php -Disallow: /INSTALL.mysql.txt -Disallow: /INSTALL.pgsql.txt -Disallow: /INSTALL.sqlite.txt -Disallow: /install.php -Disallow: /INSTALL.txt -Disallow: /LICENSE.txt -Disallow: /MAINTAINERS.txt -Disallow: /update.php -Disallow: /UPGRADE.txt -Disallow: /xmlrpc.php -# Paths (clean URLs) -Disallow: /admin/ -Disallow: /comment/reply/ -Disallow: /filter/tips/ -Disallow: /node/add/ -Disallow: /search/ -Disallow: /user/register/ -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/ diff --git a/includes/ajax.inc b/includes/ajax.inc index 4107029fe19c760cada793c135961176201dfe92..ab0111cedec8ed375f61768ecf8906ac08777ae4 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -251,8 +251,8 @@ function ajax_render($commands = array()) { // reliably diffed with array_diff_key(), since the number can change // due to factors unrelated to the inline content, so for now, we strip // the inline items from Ajax responses, and can add support for them - // when drupal_add_css() and drupal_add_js() are changed to using md5() - // or some other hash of the inline content. + // when drupal_add_css() and drupal_add_js() are changed to use a hash + // of the inline content as the array key. foreach ($items[$type] as $key => $item) { if (is_numeric($key)) { unset($items[$type][$key]); @@ -836,7 +836,8 @@ function ajax_command_insert($selector, $html, $settings = NULL) { * @return * An array suitable for use with the ajax_render() function. * - * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink + * See + * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink */ function ajax_command_replace($selector, $html, $settings = NULL) { return array( diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 92ba80cf2b06447b4f9119fd30bc7dfb959b3877..54b546d2a37b8346d58968e9904692bb111ce4eb 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.21'); +define('VERSION', '7.22'); /** * Core API compatibility. @@ -740,7 +740,6 @@ function drupal_settings_initialize() { if (isset($base_url)) { // Parse fixed base URL from settings.php. $parts = parse_url($base_url); - $http_protocol = $parts['scheme']; if (!isset($parts['path'])) { $parts['path'] = ''; } @@ -835,7 +834,7 @@ function drupal_settings_initialize() { * than by consulting the database. * * @return - * The filename of the requested item. + * The filename of the requested item or NULL if the item is not found. */ function drupal_get_filename($type, $name, $filename = NULL) { // The location of files will not change during the request, so do not use @@ -1210,10 +1209,11 @@ function _drupal_set_preferred_header_name($name = NULL) { * Headers are set in drupal_add_http_header(). Default headers are not set * if they have been replaced or unset using drupal_add_http_header(). * - * @param $default_headers - * An array of headers as name/value pairs. - * @param $single - * If TRUE and headers have already be sent, send only the specified header. + * @param array $default_headers + * (optional) An array of headers as name/value pairs. + * @param bool $only_default + * (optional) If TRUE and headers have already been sent, send only the + * specified headers. */ function drupal_send_headers($default_headers = array(), $only_default = FALSE) { $headers_sent = &drupal_static(__FUNCTION__, FALSE); @@ -1236,7 +1236,7 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE) header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value); } // Skip headers that have been unset. - elseif ($value) { + elseif ($value !== FALSE) { header($header_names[$name_lower] . ': ' . $value); } } @@ -1446,8 +1446,9 @@ function drupal_unpack($obj, $field = 'data') { * Basically, you can put variables like @name into your string, and t() will * substitute their sanitized values at translation time. (See the * Localization API pages referenced above and the documentation of - * format_string() for details.) Translators can then rearrange the string as - * necessary for the language (e.g., in Spanish, it might be "blog de @name"). + * format_string() for details about how to define variables in your string.) + * Translators can then rearrange the string as necessary for the language + * (e.g., in Spanish, it might be "blog de @name"). * * During the Drupal installation phase, some resources used by t() wil not be * available to code that needs localization. See st() and get_t() for @@ -1510,21 +1511,34 @@ function t($string, array $args = array(), array $options = array()) { } /** - * Replaces placeholders with sanitized values in a string. + * Formats a string for HTML display by replacing variable placeholders. + * + * This function replaces variable placeholders in a string with the requested + * values and escapes the values so they can be safely displayed as HTML. It + * should be used on any unknown text that is intended to be printed to an HTML + * page (especially text that may have come from untrusted users, since in that + * case it prevents cross-site scripting and other security problems). + * + * In most cases, you should use t() rather than calling this function + * directly, since it will translate the text (on non-English-only sites) in + * addition to formatting it. * * @param $string * A string containing placeholders. * @param $args * An associative array of replacements to make. Occurrences in $string of - * any key in $args are replaced with the corresponding value, after - * sanitization. The sanitization function depends on the first character of - * the key: - * - !variable: Inserted as is. Use this for text that has already been - * sanitized. - * - @variable: Escaped to HTML using check_plain(). Use this for anything - * displayed on a page on the site. - * - %variable: Escaped as a placeholder for user-submitted content using - * drupal_placeholder(), which shows up as <em>emphasized</em> text. + * any key in $args are replaced with the corresponding value, after optional + * sanitization and formatting. The type of sanitization and formatting + * depends on the first character of the key: + * - @variable: Escaped to HTML using check_plain(). Use this as the default + * choice for anything displayed on a page on the site. + * - %variable: Escaped to HTML and formatted using drupal_placeholder(), + * which makes it display as <em>emphasized</em> text. + * - !variable: Inserted as is, with no sanitization or formatting. Only use + * this for text that has already been prepared for HTML display (for + * example, user-supplied text that has already been run through + * check_plain() previously, or is expected to contain some limited HTML + * tags and has already been run through filter_xss() previously). * * @see t() * @ingroup sanitization diff --git a/includes/cache.inc b/includes/cache.inc index a19d3c38cf2f62fc3935ac8a31ed9e38e01b4908..f76164b914df09b2f6f4d5adb61644abfa13c5d1 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -80,43 +80,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * same name. Other implementations might want to store several bins in data * structures that get flushed together. While it is not a problem for most * cache bins if the entries in them are flushed before their expire time, some - * might break functionality or are extremely expensive to recalculate. These - * will be marked with a (*). The other bins expired automatically by core. - * Contributed modules can add additional bins and get them expired - * automatically by implementing hook_flush_caches(). - * - * - cache: Generic cache storage bin (used for variables, theme registry, - * locale date, list of simpletest tests etc). - * - * - cache_block: Stores the content of various blocks. - * - * - cache field: Stores the field data belonging to a given object. - * - * - cache_filter: Stores filtered pieces of content. - * - * - cache_form(*): Stores multistep forms. Flushing this bin means that some - * forms displayed to users lose their state and the data already submitted - * to them. - * - * - cache_menu: Stores the structure of visible navigation menus per page. - * - * - cache_page: Stores generated pages for anonymous users. It is flushed - * very often, whenever a page changes, at least for every ode and comment - * submission. This is the only bin affected by the page cache setting on - * the administrator panel. - * - * - cache path: Stores the system paths that have an alias. - * - * - cache update(*): Stores available releases. The update server (for - * example, drupal.org) needs to produce the relevant XML for every project - * installed on the current site. As this is different for (almost) every - * site, it's very expensive to recalculate for the update server. + * might break functionality or are extremely expensive to recalculate. The + * other bins are expired automatically by core. Contributed modules can add + * additional bins and get them expired automatically by implementing + * hook_flush_caches(). * * The reasons for having several bins are as follows: - * - * - smaller bins mean smaller database tables and allow for faster selects and - * inserts - * - we try to put fast changing cache items and rather static ones into + * - Smaller bins mean smaller database tables and allow for faster selects and + * inserts. + * - We try to put fast changing cache items and rather static ones into * different bins. The effect is that only the fast changing bins will need a * lot of writes to disk. The more static bins will also be better cacheable * with MySQL's query cache. @@ -125,13 +97,27 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. + * serialized before insertion. Strings will be stored as plain text and are + * not serialized. * @param $bin - * The cache bin to store the data in. Valid core values are 'cache_block', - * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form', - * 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default - * cache. + * The cache bin to store the data in. Valid core values are: + * - cache: (default) Generic cache storage bin (used for theme registry, + * locale date, list of simpletest tests, etc.). + * - cache_block: Stores the content of various blocks. + * - cache_bootstrap: Stores the class registry, the system list of modules, + * the list of which modules implement which hooks, and the Drupal variable + * list. + * - cache_field: Stores the field data belonging to a given object. + * - cache_filter: Stores filtered pieces of content. + * - cache_form: Stores multistep forms. Flushing this bin means that some + * forms displayed to users lose their state and the data already submitted + * to them. This bin should not be flushed before its expired time. + * - cache_menu: Stores the structure of visible navigation menus per page. + * - cache_page: Stores generated pages for anonymous users. It is flushed + * very often, whenever a page changes, at least for every node and comment + * submission. This is the only bin affected by the page cache setting on + * the administrator panel. + * - cache_path: Stores the system paths that have an alias. * @param $expire * One of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless @@ -141,6 +127,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. * + * @see _update_cache_set() * @see cache_get() */ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { diff --git a/includes/common.inc b/includes/common.inc index 8994b3f4a48e9129737cc506acb852df09257aef..ffe79b80b693a4edb7bf02ab70e7325170adba30 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -281,7 +281,7 @@ function drupal_get_rdf_namespaces() { /** * Adds output to the HEAD tag of the HTML page. * - * This function can be called as long the headers aren't sent. Pass no + * This function can be called as long as the headers aren't sent. Pass no * arguments (or NULL for both) to retrieve the currently stored elements. * * @param $data @@ -785,6 +785,13 @@ function drupal_access_denied() { * - data: A string containing the response body that was received. */ function drupal_http_request($url, array $options = array()) { + // Allow an alternate HTTP client library to replace Drupal's default + // implementation. + $override_function = variable_get('drupal_http_request_function', FALSE); + if (!empty($override_function) && function_exists($override_function)) { + return $override_function($url, $options); + } + $result = new stdClass(); // Parse the URL and make sure we can handle the schema. @@ -1167,7 +1174,8 @@ function fix_gpc_magic() { /** * Verifies the syntax of the given e-mail address. * - * See @link http://tools.ietf.org/html/rfc5321 RFC 5321 @endlink for details. + * This uses the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * * @param $mail * A string containing an e-mail address. @@ -2382,6 +2390,14 @@ function drupal_attributes(array $attributes = array()) { * internal links output by modules should be generated by this function if * possible. * + * However, for links enclosed in translatable text you should use t() and + * embed the HTML anchor tag directly in the translated string. For example: + * @code + * t('Visit the <a href="@url">settings</a> page', array('@url' => url('admin'))); + * @endcode + * This keeps the context of the link title ('settings' in the example) for + * translators. + * * @param string $text * The translated link text for the anchor tag. * @param string $path @@ -2782,7 +2798,7 @@ function drupal_set_time_limit($time_limit) { * The name of the item for which the path is requested. * * @return - * The path to the requested item. + * The path to the requested item or an empty string if the item is not found. */ function drupal_get_path($type, $name) { return dirname(drupal_get_filename($type, $name)); @@ -3872,7 +3888,16 @@ function drupal_html_id($id) { // requested id. $_POST['ajax_html_ids'] contains the ids as they were // returned by this function, potentially with the appended counter, so // we parse that to reconstruct the $seen_ids array. - foreach ($_POST['ajax_html_ids'] as $seen_id) { + if (is_array($_POST['ajax_html_ids'])) { + $ajax_html_ids = $_POST['ajax_html_ids']; + } + else { + // jquery.form.js may send the server a comma-separated string instead + // of an array (see http://drupal.org/node/1575060), so we need to + // convert it to an array in that case. + $ajax_html_ids = explode(',', $_POST['ajax_html_ids']); + } + foreach ($ajax_html_ids as $seen_id) { // We rely on '--' being used solely for separating a base id from the // counter, which this function ensures when returning an id. $parts = explode('--', $seen_id, 2); @@ -5041,6 +5066,11 @@ function drupal_get_private_key() { * * @param $value * An additional value to base the token on. + * + * @return string + * A 43-character URL-safe token for validation, based on the user session ID, + * the global $drupal_hash_salt variable from settings.php, and the + * 'drupal_private_key' configuration variable. */ function drupal_get_token($value = '') { return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt()); @@ -5571,7 +5601,7 @@ function drupal_pre_render_link($element) { * @code * $node->content['links'] = array( * '#theme' => 'links__node', - * '#pre_render' = array('drupal_pre_render_links'), + * '#pre_render' => array('drupal_pre_render_links'), * 'comment' => array( * '#theme' => 'links__node__comment', * '#links' => array( diff --git a/includes/database/database.inc b/includes/database/database.inc index cae50fb87a71bcd44dfe427d2b92e7054e9ed252..339c9b03eaf37993c064df158f6efe79b0805acc 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -167,7 +167,7 @@ * } * @endcode * - * @link http://drupal.org/developing/api/database @endlink + * @see http://drupal.org/developing/api/database */ @@ -194,7 +194,7 @@ abstract class DatabaseConnection extends PDO { /** * The key representing this connection. - * + * * The key is a unique string which identifies a database connection. A * connection can be a single server or a cluster of master and slaves (use * target to pick between master and slave). @@ -303,12 +303,28 @@ abstract class DatabaseConnection extends PDO { // Call PDO::__construct and PDO::setAttribute. parent::__construct($dsn, $username, $password, $driver_options); - // Set a specific PDOStatement class if the driver requires that. + // Set a Statement class, unless the driver opted out. if (!empty($this->statementClass)) { $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); } } + /** + * Destroys this Connection object. + * + * PHP does not destruct an object if it is still referenced in other + * variables. In case of PDO database connection objects, PHP only closes the + * connection when the PDO object is destructed, so any references to this + * object may cause the number of maximum allowed connections to be exceeded. + */ + public function destroy() { + // Destroy all references to this connection by setting them to NULL. + // The Statement class attribute only accepts a new value that presents a + // proper callable, so we reset it to PDOStatement. + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + $this->schema = NULL; + } + /** * Returns the default query options for any given query. * @@ -1627,8 +1643,8 @@ abstract class Database { */ final public static function removeConnection($key) { if (isset(self::$databaseInfo[$key])) { + self::closeConnection(NULL, $key); unset(self::$databaseInfo[$key]); - unset(self::$connections[$key]); return TRUE; } else { @@ -1694,11 +1710,24 @@ abstract class Database { if (!isset($key)) { $key = self::$activeKey; } - // To close the connection, we need to unset the static variable. + // To close a connection, it needs to be set to NULL and removed from the + // static variable. In all cases, closeConnection() might be called for a + // connection that was not opened yet, in which case the key is not defined + // yet and we just ensure that the connection key is undefined. if (isset($target)) { + if (isset(self::$connections[$key][$target])) { + self::$connections[$key][$target]->destroy(); + self::$connections[$key][$target] = NULL; + } unset(self::$connections[$key][$target]); } else { + if (isset(self::$connections[$key])) { + foreach (self::$connections[$key] as $target => $connection) { + self::$connections[$key][$target]->destroy(); + self::$connections[$key][$target] = NULL; + } + } unset(self::$connections[$key]); } } @@ -1852,8 +1881,8 @@ class DatabaseTransaction { */ protected $name; - public function __construct(DatabaseConnection &$connection, $name = NULL) { - $this->connection = &$connection; + public function __construct(DatabaseConnection $connection, $name = NULL) { + $this->connection = $connection; // If there is no transaction depth, then no transaction has started. Name // the transaction 'drupal_transaction'. if (!$depth = $connection->transactionDepth()) { diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 7ad019e58308b0e00759dccf59a055eb9bbeb1c8..00d81f473d29211449f8310bd7708d3839fe5c3a 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -13,11 +13,11 @@ class DatabaseConnection_mysql extends DatabaseConnection { /** - * Flag to indicate if we have registered the nextID cleanup function. + * Flag to indicate if the cleanup function in __destruct() should run. * * @var boolean */ - protected $shutdownRegistered = FALSE; + protected $needsCleanup = FALSE; public function __construct(array $connection_options = array()) { // This driver defaults to transaction support, except if explicitly passed FALSE. @@ -78,6 +78,12 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->exec(implode('; ', $connection_options['init_commands'])); } + public function __destruct() { + if ($this->needsCleanup) { + $this->nextIdDelete(); + } + } + public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); } @@ -115,12 +121,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id)); $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); } - if (!$this->shutdownRegistered) { - // Use register_shutdown_function() here to keep the database system - // independent of Drupal. - register_shutdown_function(array($this, 'nextIdDelete')); - $shutdownRegistered = TRUE; - } + $this->needsCleanup = TRUE; return $new_id; } diff --git a/includes/database/query.inc b/includes/database/query.inc index 612985e025e7c0336f8809fcda5a622760efb966..8beeef1e86029fae1ea3276e5be5991412e0d448 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1898,8 +1898,13 @@ class DatabaseCondition implements QueryConditionInterface, Countable { function __clone() { $this->changed = TRUE; foreach ($this->conditions as $key => $condition) { - if ($key !== '#conjunction' && $condition['field'] instanceOf QueryConditionInterface) { - $this->conditions[$key]['field'] = clone($condition['field']); + if ($key !== '#conjunction') { + if ($condition['field'] instanceOf QueryConditionInterface) { + $this->conditions[$key]['field'] = clone($condition['field']); + } + if ($condition['value'] instanceOf SelectQueryInterface) { + $this->conditions[$key]['value'] = clone($condition['value']); + } } } } diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index 74ff9ba200ecf3d32ee07c6eb4447a8edc8e1683..1bf609db10f5e42e8e10bfa2dae10c3b7a108289 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -57,39 +57,18 @@ class InsertQuery_sqlite extends InsertQuery { * we don't select those rows. * * A query like this one: - * UPDATE test SET name = 'newname' WHERE tid = 1 + * UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1 * will become: - * UPDATE test SET name = 'newname' WHERE tid = 1 AND name <> 'newname' + * UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1 AND (col1 <> 'newcol1' OR col2 <> 'newcol2') */ class UpdateQuery_sqlite extends UpdateQuery { - /** - * Helper function that removes the fields that are already in a condition. - * - * @param $fields - * The fields. - * @param QueryConditionInterface $condition - * A database condition. - */ - protected function removeFieldsInCondition(&$fields, QueryConditionInterface $condition) { - foreach ($condition->conditions() as $child_condition) { - if ($child_condition['field'] instanceof QueryConditionInterface) { - $this->removeFieldsInCondition($fields, $child_condition['field']); - } - else { - unset($fields[$child_condition['field']]); - } - } - } - public function execute() { if (!empty($this->queryOptions['sqlite_return_matched_rows'])) { return parent::execute(); } - // Get the fields used in the update query, and remove those that are already - // in the condition. + // Get the fields used in the update query. $fields = $this->expressionFields + $this->fields; - $this->removeFieldsInCondition($fields, $this->condition); // Add the inverse of the fields to the condition. $condition = new DatabaseCondition('OR'); diff --git a/includes/file.inc b/includes/file.inc index 278be3ddcefaffd28894898636f88874122a1c5e..e9a567e9c5748beefd47098ae893048eeccb4d93 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -89,7 +89,7 @@ define('FILE_STATUS_PERMANENT', 1); * wrappers that are appropriate for particular usage. For example, this returns * only stream wrappers that use local file storage: * @code - * $local_stream_wrappers = file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL); + * $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL); * @endcode * * The $filter parameter can only filter to types containing a particular flag. @@ -99,7 +99,7 @@ define('FILE_STATUS_PERMANENT', 1); * array_diff_key() function can be used to help with this. For example, this * returns only stream wrappers that do not use local file storage: * @code - * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL)); + * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL)); * @endcode * * @param $filter @@ -282,10 +282,6 @@ function file_stream_wrapper_uri_normalize($uri) { $uri = $scheme . '://' . $target; } } - else { - // The default scheme is file:// - $url = 'file://' . $uri; - } return $uri; } @@ -834,9 +830,8 @@ function file_valid_uri($uri) { * A string specifying the filepath or URI of the source file. * @param $destination * A URI containing the destination that $source should be copied to. The - * URI may be a bare filepath (without a scheme) and in that case the default - * scheme (file://) will be used. If this value is omitted, Drupal's default - * files scheme will be used, usually "public://". + * URI may be a bare filepath (without a scheme). If this value is omitted, + * Drupal's default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. @@ -892,7 +887,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST $destination = file_destination($destination, $replace); if ($destination === FALSE) { drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); - watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination)); + watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%directory' => $destination)); return FALSE; } @@ -2481,20 +2476,10 @@ function file_directory_temp() { function file_get_content_headers($file) { $name = mime_header_encode($file->filename); $type = mime_header_encode($file->filemime); - // Serve images, text, and flash content for display rather than download. - $inline_types = variable_get('file_inline_types', array('^text/', '^image/', 'flash$')); - $disposition = 'attachment'; - foreach ($inline_types as $inline_type) { - // Exclamation marks are used as delimiters to avoid escaping slashes. - if (preg_match('!' . $inline_type . '!', $file->filemime)) { - $disposition = 'inline'; - } - } return array( 'Content-Type' => $type, 'Content-Length' => $file->filesize, - 'Content-Disposition' => $disposition . '; filename="' . $name . '"', 'Cache-Control' => 'private', ); } diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc index 023b866e355ef88f23e30ce317bfe087deab703a..6c55b2f43f59a32cf38b1462dca8127bda1ac5a7 100644 --- a/includes/filetransfer/filetransfer.inc +++ b/includes/filetransfer/filetransfer.inc @@ -406,10 +406,20 @@ class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator { */ function __construct($path) { parent::__construct($path); + $this->skipdots(); + } + + function rewind() { + parent::rewind(); + $this->skipdots(); } function next() { parent::next(); + $this->skipdots(); + } + + protected function skipdots() { while ($this->isDot()) { parent::next(); } diff --git a/includes/form.inc b/includes/form.inc index aa90eca691ad4a23f76d574641311cb448b5aa5b..8ae8065a279d9524d184591095929c018dccece9 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -3058,7 +3058,6 @@ function form_process_radios($element) { */ function theme_checkbox($variables) { $element = $variables['element']; - $t = get_t(); $element['#attributes']['type'] = 'checkbox'; element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); @@ -3662,35 +3661,6 @@ function form_pre_render_fieldset($element) { /** * Creates a group formatted as vertical tabs. * - * Note that autocomplete callbacks should include special handling as the - * user's input may contain forward slashes. If the user-submitted string has a - * '/' in the text that is sent in the autocomplete request, the menu system - * will split the text and pass it to the callback as multiple arguments. - * - * Suppose your autocomplete path in the menu system is 'mymodule_autocomplete.' - * In your form you have: - * @code - * '#autocomplete_path' => 'mymodule_autocomplete/' . $some_key . '/' . $some_id, - * @endcode - * The user types in "keywords" so the full path called is: - * 'mymodule_autocomplete/$some_key/$some_id/keywords' - * - * You should include code similar to the following to handle slashes in the - * input: - * @code - * function mymodule_autocomplete_callback($arg1, $arg2, $keywords) { - * $args = func_get_args(); - * // We need to remove $arg1 and $arg2 from the beginning of the array so we - * // are left with the keywords. - * array_shift($args); - * array_shift($args); - * // We store the user's original input in $keywords, including any slashes. - * $keywords = implode('/', $args); - * - * // Your code here. - * } - * @endcode - * * @param $element * An associative array containing the properties and children of the * fieldset. @@ -4039,8 +4009,6 @@ function theme_file($variables) { */ function theme_form_element($variables) { $element = &$variables['element']; - // This is also used in the installer, pre-database setup. - $t = get_t(); // This function is invoked as theme wrapper, but the rendered form element // may not necessarily have been processed by form_builder(). @@ -4199,7 +4167,7 @@ function _form_set_class(&$element, $class = array()) { if (!empty($element['#required'])) { $element['#attributes']['class'][] = 'required'; } - if (isset($element['#parents']) && form_get_error($element) !== NULL) { + if (isset($element['#parents']) && form_get_error($element) !== NULL && !empty($element['#validated'])) { $element['#attributes']['class'][] = 'error'; } } diff --git a/includes/image.inc b/includes/image.inc index ee5a086de64b0843e2558b2296d95ebdc08398f6..e30a33855384333d1b0848e2395e8017a4b8bc23 100644 --- a/includes/image.inc +++ b/includes/image.inc @@ -233,11 +233,11 @@ function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NUL * @param $image * An image object returned by image_load(). * @param $width - * The target width, in pixels. This value is omitted then the scaling will - * based only on the height value. + * The target width, in pixels. If this value is NULL then the scaling will + * be based only on the height value. * @param $height - * The target height, in pixels. This value is omitted then the scaling will - * based only on the width value. + * The target height, in pixels. If this value is NULL then the scaling will + * be based only on the width value. * @param $upscale * Boolean indicating that files smaller than the dimensions will be scaled * up. This generally results in a low quality image. diff --git a/includes/install.core.inc b/includes/install.core.inc index 9805e1c8862cf198dce1b87925399c54e7e411dd..7a694e9bbfe066a07efc54ad192f52fa6d49b45e 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1041,7 +1041,21 @@ function install_select_profile(&$install_state) { } /** - * Selects an installation profile from a list or from a $_POST submission. + * Selects an installation profile. + * + * A profile will be selected if: + * - Only one profile is available, + * - A profile was submitted through $_POST, + * - Exactly one of the profiles is marked as "exclusive". + * If multiple profiles are marked as "exclusive" then no profile will be + * selected. + * + * @param array $profiles + * An associative array of profiles with the machine-readable names as keys. + * + * @return + * The machine-readable name of the selected profile or NULL if no profile was + * selected. */ function _install_select_profile($profiles) { if (sizeof($profiles) == 0) { @@ -1061,6 +1075,23 @@ function _install_select_profile($profiles) { } } } + // Check for a profile marked as "exclusive" and ensure that only one + // profile is marked as such. + $exclusive_profile = NULL; + foreach ($profiles as $profile) { + $profile_info = install_profile_info($profile->name); + if (!empty($profile_info['exclusive'])) { + if (empty($exclusive_profile)) { + $exclusive_profile = $profile->name; + } + else { + // We found a second "exclusive" profile. There's no way to choose + // between them, so we ignore the property. + return; + } + } + } + return $exclusive_profile; } /** diff --git a/includes/install.inc b/includes/install.inc index 0372483b6decf66ac7b0d56bddae84dd6d5356b6..c4bcb88b24c121925c52767724b4788dd8761f8c 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1244,6 +1244,12 @@ function drupal_check_module($module) { * - distribution_name: The name of the Drupal distribution that is being * installed, to be shown throughout the installation process. Defaults to * 'Drupal'. + * - exclusive: If the install profile is intended to be the only eligible + * choice in a distribution, setting exclusive = TRUE will auto-select it + * during installation, and the install profile selection screen will be + * skipped. If more than one profile is found where exclusive = TRUE then + * this property will have no effect and the profile selection screen will + * be shown as normal with all available profiles shown. * * Note that this function does an expensive file system scan to get info file * information for dependencies. If you only need information from the info diff --git a/includes/language.inc b/includes/language.inc index d0ea83113371ea7517548774a6d790d696a64eb1..ea63948d980d7b012fd118293f6dcd7d598243cf 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -2,7 +2,9 @@ /** * @file - * Multiple language handling functionality. + * Language Negotiation API. + * + * @see http://drupal.org/node/1497272 */ /** @@ -11,7 +13,96 @@ define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); /** - * Return all the defined language types. + * @defgroup language_negotiation Language Negotiation API functionality + * @{ + * Functions to customize the language types and the negotiation process. + * + * The language negotiation API is based on two major concepts: + * - Language types: types of translatable data (the types of data that a user + * can view or request). + * - Language negotiation providers: functions for determining which language to + * use to present a particular piece of data to the user. + * Both language types and language negotiation providers are customizable. + * + * Drupal defines three built-in language types: + * - Interface language: The page's main language, used to present translated + * user interface elements such as titles, labels, help text, and messages. + * - Content language: The language used to present content that is available + * in more than one language (see + * @link field_language Field Language API @endlink for details). + * - URL language: The language associated with URLs. When generating a URL, + * this value will be used by url() as a default if no explicit preference is + * provided. + * Modules can define additional language types through + * hook_language_types_info(), and alter existing language type definitions + * through hook_language_types_info_alter(). + * + * Language types may be configurable or fixed. The language negotiation + * providers associated with a configurable language type can be explicitly + * set through the user interface. A fixed language type has predetermined + * (module-defined) language negotiation settings and, thus, does not appear in + * the configuration page. Here is a code snippet that makes the content + * language (which by default inherits the interface language's values) + * configurable: + * @code + * function mymodule_language_types_info_alter(&$language_types) { + * unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + * } + * @endcode + * + * Every language type can have a different set of language negotiation + * providers assigned to it. Different language types often share the same + * language negotiation settings, but they can have independent settings if + * needed. If two language types are configured the same way, their language + * switcher configuration will be functionally identical and the same settings + * will act on both language types. + * + * Drupal defines the following built-in language negotiation providers: + * - URL: Determine the language from the URL (path prefix or domain). + * - Session: Determine the language from a request/session parameter. + * - User: Follow the user's language preference. + * - Browser: Determine the language from the browser's language settings. + * - Default language: Use the default site language. + * Language negotiation providers are simple callback functions that implement a + * particular logic to return a language code. For instance, the URL provider + * searches for a valid path prefix or domain name in the current request URL. + * If a language negotiation provider does not return a valid language code, the + * next provider associated to the language type (based on provider weight) is + * invoked. + * + * Modules can define additional language negotiation providers through + * hook_language_negotiation_info(), and alter existing providers through + * hook_language_negotiation_info_alter(). Here is an example snippet that lets + * path prefixes be ignored for administrative paths: + * @code + * function mymodule_language_negotiation_info_alter(&$negotiation_info) { + * // Replace the core function with our own function. + * module_load_include('language', 'inc', 'language.negotiation'); + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['negotiation'] = 'mymodule_from_url'; + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module'; + * } + * + * function mymodule_from_url($languages) { + * // Use the core URL language negotiation provider to get a valid language + * // code. + * module_load_include('language', 'inc', 'language.negotiation'); + * $langcode = language_from_url($languages); + * + * // If we are on an administrative path, override with the default language. + * if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') { + * return language_default()->langcode; + * } + * return $langcode; + * } + * ?> + * @endcode + * + * For more information, see + * @link http://drupal.org/node/1497272 Language Negotiation API @endlink + */ + +/** + * Returns all the defined language types. * * @return * An array of language type names. The name will be used as the global @@ -30,11 +121,11 @@ function language_types_info() { } /** - * Return only the configurable language types. + * Returns only the configurable language types. * * A language type maybe configurable or fixed. A fixed language type is a type - * whose negotiation values are unchangeable and defined while defining the - * language type itself. + * whose language negotiation providers are module-defined and not altered + * through the user interface. * * @param $stored * Optional. By default retrieves values from the 'language_types' variable to @@ -68,7 +159,7 @@ function language_types_configurable($stored = TRUE) { } /** - * Disable the given language types. + * Disables the given language types. * * @param $types * An array of language types. @@ -122,16 +213,17 @@ function language_types_set() { } /** - * Check if a language provider is enabled. + * Checks whether a language negotiation provider is enabled for a language type. * * This has two possible behaviors: * - If $provider_id is given return its ID if enabled, FALSE otherwise. - * - If no ID is passed the first enabled language provider is returned. + * - If no ID is passed the first enabled language negotiation provider is + * returned. * * @param $type - * The language negotiation type. + * The language negotiation provider type. * @param $provider_id - * The language provider ID. + * The language negotiation provider ID. * * @return * The provider ID if it is enabled, FALSE otherwise. @@ -155,14 +247,13 @@ function language_negotiation_get($type, $provider_id = NULL) { } /** - * Check if the given language provider is enabled for any configurable language - * type. + * Checks if the language negotiation provider is enabled for any language type. * * @param $provider_id - * The language provider ID. + * The language negotiation provider ID. * * @return - * TRUE if there is at least one language type for which the give language + * TRUE if there is at least one language type for which the given language * provider is enabled, FALSE otherwise. */ function language_negotiation_get_any($provider_id) { @@ -176,7 +267,7 @@ function language_negotiation_get_any($provider_id) { } /** - * Return the language switch links for the given language. + * Returns the language switch links for the given language. * * @param $type * The language negotiation type. @@ -223,7 +314,7 @@ function language_negotiation_get_switch_links($type, $path) { } /** - * Updates language configuration to remove any language provider that is no longer defined. + * Removes any unused language negotation providers from the configuration. */ function language_negotiation_purge() { // Ensure that we are getting the defined language negotiation information. An @@ -246,12 +337,12 @@ function language_negotiation_purge() { } /** - * Save a list of language providers. + * Saves a list of language negotiation providers. * * @param $type * The language negotiation type. * @param $language_providers - * An array of language provider weights keyed by id. + * An array of language negotiation provider weights keyed by provider ID. * @see language_provider_weight() */ function language_negotiation_set($type, $language_providers) { @@ -277,7 +368,7 @@ function language_negotiation_set($type, $language_providers) { // If the provider does not express any preference about types, make it // available for any configurable type. $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); - // Check if the provider is defined and has the right type. + // Check whether the provider is defined and has the right type. if (isset($types[$type])) { $provider_data = array(); foreach ($provider_fields as $field) { @@ -294,10 +385,10 @@ function language_negotiation_set($type, $language_providers) { } /** - * Return all the defined language providers. + * Returns all the defined language negotiation providers. * * @return - * An array of language providers. + * An array of language negotiation providers. */ function language_negotiation_info() { $language_providers = &drupal_static(__FUNCTION__); @@ -306,7 +397,7 @@ function language_negotiation_info() { // Collect all the module-defined language negotiation providers. $language_providers = module_invoke_all('language_negotiation_info'); - // Add the default language provider. + // Add the default language negotiation provider. $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( 'callbacks' => array('language' => 'language_from_default'), 'weight' => 10, @@ -314,7 +405,7 @@ function language_negotiation_info() { 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)), ); - // Let other modules alter the list of language providers. + // Let other modules alter the list of language negotiation providers. drupal_alter('language_negotiation_info', $language_providers); } @@ -322,16 +413,17 @@ function language_negotiation_info() { } /** - * Helper function used to cache the language providers results. + * Helper function used to cache the language negotiation providers results. * * @param $provider_id - * The language provider ID. + * The language negotiation provider's identifier. * @param $provider - * The language provider to be invoked. If not passed it will be explicitly - * loaded through language_negotiation_info(). + * (optional) An associative array of information about the provider to be + * invoked (see hook_language_negotiation_info() for details). If not passed + * in, it will be loaded through language_negotiation_info(). * * @return - * The language provider's return value. + * A language object representing the language chosen by the provider. */ function language_provider_invoke($provider_id, $provider = NULL) { $results = &drupal_static(__FUNCTION__); @@ -352,25 +444,26 @@ function language_provider_invoke($provider_id, $provider = NULL) { require_once DRUPAL_ROOT . '/' . $provider['file']; } - // If the language provider has no cache preference or this is satisfied - // we can execute the callback. + // If the language negotiation provider has no cache preference or this is + // satisfied we can execute the callback. $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0); $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } - // Since objects are resources we need to return a clone to prevent the - // provider cache to be unintentionally altered. The same providers might be - // used with different language types based on configuration. + // Since objects are resources, we need to return a clone to prevent the + // language negotiation provider cache from being unintentionally altered. The + // same providers might be used with different language types based on + // configuration. return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id]; } /** - * Return the passed language provider weight or a default value. + * Returns the passed language negotiation provider weight or a default value. * * @param $provider - * A language provider data structure. + * A language negotiation provider data structure. * * @return * A numeric weight. @@ -381,16 +474,16 @@ function language_provider_weight($provider) { } /** - * Choose a language for the given type based on language negotiation settings. + * Chooses a language based on language negotiation provider settings. * * @param $type - * The language type. + * The language type key to find the language for. * * @return * The negotiated language object. */ function language_initialize($type) { - // Execute the language providers in the order they were set up and return the + // Execute the language negotiation providers in the order they were set up and return the // first valid language found. $negotiation = variable_get("language_negotiation_$type", array()); @@ -409,7 +502,7 @@ function language_initialize($type) { } /** - * Default language provider. + * Returns the default language negotiation provider. * * @return * The default language code. @@ -421,8 +514,8 @@ function language_from_default() { /** * Splits the given path into prefix and actual path. * - * Parse the given path and return the language object identified by the - * prefix and the actual path. + * Parse the given path and return the language object identified by the prefix + * and the actual path. * * @param $path * The path to split. @@ -482,3 +575,7 @@ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { return $fallback_candidates; } + +/** + * @} End of "language_negotiation" + */ diff --git a/includes/mail.inc b/includes/mail.inc index 8479d8e9bfb22298a6c164a3ab6f4c3d933bd8c6..bbb55357d4b896d946e6488665bc1b32e536e908 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -93,7 +93,9 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * will be {$module}_{$key}. * @param $to * The e-mail address or addresses where the message will be sent to. The - * formatting of this string must comply with RFC 2822. Some examples are: + * formatting of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink + * Some examples are: * - user@example.com * - user@example.com, anotheruser@example.com * - User <user@example.com> @@ -212,9 +214,9 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * 'mail_system', which is a keyed array. The default implementation * is the class whose name is the value of 'default-system' key. A more specific * match first to key and then to module will be used in preference to the - * default. To specificy a different class for all mail sent by one module, set + * default. To specify a different class for all mail sent by one module, set * the class name as the value for the key corresponding to the module name. To - * specificy a class for a particular message sent by one module, set the class + * specify a class for a particular message sent by one module, set the class * name as the value for the array key that is the message id, which is * "${module}_${key}". * @@ -307,19 +309,21 @@ interface MailSystemInterface { * - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy', * 'user_password_reset'. * - to: The mail address or addresses where the message will be sent to. - * The formatting of this string must comply with RFC 2822. Some examples: + * The formatting of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink + * Some examples are: * - user@example.com * - user@example.com, anotheruser@example.com * - User <user@example.com> * - User <user@example.com>, Another User <anotheruser@example.com> - * - subject: Subject of the e-mail to be sent. This must not contain any - * newline characters, or the mail may not be sent properly. - * - body: Message to be sent. Accepts both CRLF and LF line-endings. - * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for - * smart plain text wrapping. - * - headers: Associative array containing all additional mail headers not - * defined by one of the other parameters. PHP's mail() looks for Cc - * and Bcc headers and sends the mail to addresses in these headers too. + * - subject: Subject of the e-mail to be sent. This must not contain any + * newline characters, or the mail may not be sent properly. + * - body: Message to be sent. Accepts both CRLF and LF line-endings. + * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for + * smart plain text wrapping. + * - headers: Associative array containing all additional mail headers not + * defined by one of the other parameters. PHP's mail() looks for Cc and + * Bcc headers and sends the mail to addresses in these headers too. * * @return * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. diff --git a/includes/menu.inc b/includes/menu.inc index 0cb9d23b89c95c8e313560385e8d2b4859eb1eea..2be090327199b7f6efae4ea024b2ba9257533a65 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -618,6 +618,7 @@ function _menu_load_objects(&$item, &$map) { * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. */ function _menu_check_access(&$item, $map) { + $item['access'] = FALSE; // Determine access callback, which will decide whether or not the current // user has access to this path. $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); diff --git a/includes/module.inc b/includes/module.inc index d932f07b96e1d73ba034eea4449be78e43336c66..341cd79111a5a9f0054f47e8bd15d7f09cdb821e 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -898,9 +898,10 @@ function drupal_required_modules() { * hook_TYPE_alter() implementations in modules. It ensures a consistent * interface for all altering operations. * - * A maximum of 2 alterable arguments is supported. In case more arguments need - * to be passed and alterable, modules provide additional variables assigned by - * reference in the last $context argument: + * A maximum of 2 alterable arguments is supported (a third is supported for + * legacy reasons, but should not be used in new code). In case more arguments + * need to be passed and alterable, modules provide additional variables + * assigned by reference in the last $context argument: * @code * $context = array( * 'alterable' => &$alterable, @@ -939,8 +940,14 @@ function drupal_required_modules() { * (optional) An additional variable that is passed by reference. If more * context needs to be provided to implementations, then this should be an * associative array as described above. + * @param $context3 + * (optional) An additional variable that is passed by reference. This + * parameter is deprecated and will not exist in Drupal 8; consequently, it + * should not be used for new Drupal 7 code either. It is here only for + * backwards compatibility with older code that passed additional arguments + * to drupal_alter(). */ -function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { +function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { @@ -1053,6 +1060,6 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } foreach ($functions[$cid] as $function) { - $function($data, $context1, $context2); + $function($data, $context1, $context2, $context3); } } diff --git a/includes/password.inc b/includes/password.inc index d4f5f738aca25e596336b11122eb47b861662d5b..3d5a400d26037cd6c233b9775c380edcb608804d 100644 --- a/includes/password.inc +++ b/includes/password.inc @@ -43,7 +43,7 @@ function _password_itoa64() { } /** - * Encode bytes into printable base 64 using the *nix standard from crypt(). + * Encodes bytes into printable base 64 using the *nix standard from crypt(). * * @param $input * The string containing bytes to encode. diff --git a/includes/path.inc b/includes/path.inc index 411a7a71c38bd1e91d0d0e7633f4b6e8bc3630a0..234430ea1c46e94a3a7839d681384317a43261f7 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -386,7 +386,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) { } /** - * Fetch a specific URL alias from the database. + * Fetches a specific URL alias from the database. * * @param $conditions * A string representing the source, a number representing the pid, or an @@ -475,11 +475,11 @@ function path_delete($criteria) { } /** - * Determine whether a path is in the administrative section of the site. + * Determines whether a path is in the administrative section of the site. * - * By default, paths are considered to be non-administrative. If a path does not - * match any of the patterns in path_get_admin_paths(), or if it matches both - * administrative and non-administrative patterns, it is considered + * By default, paths are considered to be non-administrative. If a path does + * not match any of the patterns in path_get_admin_paths(), or if it matches + * both administrative and non-administrative patterns, it is considered * non-administrative. * * @param $path @@ -503,7 +503,7 @@ function path_is_admin($path) { } /** - * Get a list of administrative and non-administrative paths. + * Gets a list of administrative and non-administrative paths. * * @return array * An associative array containing the following keys: diff --git a/includes/session.inc b/includes/session.inc index 0a95b8548c11744860316e95da4edc1ea02b2d30..4c9f8b9b5d60fdca01443f2a3ca4cd88bd9e7822 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -278,7 +278,7 @@ function drupal_session_initialize() { } /** - * Forcefully starts a session, preserving already set session data. + * Starts a session forcefully, preserving already set session data. * * @ingroup php_wrappers */ diff --git a/includes/tablesort.inc b/includes/tablesort.inc index 121a1b909346fda10c7959ea4344b267d13daa09..e589526c652d2dcfc6294bb434a84148998cdfbc 100644 --- a/includes/tablesort.inc +++ b/includes/tablesort.inc @@ -55,7 +55,7 @@ class TableSort extends SelectQueryExtender { } /** - * Initialize the table sort context. + * Initializes the table sort context. */ protected function init() { $ts = $this->order(); @@ -115,7 +115,7 @@ function tablesort_init($header) { } /** - * Format a column header. + * Formats a column header. * * If the cell in question is the column header for the current sort criterion, * it gets special formatting. All possible sort criteria become links. @@ -126,6 +126,7 @@ function tablesort_init($header) { * An array of column headers in the format described in theme_table(). * @param $ts * The current table sort context as returned from tablesort_init(). + * * @return * A properly formatted cell, ready for _theme_table_cell(). */ @@ -151,7 +152,7 @@ function tablesort_header($cell, $header, $ts) { } /** - * Format a table cell. + * Formats a table cell. * * Adds a class attribute to all cells in the currently active column. * @@ -163,6 +164,7 @@ function tablesort_header($cell, $header, $ts) { * The current table sort context as returned from tablesort_init(). * @param $i * The index of the cell's table column. + * * @return * A properly formatted cell, ready for _theme_table_cell(). */ @@ -179,7 +181,7 @@ function tablesort_cell($cell, $header, $ts, $i) { } /** - * Compose a URL query parameter array for table sorting links. + * Composes a URL query parameter array for table sorting links. * * @return * A URL query parameter array that consists of all components of the current @@ -190,10 +192,11 @@ function tablesort_get_query_parameters() { } /** - * Determine the current sort criterion. + * Determines the current sort criterion. * * @param $headers * An array of column headers in the format described in theme_table(). + * * @return * An associative array describing the criterion, containing the keys: * - "name": The localized title of the table column. @@ -226,10 +229,11 @@ function tablesort_get_order($headers) { } /** - * Determine the current sort direction. + * Determines the current sort direction. * * @param $headers * An array of column headers in the format described in theme_table(). + * * @return * The current sort direction ("asc" or "desc"). */ diff --git a/includes/theme.inc b/includes/theme.inc index 777922f05f7c0744ab264952dc186680eea981d0..5920624dca80a8e0027795106f1e0154d680f44d 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -65,7 +65,7 @@ function _drupal_theme_access($theme) { } /** - * Initialize the theme system by loading the theme. + * Initializes the theme system by loading the theme. */ function drupal_theme_initialize() { global $theme, $user, $theme_key; @@ -113,8 +113,9 @@ function drupal_theme_initialize() { } /** - * Initialize the theme system given already loaded information. This - * function is useful to initialize a theme when no database is present. + * Initializes the theme system given already loaded information. + * + * This function is useful to initialize a theme when no database is present. * * @param $theme * An object with the following information: @@ -235,7 +236,7 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb } /** - * Get the theme registry. + * Gets the theme registry. * * @param $complete * Optional boolean to indicate whether to return the complete theme registry @@ -280,7 +281,7 @@ function theme_get_registry($complete = TRUE) { } /** - * Set the callback that will be used by theme_get_registry() to fetch the registry. + * Sets the callback that will be used by theme_get_registry(). * * @param $callback * The name of the callback function. @@ -296,7 +297,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) } /** - * Get the theme_registry cache; if it doesn't exist, build it. + * Gets the theme_registry cache; if it doesn't exist, builds it. * * @param $theme * The loaded $theme object as returned by list_themes(). @@ -336,16 +337,17 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, } /** - * Write the theme_registry cache into the database. + * Writes the theme_registry cache into the database. */ function _theme_save_registry($theme, $registry) { cache_set("theme_registry:$theme->name", $registry); } /** - * Force the system to rebuild the theme registry; this should be called - * when modules are added to the system, or when a dynamic system needs - * to add more theme hooks. + * Forces the system to rebuild the theme registry. + * + * This function should be called when modules are added to the system, or when + * a dynamic system needs to add more theme hooks. */ function drupal_theme_rebuild() { drupal_static_reset('theme_get_registry'); @@ -635,7 +637,8 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { $cache = $result + $cache; } - // Let themes have variable processors even if they didn't register a template. + // Let themes have variable processors even if they didn't register a + // template. if ($type == 'theme' || $type == 'base_theme') { foreach ($cache as $hook => $info) { // Check only if not registered by the theme or engine. @@ -662,7 +665,7 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { } /** - * Build the theme registry cache. + * Builds the theme registry cache. * * @param $theme * The loaded $theme object as returned by list_themes(). @@ -724,7 +727,7 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { } /** - * Return a list of all currently available themes. + * Returns a list of all currently available themes. * * Retrieved from the database, if available and the site is not in maintenance * mode; otherwise compiled freshly from the filesystem. @@ -766,7 +769,7 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { * their base theme), direct sub-themes of sub-themes, etc. The keys are * the themes' machine names, and the values are the themes' human-readable * names. This element is not set if there are no themes on the system that - * declare this theme as their base theme. + * declare this theme as their base theme. */ function list_themes($refresh = FALSE) { $list = &drupal_static(__FUNCTION__, array()); @@ -900,15 +903,15 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { * executed (if they exist), in the following order (note that in the following * list, HOOK indicates the theme hook name, MODULE indicates a module name, * THEME indicates a theme name, and ENGINE indicates a theme engine name): - * - template_preprocess(&$variables, $hook): Creates a default set of variables - * for all theme hooks with template implementations. + * - template_preprocess(&$variables, $hook): Creates a default set of + * variables for all theme hooks with template implementations. * - template_preprocess_HOOK(&$variables): Should be implemented by the module * that registers the theme hook, to set up default variables. * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all * implementing modules. * - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on - * all implementing modules, so that modules that didn't define the theme hook - * can alter the variables. + * all implementing modules, so that modules that didn't define the theme + * hook can alter the variables. * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to * set necessary variables for all theme hooks with template implementations. * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set @@ -963,10 +966,10 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { * @param $hook * The name of the theme hook to call. If the name contains a * double-underscore ('__') and there isn't an implementation for the full - * name, the part before the '__' is checked. This allows a fallback to a more - * generic implementation. For example, if theme('links__node', ...) is - * called, but there is no implementation of that theme hook, then the 'links' - * implementation is used. This process is iterative, so if + * name, the part before the '__' is checked. This allows a fallback to a + * more generic implementation. For example, if theme('links__node', ...) is + * called, but there is no implementation of that theme hook, then the + * 'links' implementation is used. This process is iterative, so if * theme('links__contextual__node', ...) is called, theme() checks for the * following implementations, and uses the first one that exists: * - links__contextual__node @@ -1030,7 +1033,7 @@ function theme($hook, $variables = array()) { // Only log a message when not trying theme suggestions ($hook being an // array). if (!isset($candidate)) { - watchdog('theme', 'Theme key "@key" not found.', array('@key' => $hook), WATCHDOG_WARNING); + watchdog('theme', 'Theme hook %hook not found.', array('%hook' => $hook), WATCHDOG_WARNING); } return ''; } @@ -1042,7 +1045,8 @@ function theme($hook, $variables = array()) { // point path_to_theme() to the currently used theme path: $theme_path = $info['theme path']; - // Include a file if the theme function or variable processor is held elsewhere. + // Include a file if the theme function or variable processor is held + // elsewhere. if (!empty($info['includes'])) { foreach ($info['includes'] as $include_file) { include_once DRUPAL_ROOT . '/' . $include_file; @@ -1191,14 +1195,14 @@ function theme($hook, $variables = array()) { } /** - * Return the path to the current themed element. - * - * It can point to the active theme or the module handling a themed implementation. - * For example, when invoked within the scope of a theming call it will depend - * on where the theming function is handled. If implemented from a module, it - * will point to the module. If implemented from the active theme, it will point - * to the active theme. When called outside the scope of a theming call, it will - * always point to the active theme. + * Returns the path to the current themed element. + * + * It can point to the active theme or the module handling a themed + * implementation. For example, when invoked within the scope of a theming call + * it will depend on where the theming function is handled. If implemented from + * a module, it will point to the module. If implemented from the active theme, + * it will point to the active theme. When called outside the scope of a + * theming call, it will always point to the active theme. */ function path_to_theme() { global $theme_path; @@ -1211,7 +1215,7 @@ function path_to_theme() { } /** - * Allow themes and/or theme engines to easily discover overridden theme functions. + * Allows themes and/or theme engines to discover overridden theme functions. * * @param $cache * The existing cache of theme hooks to test against. @@ -1268,7 +1272,7 @@ function drupal_find_theme_functions($cache, $prefixes) { } /** - * Allow themes and/or theme engines to easily discover overridden templates. + * Allows themes and/or theme engines to easily discover overridden templates. * * @param $cache * The existing cache of theme hooks to test against. @@ -1345,7 +1349,8 @@ function drupal_find_theme_templates($cache, $extension, $path) { if ($matches) { foreach ($matches as $match) { $file = substr($match, 0, strpos($match, '.')); - // Put the underscores back in for the hook name and register this pattern. + // Put the underscores back in for the hook name and register this + // pattern. $arg_name = isset($info['variables']) ? 'variables' : 'render element'; $implementations[strtr($file, '-', '_')] = array( 'template' => $file, @@ -1361,7 +1366,7 @@ function drupal_find_theme_templates($cache, $extension, $path) { } /** - * Retrieve a setting for the current theme or for a given theme. + * Retrieves a setting for the current theme or for a given theme. * * The final setting is obtained from the last value found in the following * sources: @@ -1479,7 +1484,7 @@ function theme_get_setting($setting_name, $theme = NULL) { } /** - * Render a system default template, which is essentially a PHP template. + * Renders a system default template, which is essentially a PHP template. * * @param $template_file * The filename of the template to render. @@ -1490,14 +1495,21 @@ function theme_get_setting($setting_name, $theme = NULL) { * The output generated by the template. */ function theme_render_template($template_file, $variables) { - extract($variables, EXTR_SKIP); // Extract the variables to a local namespace - ob_start(); // Start output buffering - include DRUPAL_ROOT . '/' . $template_file; // Include the template file - return ob_get_clean(); // End buffering and return its contents + // Extract the variables to a local namespace + extract($variables, EXTR_SKIP); + + // Start output buffering + ob_start(); + + // Include the template file + include DRUPAL_ROOT . '/' . $template_file; + + // End buffering and return its contents + return ob_get_clean(); } /** - * Enable a given list of themes. + * Enables a given list of themes. * * @param $theme_list * An array of theme names. @@ -1522,7 +1534,7 @@ function theme_enable($theme_list) { } /** - * Disable a given list of themes. + * Disables a given list of themes. * * @param $theme_list * An array of theme names. @@ -1608,13 +1620,13 @@ function theme_status_messages($variables) { * theme('link') for rendering the anchor tag. * * To optimize performance for sites that don't need custom theming of links, - * the l() function includes an inline copy of this function, and uses that copy - * if none of the enabled modules or the active theme implement any preprocess - * or process functions or override this theme implementation. + * the l() function includes an inline copy of this function, and uses that + * copy if none of the enabled modules or the active theme implement any + * preprocess or process functions or override this theme implementation. * * @param $variables - * An associative array containing the keys 'text', 'path', and 'options'. See - * the l() function for information about these variables. + * An associative array containing the keys 'text', 'path', and 'options'. + * See the l() function for information about these variables. * * @see l() */ @@ -1635,15 +1647,16 @@ function theme_link($variables) { * item in the links list. * - html: (optional) Whether or not 'title' is HTML. If set, the title * will not be passed through check_plain(). - * - attributes: (optional) Attributes for the anchor, or for the <span> tag - * used in its place if no 'href' is supplied. If element 'class' is + * - attributes: (optional) Attributes for the anchor, or for the <span> + * tag used in its place if no 'href' is supplied. If element 'class' is * included, it must be an array of one or more class names. - * If the 'href' element is supplied, the entire link array is passed to l() - * as its $options parameter. + * If the 'href' element is supplied, the entire link array is passed to + * l() as its $options parameter. * - attributes: A keyed array of attributes for the UL containing the * list of links. - * - heading: (optional) A heading to precede the links. May be an associative - * array or a string. If it's an array, it can have the following elements: + * - heading: (optional) A heading to precede the links. May be an + * associative array or a string. If it's an array, it can have the + * following elements: * - text: The heading text. * - level: The heading level (e.g. 'h2', 'h3'). * - class: (optional) An array of the CSS classes for the heading. @@ -1747,8 +1760,8 @@ function theme_links($variables) { * attribute to be omitted in some cases. Therefore, this variable defaults * to an empty string, but can be set to NULL for the attribute to be * omitted. Usually, neither omission nor an empty string satisfies - * accessibility requirements, so it is strongly encouraged for code calling - * theme('image') to pass a meaningful value for this variable. + * accessibility requirements, so it is strongly encouraged for code + * calling theme('image') to pass a meaningful value for this variable. * - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 * - http://www.w3.org/TR/xhtml1/dtds.html * - http://dev.w3.org/html5/spec/Overview.html#alt @@ -2005,7 +2018,8 @@ function theme_table($variables) { * * @param $variables * An associative array containing: - * - style: Set to either 'asc' or 'desc', this determines which icon to show. + * - style: Set to either 'asc' or 'desc', this determines which icon to + * show. */ function theme_tablesort_indicator($variables) { if ($variables['style'] == "asc") { @@ -2148,7 +2162,8 @@ function theme_feed_icon($variables) { * - script: To load JavaScript. * - #attributes: (optional) An array of HTML attributes to apply to the * tag. - * - #value: (optional) A string containing tag content, such as inline CSS. + * - #value: (optional) A string containing tag content, such as inline + * CSS. * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA * wrapper prefix. * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA @@ -2316,8 +2331,9 @@ function template_preprocess(&$variables, $hook) { global $user; static $count = array(); - // Track run count for each hook to provide zebra striping. - // See "template_preprocess_block()" which provides the same feature specific to blocks. + // Track run count for each hook to provide zebra striping. See + // "template_preprocess_block()" which provides the same feature specific to + // blocks. $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; $variables['id'] = $count[$hook]++; @@ -2677,13 +2693,13 @@ function theme_get_suggestions($args, $base, $delimiter = '__') { } /** - * The variables array generated here is a mirror of template_preprocess_page(). - * This preprocessor will run its course when theme_maintenance_page() is - * invoked. + * Process variables for maintenance-page.tpl.php. * - * An alternate template file of "maintenance-page--offline.tpl.php" can be - * used when the database is offline to hide errors and completely replace the - * content. + * The variables array generated here is a mirror of + * template_preprocess_page(). This preprocessor will run its course when + * theme_maintenance_page() is invoked. An alternate template file of + * maintenance-page--offline.tpl.php can be used when the database is offline to + * hide errors and completely replace the content. * * The $variables array contains the following arguments: * - $content @@ -2777,10 +2793,13 @@ function template_preprocess_maintenance_page(&$variables) { } /** + * Theme process function for theme_maintenance_field(). + * * The variables array generated here is a mirror of template_process_html(). * This processor will run its course when theme_maintenance_page() is invoked. * * @see maintenance-page.tpl.php + * @see template_process_html() */ function template_process_maintenance_page(&$variables) { $variables['head'] = drupal_get_html_head(); @@ -2792,7 +2811,7 @@ function template_process_maintenance_page(&$variables) { /** * Preprocess variables for region.tpl.php * - * Prepare the values passed to the theme_region function to be passed into a + * Prepares the values passed to the theme_region function to be passed into a * pluggable template engine. Uses the region name to generate a template file * suggestions. If none are found, the default region.tpl.php is used. * diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index 218a8adaa098eec6bb1de95039fcde9e57a6ec33..6baf219b03e0edda3d7e61066639496eb6f830ae 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -10,9 +10,9 @@ * * Used for site installs, updates and when the site is in maintenance mode. * It also applies when the database is unavailable or bootstrap was not - * complete. Seven is always used for the initial install and update operations. - * In other cases, Bartik is used, but this can be overridden by setting a - * "maintenance_theme" key in the $conf variable in settings.php. + * complete. Seven is always used for the initial install and update + * operations. In other cases, Bartik is used, but this can be overridden by + * setting a "maintenance_theme" key in the $conf variable in settings.php. */ function _drupal_maintenance_theme() { global $theme, $theme_key, $conf; @@ -85,7 +85,7 @@ function _drupal_maintenance_theme() { } /** - * This builds the registry when the site needs to bypass any database calls. + * Builds the registry when the site needs to bypass any database calls. */ function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { return _theme_build_registry($theme, $base_theme, $theme_engine); @@ -160,7 +160,7 @@ function theme_update_page($variables) { } /** - * Returns HTML for a report of the results from an operation run via authorize.php. + * Returns HTML for a results report of an operation run by authorize.php. * * @param $variables * An associative array containing: diff --git a/includes/token.inc b/includes/token.inc index 0b05c68f473f860eeba78d719762bda52ecbc8f6..5e9ece85d82d2e797aedad37e69a936970ab2574 100644 --- a/includes/token.inc +++ b/includes/token.inc @@ -113,13 +113,13 @@ function token_replace($text, array $data = array(), array $options = array()) { */ function token_scan($text) { // Matches tokens with the following pattern: [$type:$name] - // $type and $name may not contain [ ] or whitespace characters. - // $type may not contain : characters, but $name may. + // $type and $name may not contain [ ] characters. + // $type may not contain : or whitespace characters, but $name may. preg_match_all('/ \[ # [ - pattern start ([^\s\[\]:]*) # match $type not containing whitespace : [ or ] : # : - separator - ([^\s\[\]]*) # match $name not containing whitespace [ or ] + ([^\[\]]*) # match $name not containing [ or ] \] # ] - pattern end /x', $text, $matches); @@ -190,10 +190,10 @@ function token_generate($type, array $tokens, array $data = array(), array $opti } /** - * Given a list of tokens, returns those that begin with a specific prefix. + * Returns a list of tokens that begin with a specific prefix. * - * Used to extract a group of 'chained' tokens (such as [node:author:name]) from - * the full list of tokens found in text. For example: + * Used to extract a group of 'chained' tokens (such as [node:author:name]) + * from the full list of tokens found in text. For example: * @code * $data = array( * 'author:name' => '[node:author:name]', @@ -230,8 +230,10 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { /** * Returns metadata describing supported tokens. * - * The metadata array contains token type, name, and description data as well as - * an optional pointer indicating that the token chains to another set of tokens. + * The metadata array contains token type, name, and description data as well + * as an optional pointer indicating that the token chains to another set of + * tokens. + * * For example: * @code * $data['types']['node'] = array( diff --git a/includes/unicode.inc b/includes/unicode.inc index 81a0a4dfe2dcdc1d6af0eeb5ba65b37af96c64ea..fd497cca70b364073483ded5a9357d62920df8ef 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -1,5 +1,10 @@ <?php +/** +* @file +* Provides Unicode-related conversions and operations. +*/ + /** * Indicates an error during check for PHP unicode support. */ @@ -19,8 +24,6 @@ define('UNICODE_MULTIBYTE', 1); /** * Matches Unicode characters that are word boundaries. * - * @see http://unicode.org/glossary - * * Characters with the following General_category (gc) property values are used * as word boundaries. While this does not fully conform to the Word Boundaries * algorithm described in http://unicode.org/reports/tr29, as PCRE does not @@ -39,6 +42,8 @@ define('UNICODE_MULTIBYTE', 1); * Note that the PCRE property matcher is not used because we wanted to be * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any * bugs in PCRE property tables). + * + * @see http://unicode.org/glossary */ define('PREG_CLASS_UNICODE_WORD_BOUNDARY', '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' . @@ -125,7 +130,7 @@ function _unicode_check() { } /** - * Return Unicode library status and errors. + * Returns Unicode library status and errors. */ function unicode_requirements() { // Ensure translations don't break during installation. @@ -157,14 +162,14 @@ function unicode_requirements() { } /** - * Prepare a new XML parser. + * Prepares a new XML parser. * - * This is a wrapper around xml_parser_create() which extracts the encoding from - * the XML data first and sets the output encoding to UTF-8. This function should - * be used instead of xml_parser_create(), because PHP 4's XML parser doesn't - * check the input encoding itself. "Starting from PHP 5, the input encoding is - * automatically detected, so that the encoding parameter specifies only the - * output encoding." + * This is a wrapper around xml_parser_create() which extracts the encoding + * from the XML data first and sets the output encoding to UTF-8. This function + * should be used instead of xml_parser_create(), because PHP 4's XML parser + * doesn't check the input encoding itself. "Starting from PHP 5, the input + * encoding is automatically detected, so that the encoding parameter specifies + * only the output encoding." * * This is also where unsupported encodings will be converted. Callers should * take this into account: $data might have been changed after the call. @@ -213,7 +218,7 @@ function drupal_xml_parser_create(&$data) { } /** - * Convert data to UTF-8 + * Converts data to UTF-8. * * Requires the iconv, GNU recode or mbstring PHP extension. * @@ -244,15 +249,15 @@ function drupal_convert_to_utf8($data, $encoding) { } /** - * Truncate a UTF-8-encoded string safely to a number of bytes. + * Truncates a UTF-8-encoded string safely to a number of bytes. * * If the end position is in the middle of a UTF-8 sequence, it scans backwards * until the beginning of the byte sequence. * * Use this function whenever you want to chop off a string at an unsure * location. On the other hand, if you're sure that you're splitting on a - * character boundary (e.g. after using strpos() or similar), you can safely use - * substr() instead. + * character boundary (e.g. after using strpos() or similar), you can safely + * use substr() instead. * * @param $string * The string to truncate. @@ -306,7 +311,7 @@ function drupal_truncate_bytes($string, $len) { * boundaries, giving you "See myverylongurl..." (assuming you had set * $add_ellipses to TRUE). * - * @return + * @return string * The truncated string. */ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { @@ -356,8 +361,7 @@ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = } /** - * Encodes MIME/HTTP header values that contain non-ASCII, UTF-8 encoded - * characters. + * Encodes MIME/HTTP header values that contain incorrectly encoded characters. * * For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=". * @@ -369,6 +373,14 @@ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = * each chunk starts and ends on a character boundary. * - Using \n as the chunk separator may cause problems on some systems and may * have to be changed to \r\n or \r. + * + * @param $string + * The header to encode. + * + * @return string + * The mime-encoded header. + * + * @see mime_header_decode() */ function mime_header_encode($string) { if (preg_match('/[^\x20-\x7E]/', $string)) { @@ -388,7 +400,15 @@ function mime_header_encode($string) { } /** - * Complement to mime_header_encode + * Decodes MIME/HTTP encoded header values. + * + * @param $header + * The header to decode. + * + * @return string + * The mime-decoded header. + * + * @see mime_header_encode() */ function mime_header_decode($header) { // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace) @@ -398,7 +418,17 @@ function mime_header_decode($header) { } /** - * Helper function to mime_header_decode + * Decodes encoded header data passed from mime_header_decode(). + * + * Callback for preg_replace_callback() within mime_header_decode(). + * + * @param $matches + * The array of matches from preg_replace_callback(). + * + * @return string + * The mime-decoded string. + * + * @see mime_header_decode() */ function _mime_header_decode($matches) { // Regexp groups: @@ -415,9 +445,9 @@ function _mime_header_decode($matches) { /** * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. * - * Double-escaped entities will only be decoded once ("&lt;" becomes "<", - * not "<"). Be careful when using this function, as decode_entities can revert - * previous sanitization efforts (<script> will become <script>). + * Double-escaped entities will only be decoded once ("&lt;" becomes "<" + * , not "<"). Be careful when using this function, as decode_entities can + * revert previous sanitization efforts (<script> will become <script>). * * @param $text * The text to decode entities in. @@ -430,8 +460,15 @@ function decode_entities($text) { } /** - * Count the amount of characters in a UTF-8 string. This is less than or - * equal to the byte count. + * Counts the number of characters in a UTF-8 string. + * + * This is less than or equal to the byte count. + * + * @param $text + * The string to run the operation on. + * + * @return integer + * The length of the string. * * @ingroup php_wrappers */ @@ -449,6 +486,12 @@ function drupal_strlen($text) { /** * Uppercase a UTF-8 string. * + * @param $text + * The string to run the operation on. + * + * @return string + * The string in uppercase. + * * @ingroup php_wrappers */ function drupal_strtoupper($text) { @@ -468,6 +511,12 @@ function drupal_strtoupper($text) { /** * Lowercase a UTF-8 string. * + * @param $text + * The string to run the operation on. + * + * @return string + * The string in lowercase. + * * @ingroup php_wrappers */ function drupal_strtolower($text) { @@ -485,15 +534,28 @@ function drupal_strtolower($text) { } /** - * Helper function for case conversion of Latin-1. - * Used for flipping U+C0-U+DE to U+E0-U+FD and back. + * Flips U+C0-U+DE to U+E0-U+FD and back. + * + * @param $matches + * An array of matches. + * + * @return array + * The Latin-1 version of the array of matches. + * + * @see drupal_strtolower() */ function _unicode_caseflip($matches) { return $matches[0][0] . chr(ord($matches[0][1]) ^ 32); } /** - * Capitalize the first letter of a UTF-8 string. + * Capitalizes the first letter of a UTF-8 string. + * + * @param $text + * The string to convert. + * + * @return + * The string with the first letter as uppercase. * * @ingroup php_wrappers */ @@ -503,12 +565,21 @@ function drupal_ucfirst($text) { } /** - * Cut off a piece of a string based on character indices and counts. Follows - * the same behavior as PHP's own substr() function. + * Cuts off a piece of a string based on character indices and counts. * - * Note that for cutting off a string at a known character/substring - * location, the usage of PHP's normal strpos/substr is safe and - * much faster. + * Follows the same behavior as PHP's own substr() function. Note that for + * cutting off a string at a known character/substring location, the usage of + * PHP's normal strpos/substr is safe and much faster. + * + * @param $text + * The input string. + * @param $start + * The position at which to start reading. + * @param $length + * The number of characters to read. + * + * @return + * The shortened string. * * @ingroup php_wrappers */ diff --git a/includes/update.inc b/includes/update.inc index 559a0a7118a26fcfcc813d720980d64e755952ca..fd50cda428a3ef35ecab93ffe2896e547cec3131 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -38,7 +38,7 @@ function update_fix_compatibility() { } /** - * Helper function to test compatibility of a module or theme. + * Tests the compatibility of a module or theme. */ function update_check_incompatibility($name, $type = 'module') { static $themes, $modules; @@ -910,7 +910,7 @@ function update_get_d6_session_name() { } /** - * Perform one update and store the results for display on finished page. + * Performs one update and stores the results for display on the results page. * * If an update function completes successfully, it should return a message * as a string indicating success, for example: @@ -1010,7 +1010,7 @@ function update_do_one($module, $number, $dependency_map, &$context) { class DrupalUpdateException extends Exception { } /** - * Start the database update batch process. + * Starts the database update batch process. * * @param $start * An array whose keys contain the names of modules to be updated during the @@ -1080,7 +1080,7 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $ } /** - * Finish the update process and store results for eventual display. + * Finishes the update process and stores the results for eventual display. * * After the updates run, all caches are flushed. The update results are * stored into the session (for example, to be displayed on the update results @@ -1117,7 +1117,7 @@ function update_finished($success, $results, $operations) { } /** - * Return a list of all the pending database updates. + * Returns a list of all the pending database updates. * * @return * An associative array keyed by module name which contains all information @@ -1411,7 +1411,7 @@ function update_already_performed($module, $number) { } /** - * Invoke hook_update_dependencies() in all installed modules. + * Invokes hook_update_dependencies() in all installed modules. * * This function is similar to module_invoke_all(), with the main difference * that it does not require that a module be enabled to invoke its hook, only diff --git a/includes/utility.inc b/includes/utility.inc index 5019852c7e768dd67dd7afd80216e0ca0193ef6b..f651fd63126609ce89b7bf724898063d6332489e 100644 --- a/includes/utility.inc +++ b/includes/utility.inc @@ -12,6 +12,7 @@ * The variable to export. * @param $prefix * A prefix that will be added at the beginning of every lines of the output. + * * @return * The variable exported in a way compatible to Drupal's coding standards. */ diff --git a/install.php b/install.php index 881ca5a82beab9796c96f3cc4ba9171958a761eb..685d3b4ee3030809ff568b72a499b4170151f601 100644 --- a/install.php +++ b/install.php @@ -6,12 +6,12 @@ */ /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); /** - * Global flag to indicate that site is in installation mode. + * Global flag to indicate the site is in installation mode. */ define('MAINTENANCE_MODE', 'install'); diff --git a/misc/ajax.js b/misc/ajax.js index 900ca1d22c5c4296c4f2fffacce650b5679e5faf..90c3bb8bbef3e306cfa2b176920a9eff5adf18b0 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -396,7 +396,7 @@ Drupal.ajax.prototype.success = function (response, status) { Drupal.freezeHeight(); for (var i in response) { - if (response[i]['command'] && this.commands[response[i]['command']]) { + if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) { this.commands[response[i]['command']](this, response[i], status); } } diff --git a/misc/machine-name.js b/misc/machine-name.js index ced8c4bee5d4a834a9bbe8f704b4e114304586fb..4678e0b534d8fab9791f25b2c65a0c7a709a5c39 100644 --- a/misc/machine-name.js +++ b/misc/machine-name.js @@ -80,7 +80,7 @@ Drupal.behaviors.machineName = { // changes, but only if there is no machine name yet; i.e., only upon // initial creation, not when editing. if ($target.val() == '') { - $source.bind('keyup.machineName change.machineName', function () { + $source.bind('keyup.machineName change.machineName input.machineName', function () { machine = self.transliterate($(this).val(), options); // Set the machine name to the transliterated value. if (machine != '') { diff --git a/misc/tableheader.js b/misc/tableheader.js index a9f98a680e633ebda076b3001046fa58ab12a6b5..47201b2691a736d1b7fa2c6f72b9ffc5ea9e5f61 100644 --- a/misc/tableheader.js +++ b/misc/tableheader.js @@ -126,7 +126,7 @@ Drupal.tableHeader.prototype.eventhandlerRecalculateStickyHeader = function (eve $stickyCell.css('display', 'none'); } } - this.stickyTable.css('width', this.originalTable.css('width')); + this.stickyTable.css('width', this.originalTable.outerWidth()); } }; diff --git a/modules/aggregator/aggregator-rtl.css b/modules/aggregator/aggregator-rtl.css index ea59ca3a19f10c0103741d0d7f02ca6d1406fc28..057d01565853080178de063df2e6dc65966dd5bd 100644 --- a/modules/aggregator/aggregator-rtl.css +++ b/modules/aggregator/aggregator-rtl.css @@ -1,3 +1,6 @@ +/** + * Right-to-Left styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-icon { float: left; diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index 8b817c0fafc4fdce57952e609c678f4b469fd121..443facb129950a14dc9299b1269e13c08e7bacea 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -2,11 +2,11 @@ /** * @file - * Admin page callbacks for the aggregator module. + * Administration page callbacks for the Aggregator module. */ /** - * Menu callback; displays the aggregator administration page. + * Page callback: Displays the Aggregator module administration page. */ function aggregator_admin_overview() { return aggregator_view(); @@ -16,7 +16,7 @@ function aggregator_admin_overview() { * Displays the aggregator administration page. * * @return - * The page HTML. + * A HTML-formatted string with administration page content. */ function aggregator_view() { $result = db_query('SELECT f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block ORDER BY f.title'); @@ -56,8 +56,8 @@ function aggregator_view() { * Form constructor for adding and editing feed sources. * * @param $feed - * If editing a feed, the feed to edit as a PHP stdClass value; if adding a - * new feed, NULL. + * (optional) If editing a feed, the feed to edit as a PHP stdClass value; if + * adding a new feed, NULL. Defaults to NULL. * * @ingroup forms * @see aggregator_form_feed_validate() @@ -165,6 +165,7 @@ function aggregator_form_feed_validate($form, &$form_state) { * Form submission handler for aggregator_form_feed(). * * @see aggregator_form_feed_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_feed_submit($form, &$form_state) { @@ -398,7 +399,7 @@ function _aggregator_parse_opml($opml) { } /** - * Menu callback; refreshes a feed, then redirects to the overview page. + * Page callback: Refreshes a feed, then redirects to the overview page. * * @param $feed * An object describing the feed to be refreshed. @@ -590,6 +591,7 @@ function aggregator_form_category_validate($form, &$form_state) { * Form submission handler for aggregator_form_category(). * * @see aggregator_form_category_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_category_submit($form, &$form_state) { diff --git a/modules/aggregator/aggregator.css b/modules/aggregator/aggregator.css index 13c58ffe70eeeb58f82954a4f75c6c224c1c7633..4285631e87aa657329740a18f33b3b0f717c8386 100644 --- a/modules/aggregator/aggregator.css +++ b/modules/aggregator/aggregator.css @@ -1,3 +1,6 @@ +/** + * Styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-title { margin-top: 0; diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info index 53683b694fde09f17d15e5e1376d401404e67f0b..23a309f315b6fa1e231b2f2dd89e48ffa61c9487 100644 --- a/modules/aggregator/aggregator.info +++ b/modules/aggregator/aggregator.info @@ -7,8 +7,8 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 93457c68de0adf91d0c2fba6555ae6a3ee065b1e..70f8c5cd828bd63c096419b35336ec1626ca09d7 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -266,13 +266,13 @@ function aggregator_menu() { } /** - * Title callback: Returns a title for aggregatory category pages. + * Title callback: Returns a title for aggregator category pages. * * @param $category * An aggregator category. * * @return - * An aggregator category title. + * A string with the aggregator category title. */ function _aggregator_category_title($category) { return $category['title']; @@ -723,7 +723,7 @@ function theme_aggregator_block_item($variables) { } /** - * Safely renders HTML content, as allowed. + * Renders the HTML content safely, as allowed. * * @param $value * The content to be filtered. @@ -739,7 +739,7 @@ function aggregator_filter_xss($value) { * Checks and sanitizes the aggregator configuration. * * Goes through all fetchers, parsers and processors and checks whether they - * are available. If one is missing resets to standard configuration. + * are available. If one is missing, resets to standard configuration. * * @return * TRUE if this function resets the configuration; FALSE if not. @@ -775,7 +775,7 @@ function aggregator_sanitize_configuration() { * Items count. * * @return - * Plural-formatted "@count items" + * A string that is plural-formatted as "@count items". */ function _aggregator_items($count) { return format_plural($count, '1 item', '@count items'); diff --git a/modules/aggregator/aggregator.pages.inc b/modules/aggregator/aggregator.pages.inc index cd1c4cb2c08cc0600bc43c248b36aed654c538d5..bfba3fffb0dfe8ff6bd28ceafbf3fd3ac3e40110 100644 --- a/modules/aggregator/aggregator.pages.inc +++ b/modules/aggregator/aggregator.pages.inc @@ -2,14 +2,14 @@ /** * @file - * User page callbacks for the aggregator module. + * User page callbacks for the Aggregator module. */ /** - * Menu callback; displays the most recent items gathered from any feed. + * Page callback: Displays the most recent items gathered from any feed. * * @return - * The items HTML. + * The rendered list of items for the feed. */ function aggregator_page_last() { drupal_add_feed('aggregator/rss', variable_get('site_name', 'Drupal') . ' ' . t('aggregator')); @@ -20,13 +20,15 @@ function aggregator_page_last() { } /** - * Menu callback; displays all the items captured from a particular feed. + * Page callback: Displays all the items captured from the particular feed. * * @param $feed * The feed for which to display all items. * * @return * The rendered list of items for a feed. + * + * @see aggregator_menu() */ function aggregator_page_source($feed) { drupal_set_title($feed->title); @@ -40,13 +42,13 @@ function aggregator_page_source($feed) { } /** - * Menu callback; displays a form with all items captured from a feed. + * Page callback: Displays a form with all items captured from a feed. * * @param $feed - * The feed for which to list all the aggregated items. + * The feed for which to list all of the aggregated items. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. * * @see aggregator_page_source() */ @@ -55,13 +57,13 @@ function aggregator_page_source_form($form, $form_state, $feed) { } /** - * Menu callback; displays all the items aggregated in a particular category. + * Page callback: Displays all the items aggregated in a particular category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. */ function aggregator_page_category($category) { drupal_add_feed('aggregator/rss/' . $category['cid'], variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category['title']))); @@ -74,13 +76,13 @@ function aggregator_page_category($category) { } /** - * Menu callback; displays a form containing items aggregated in a category. + * Page callback: Displays a form containing items aggregated in a category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. * * @see aggregator_page_category() */ @@ -166,7 +168,7 @@ function aggregator_feed_items_load($type, $data = NULL) { * The feed source URL. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. */ function _aggregator_page_list($items, $op, $feed_source = '') { if (user_access('administer news feeds') && ($op == 'categorize')) { @@ -191,10 +193,14 @@ function _aggregator_page_list($items, $op, $feed_source = '') { * @param $items * An array of the feed items. * @param $feed_source - * The feed source URL. + * (optional) The feed source URL. Defaults to an empty string. + * + * @return array + * An array of FAPI elements. * - * @ingroup forms * @see aggregator_categorize_items_submit() + * @see theme_aggregator_categorize_items() + * @ingroup forms */ function aggregator_categorize_items($items, $feed_source = '') { $form['#submit'][] = 'aggregator_categorize_items_submit'; @@ -334,7 +340,12 @@ function template_preprocess_aggregator_item(&$variables) { } /** - * Menu callback; displays all the feeds used by the aggregator. + * Page callback: Displays all the feeds used by the aggregator. + * + * @return + * An HTML-formatted string. + * + * @see aggregator_menu() */ function aggregator_page_sources() { $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title'); @@ -358,7 +369,12 @@ function aggregator_page_sources() { } /** - * Menu callback; displays all the categories used by the aggregator. + * Page callback: Displays all the categories used by the Aggregator module. + * + * @return string + * An HTML formatted string. + * + * @see aggregator_menu() */ function aggregator_page_categories() { $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description'); @@ -380,7 +396,10 @@ function aggregator_page_categories() { } /** - * Menu callback; generate an RSS 0.92 feed of aggregator items or categories. + * Page callback: Generates an RSS 0.92 feed of aggregator items or categories. + * + * @return string + * An HTML formatted string. */ function aggregator_page_rss() { $result = NULL; @@ -448,12 +467,14 @@ function theme_aggregator_page_rss($variables) { } /** - * Menu callback; generates an OPML representation of all feeds. + * Page callback: Generates an OPML representation of all feeds. * * @param $cid - * If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported. + * (optional) If set, feeds are exported only from a category with this ID. + * Otherwise, all feeds are exported. Defaults to NULL. + * * @return - * The output XML. + * An OPML formatted string. */ function aggregator_page_opml($cid = NULL) { if ($cid) { @@ -468,14 +489,12 @@ function aggregator_page_opml($cid = NULL) { } /** - * Prints the OPML page for a feed. + * Prints the OPML page for the feed. * * @param $variables * An associative array containing: * - feeds: An array of the feeds to theme. * - * @return void - * * @ingroup themeable */ function theme_aggregator_page_opml($variables) { diff --git a/modules/aggregator/aggregator.processor.inc b/modules/aggregator/aggregator.processor.inc index 3f1319c8c118de093cbd48206a4474564df14d11..44ed549962a041390a2a0a0029579b0810a91fcf 100644 --- a/modules/aggregator/aggregator.processor.inc +++ b/modules/aggregator/aggregator.processor.inc @@ -131,6 +131,12 @@ function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { * * Callback for drupal_map_assoc() within * aggregator_form_aggregator_admin_form_alter(). + * + * @param $length + * The desired length of teaser text, in bytes. + * + * @return + * A translated string explaining the teaser string length. */ function _aggregator_characters($length) { return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters'); diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index eff31020f104f7b879974de4ded83ff59db49b50..8b95d6e379bb50554ef9e79a77b564f5b5fa5cca 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -5,6 +5,9 @@ * Tests for aggregator.module. */ +/** + * Defines a base class for testing the Aggregator module. + */ class AggregatorTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('aggregator', 'aggregator_test'); @@ -13,10 +16,15 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed). + * Creates an aggregator feed. + * + * This method simulates the form submission on path + * admin/config/services/aggregator/add/feed. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. + * * @return $feed * Full feed object if possible. * @@ -33,7 +41,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Delete an aggregator feed. + * Deletes an aggregator feed. * * @param $feed * Feed object representing the feed. @@ -44,10 +52,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return a randomly generated feed edit array. + * Returns a randomly generated feed edit array. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. * @return * A feed array. */ @@ -68,7 +77,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return the count of the randomly created feed array. + * Returns the count of the randomly created feed array. * * @return * Number of feed items on default feed created by createFeed(). @@ -80,10 +89,13 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Update feed items (simulate click to admin/config/services/aggregator/update/$fid). + * Updates the feed items. + * + * This method simulates a click to + * admin/config/services/aggregator/update/$fid. * * @param $feed - * Feed object representing the feed. + * Feed object representing the feed, passed by reference. * @param $expected_count * Expected number of feed items. */ @@ -112,7 +124,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Confirm item removal from a feed. + * Confirms an item removal from a feed. * * @param $feed * Feed object representing the feed. @@ -123,7 +135,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Add and remove feed items and ensure that the count is zero. + * Adds and removes feed items and ensure that the count is zero. * * @param $feed * Feed object representing the feed. @@ -140,7 +152,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull feed categories from aggregator_category_feed table. + * Pulls feed categories from {aggregator_category_feed} table. * * @param $feed * Feed object representing the feed. @@ -154,7 +166,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull categories from aggregator_category table. + * Pulls categories from {aggregator_category} table. + * + * @return + * An associative array keyed by category ID and values are set to the + * category names. */ function getCategories() { $categories = array(); @@ -165,14 +181,14 @@ class AggregatorTestCase extends DrupalWebTestCase { return $categories; } - /** - * Check if the feed name and URL is unique. + * Checks whether the feed name and URL are unique. * * @param $feed_name * String containing the feed name to check. * @param $feed_url * String containing the feed URL to check. + * * @return * TRUE if feed is unique. */ @@ -182,10 +198,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create a valid OPML file from an array of feeds. + * Creates a valid OPML file from an array of feeds. * * @param $feeds * An array of feeds. + * * @return * Path to valid OPML file. */ @@ -223,7 +240,7 @@ EOF; } /** - * Create an invalid OPML file. + * Creates an invalid OPML file. * * @return * Path to invalid OPML file. @@ -240,7 +257,7 @@ EOF; } /** - * Create a valid but empty OPML file. + * Creates a valid but empty OPML file. * * @return * Path to empty OPML file. @@ -275,7 +292,7 @@ EOF; * Creates sample article nodes. * * @param $count - * (optional) The number of nodes to generate. + * (optional) The number of nodes to generate. Defaults to five. */ function createSampleNodes($count = 5) { $langcode = LANGUAGE_NONE; @@ -290,7 +307,7 @@ EOF; } /** - * Tests aggregator configuration settings. + * Tests functionality of the configuration settings in the Aggregator module. */ class AggregatorConfigurationTestCase extends AggregatorTestCase { public static function getInfo() { @@ -321,6 +338,9 @@ class AggregatorConfigurationTestCase extends AggregatorTestCase { } } +/** + * Tests adding aggregator feeds. + */ class AddFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -331,7 +351,7 @@ class AddFeedTestCase extends AggregatorTestCase { } /** - * Create a feed, ensure that it is unique, check the source, and delete the feed. + * Creates and ensures that a feed is unique, checks source, and deletes feed. */ function testAddFeed() { $feed = $this->createFeed(); @@ -381,6 +401,9 @@ class AddFeedTestCase extends AggregatorTestCase { } } +/** + * Tests the categorize feed functionality in the Aggregator module. + */ class CategorizeFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -391,7 +414,7 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and make sure you can add more than one category to it. + * Creates a feed and makes sure you can add more than one category to it. */ function testCategorizeFeed() { @@ -424,6 +447,9 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating the feed in the Aggregator module. + */ class UpdateFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -434,7 +460,7 @@ class UpdateFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and attempt to update it. + * Creates a feed and attempts to update it. */ function testUpdateFeed() { $remamining_fields = array('title', 'url', ''); @@ -466,6 +492,9 @@ class UpdateFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality for removing feeds in the Aggregator module. + */ class RemoveFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -476,7 +505,7 @@ class RemoveFeedTestCase extends AggregatorTestCase { } /** - * Remove a feed and ensure that all it services are removed. + * Removes a feed and ensures that all of its services are removed. */ function testRemoveFeed() { $feed = $this->createFeed(); @@ -494,6 +523,9 @@ class RemoveFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating a feed item in the Aggregator module. + */ class UpdateFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -504,7 +536,7 @@ class UpdateFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "update items" from the 'admin/config/services/aggregator' page. + * Tests running "update items" from 'admin/config/services/aggregator' page. */ function testUpdateFeedItem() { $this->createSampleNodes(); @@ -564,7 +596,7 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "remove items" from the 'admin/config/services/aggregator' page. + * Tests running "remove items" from 'admin/config/services/aggregator' page. */ function testRemoveFeedItem() { // Create a bunch of test feeds. @@ -592,6 +624,9 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests categorization functionality in the Aggregator module. + */ class CategorizeFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -602,6 +637,8 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } /** + * Checks that children of a feed inherit a defined category. + * * If a feed has a category, make sure that the children inherit that * categorization. */ @@ -649,6 +686,9 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests importing feeds from OPML functionality for the Aggregator module. + */ class ImportOPMLTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -659,7 +699,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Open OPML import form. + * Opens OPML import form. */ function openImportForm() { db_delete('aggregator_category')->execute(); @@ -681,7 +721,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form filled with invalid fields. + * Submits form filled with invalid fields. */ function validateImportFormFields() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -707,7 +747,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form with invalid, empty and valid OPML files. + * Submits form with invalid, empty, and valid OPML files. */ function submitImportForm() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -766,6 +806,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { $this->assertTrue($category, 'Categories are correct.'); } + /** + * Tests the import of an OPML file. + */ function testOPMLImport() { $this->openImportForm(); $this->validateImportFormFields(); @@ -773,6 +816,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of the cron process in the Aggregator module. + */ class AggregatorCronTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -783,7 +829,7 @@ class AggregatorCronTestCase extends AggregatorTestCase { } /** - * Add feeds update them on cron. + * Adds feeds and updates them via cron process. */ public function testCron() { // Create feed and test basic updating on cron. @@ -819,6 +865,9 @@ class AggregatorCronTestCase extends AggregatorTestCase { } } +/** + * Tests rendering functionality in the Aggregator module. + */ class AggregatorRenderingTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -829,9 +878,9 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Add a feed block to the page and checks its links. + * Adds a feed block to the page and checks its links. * - * TODO: Test the category block as well. + * @todo Test the category block as well. */ public function testBlockLinks() { // Create feed. @@ -890,7 +939,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Create a feed and check that feed's page. + * Creates a feed and checks that feed's page. */ public function testFeedPage() { // Increase the number of items published in the rss.xml feed so we have @@ -913,7 +962,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Tests for feed parsing. + * Tests feed parsing in the Aggregator module. */ class FeedParserTestCase extends AggregatorTestCase { public static function getInfo() { @@ -933,7 +982,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the RSS 0.91 format. + * Tests a feed that uses the RSS 0.91 format. */ function testRSS091Sample() { $feed = $this->createFeed($this->getRSS091Sample()); @@ -955,7 +1004,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the Atom format. + * Tests a feed that uses the Atom format. */ function testAtomSample() { $feed = $this->createFeed($this->getAtomSample()); diff --git a/modules/aggregator/tests/aggregator_test.info b/modules/aggregator/tests/aggregator_test.info index d1e0bcc6483c1c4ec4f534f2bfd2728667000a56..2508a29fde002659e1accdc534817d48e6c45cd1 100644 --- a/modules/aggregator/tests/aggregator_test.info +++ b/modules/aggregator/tests/aggregator_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/block/block.info b/modules/block/block.info index 470a37831db88ef2a3111bc44dd71ca4458f22a1..6710487806398a1a99e8eebc1219e274a36d15c9 100644 --- a/modules/block/block.info +++ b/modules/block/block.info @@ -6,8 +6,8 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/block/block.module b/modules/block/block.module index 70f55e5ab74f4ba68a21d0d999c888370ed45d17..ff77d9ec3cabc57f8cc629bf2a964423d7077c02 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -740,7 +740,7 @@ function _block_load_blocks() { /** * Implements hook_block_list_alter(). * - * Checks the page, user role, and user-specific visibilty settings. + * Checks the page, user role, and user-specific visibility settings. * Removes the block if the visibility conditions are not met. */ function block_block_list_alter(&$blocks) { diff --git a/modules/block/tests/block_test.info b/modules/block/tests/block_test.info index d5a6af3c6c80f83da3fc002ce3a98db4fbd5424f..d1a01cfdcca68946353fbe44e5878aa4a8e92945 100644 --- a/modules/block/tests/block_test.info +++ b/modules/block/tests/block_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/block/tests/themes/block_test_theme/block_test_theme.info b/modules/block/tests/themes/block_test_theme/block_test_theme.info index 46291e41b29d2bec32137ef7307866dbaf314a9f..db7de24cf2cfceff1b75fd617d99f437669522e7 100644 --- a/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,8 +13,8 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/blog/blog.info b/modules/blog/blog.info index 16ef24aaf2ad42f5995185c595fa3f4bb9ba67bf..0da270332f39c3b1c7e535a02de45b148f85ba7d 100644 --- a/modules/blog/blog.info +++ b/modules/blog/blog.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/book/book-rtl.css b/modules/book/book-rtl.css index f3a84c20e32f7c2b93b97723e800cece3375bde7..40dff0e53c98c22dc153ac78bcb060e830b61cd6 100644 --- a/modules/book/book-rtl.css +++ b/modules/book/book-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Book module. + */ .book-navigation .menu { padding: 1em 3em 0 0; diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index 62c6e841a3f315839f89c3111c48d3c307b44164..cc3f08fc8e07986563a4f83801a98eae7cd110a3 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -2,11 +2,16 @@ /** * @file - * Admin page callbacks for the book module. + * Administration page callbacks for the Book module. */ /** * Returns an administrative overview of all books. + * + * @return string + * A HTML-formatted string with the administrative page content. + * + * @see book_menu() */ function book_admin_overview() { $rows = array(); @@ -53,6 +58,8 @@ function book_admin_settings() { /** * Form validation handler for book_admin_settings(). + * + * @see book_admin_settings_submit() */ function book_admin_settings_validate($form, &$form_state) { $child_type = $form_state['values']['book_child_type']; @@ -149,7 +156,7 @@ function book_admin_edit_submit($form, &$form_state) { * @param $node * The node of the top-level page in the book. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @see book_admin_edit() */ @@ -184,10 +191,10 @@ function _book_admin_table($node, &$form) { * @param $tree * A subtree of the book menu hierarchy. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @return - * The form that is being modified. + * The modified form array. * * @see book_admin_edit() */ diff --git a/modules/book/book.css b/modules/book/book.css index a8d2136df0cba6282fb3c881c7973ad1a0124a34..00e379ee058f03ff8e19c63f1853b750de205099 100644 --- a/modules/book/book.css +++ b/modules/book/book.css @@ -1,3 +1,7 @@ + /** + * @file + * Styling for the Book module. + */ .book-navigation .menu { border-top: 1px solid #888; diff --git a/modules/book/book.info b/modules/book/book.info index 4a96b2cbed65cf148b84d20976f041aab72e59ed..cb8a5db8ec48d7586474088dd33fd23e6a5dbe86 100644 --- a/modules/book/book.info +++ b/modules/book/book.info @@ -7,8 +7,8 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/book/book.js b/modules/book/book.js index 0853e8ecbeaa030dab5d726ffd1efa82a4234393..64f4aee687128f0cff4a6509a138311ac34347ba 100644 --- a/modules/book/book.js +++ b/modules/book/book.js @@ -3,7 +3,6 @@ * Javascript behaviors for the Book module. */ - (function ($) { Drupal.behaviors.bookFieldsetSummaries = { diff --git a/modules/book/book.module b/modules/book/book.module index 1fb0c0b119f6ec66eaad5393ce09f0e9899911cf..71b89945ee51890c302bd0b54fb481ab44b3fee4 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -221,6 +221,9 @@ function _book_outline_remove_access($node) { * * A node can be removed from a book if it is actually in a book and it either * is not a top-level page or is a top-level page with no children. + * + * @param $node + * The node to remove from the outline. */ function _book_node_is_removable($node) { return (!empty($node->book['bid']) && (($node->book['bid'] != $node->nid) || !$node->book['has_children'])); @@ -734,7 +737,7 @@ function book_get_flat_menu($book_link) { * @param $tree * A tree of menu links in an array. * @param $flat - * A flat array of the menu links from $tree. + * A flat array of the menu links from $tree, passed by reference. * * @see book_get_flat_menu(). */ @@ -1062,8 +1065,9 @@ function _book_link_defaults($nid) { * to the structured data but can also simply iterate over all elements and * render them (as in the default template). * - * The $variables array contains the following elements: - * - book_menus + * @param $variables + * An associative array containing the following key: + * - book_menus * * @see book-all-books-block.tpl.php */ @@ -1079,8 +1083,9 @@ function template_preprocess_book_all_books_block(&$variables) { /** * Processes variables for book-navigation.tpl.php. * - * The $variables array contains the following elements: - * - book_link + * @param $variables + * An associative array containing the following key: + * - book_link * * @see book-navigation.tpl.php */ @@ -1151,8 +1156,9 @@ function template_preprocess_book_navigation(&$variables) { * Reference to the table of contents array. This is modified in place, so the * function does not have a return value. * @param $exclude - * Optional array of menu link ID values. Any link whose menu link ID is in - * this array will be excluded (along with its children). + * (optional) An array of menu link ID values. Any link whose menu link ID is + * in this array will be excluded (along with its children). Defaults to an + * empty array. * @param $depth_limit * Any link deeper than this value will be excluded (along with its children). */ @@ -1198,10 +1204,11 @@ function book_toc($bid, $depth_limit, $exclude = array()) { /** * Processes variables for book-export-html.tpl.php. * - * The $variables array contains the following elements: - * - title - * - contents - * - depth + * @param $variables + * An associative array containing the following keys: + * - title + * - contents + * - depth * * @see book-export-html.tpl.php */ @@ -1261,7 +1268,8 @@ function book_export_traverse($tree, $visit_func) { * @param $node * The node that will be output. * @param $children - * All the rendered child nodes within the current node. + * (optional) All the rendered child nodes within the current node. Defaults + * to an empty string. * * @return * The HTML generated for the given node. @@ -1280,9 +1288,10 @@ function book_node_export($node, $children = '') { /** * Processes variables for book-node-export-html.tpl.php. * - * The $variables array contains the following elements: - * - node - * - children + * @param $variables + * An associative array containing the following keys: + * - node + * - children * * @see book-node-export-html.tpl.php */ @@ -1294,6 +1303,12 @@ function template_preprocess_book_node_export_html(&$variables) { /** * Determine if a given node type is in the list of types allowed for books. + * + * @param $type + * A node type. + * + * @return + * A Boolean TRUE if the node type can be included in books; otherwise, FALSE. */ function book_type_is_allowed($type) { return in_array($type, variable_get('book_allowed_types', array('book'))); @@ -1336,7 +1351,7 @@ function book_node_type_update($type) { * * @return * A menu link, with the link translated for rendering and data added from the - * {book} table. + * {book} table. FALSE if there is an error. */ function book_link_load($mlid) { if ($item = db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array( diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index 63a1d15a4fc21c815c7bab9c2bc2fd43bd9cda94..ac4f3572fd459ac6d943567e6f238a8c3dc3ad73 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -7,6 +7,11 @@ /** * Menu callback: Prints a listing of all books. + * + * @return string + * A HTML-formatted string with the listing of all books content. + * + * @see book_menu() */ function book_render() { $book_list = array(); @@ -36,6 +41,8 @@ function book_render() { * @return * A string representing the node and its children in the book hierarchy in a * format determined by the $type parameter. + * + * @see book_menu() */ function book_export($type, $nid) { // Check that the node exists and that the current user has access to it. @@ -79,7 +86,6 @@ function book_export($type, $nid) { */ function book_export_html($nid) { if (user_access('access printer-friendly version')) { - $export_data = array(); $node = node_load($nid); if (isset($node->book)) { $tree = book_menu_subtree_data($node->book); @@ -100,6 +106,11 @@ function book_export_html($nid) { * * @param $node * The book node for which to show the outline. + * + * @return string + * A HTML-formatted string with the outline form for a single node. + * + * @see book_menu() */ function book_outline($node) { drupal_set_title($node->title); diff --git a/modules/book/book.test b/modules/book/book.test index 2708e367477ae2fcd945c470cb7b12f6e5494874..81f4524ac92079bf173268ea8dc3532421d731e8 100644 --- a/modules/book/book.test +++ b/modules/book/book.test @@ -5,14 +5,37 @@ * Tests for book.module. */ +/** + * Tests the functionality of the Book module. + */ class BookTestCase extends DrupalWebTestCase { + + /** + * A book node. + * + * @var object + */ protected $book; - // $book_author is a user with permission to create and edit books. + + /** + * A user with permission to create and edit books. + * + * @var object + */ protected $book_author; - // $web_user is a user with permission to view a book - // and access the printer-friendly version. + + /** + * A user with permission to view a book and access printer-friendly version. + * + * @var object + */ protected $web_user; - // $admin_user is a user with permission to create and edit books and to administer blocks. + + /** + * A user with permission to create and edit books and to administer blocks. + * + * @var object + */ protected $admin_user; public static function getInfo() { @@ -36,7 +59,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create a new book with a page hierarchy. + * Creates a new book with a page hierarchy. */ function createBook() { // Create new book. @@ -67,7 +90,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Test book functionality through node interfaces. + * Tests book functionality through node interfaces. */ function testBook() { // Create new book. @@ -106,18 +129,20 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Check the outline of sub-pages; previous, up, and next; and printer friendly version. + * Checks the outline of sub-pages; previous, up, and next. + * + * Also checks the printer friendly version of the outline. * * @param $node * Node to check. * @param $nodes * Nodes that should be in outline. * @param $previous - * Previous link node. + * (optional) Previous link node. Defaults to FALSE. * @param $up - * Up link node. + * (optional) Up link node. Defaults to FALSE. * @param $next - * Next link node. + * (optional) Next link node. Defaults to FALSE. * @param $breadcrumb * The nodes that should be displayed in the breadcrumb. */ @@ -129,23 +154,23 @@ class BookTestCase extends DrupalWebTestCase { // Check outline structure. if ($nodes !== NULL) { - $this->assertPattern($this->generateOutlinePattern($nodes), t('Node ' . $number . ' outline confirmed.')); + $this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node %number outline confirmed.', array('%number' => $number))); } else { - $this->pass(t('Node ' . $number . ' doesn\'t have outline.')); + $this->pass(format_string('Node %number does not have outline.', array('%number' => $number))); } // Check previous, up, and next links. if ($previous) { - $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), t('Previous page link found.')); + $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), 'Previous page link found.'); } if ($up) { - $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), t('Up page link found.')); + $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), 'Up page link found.'); } if ($next) { - $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), t('Next page link found.')); + $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), 'Next page link found.'); } // Compute the expected breadcrumb. @@ -163,20 +188,24 @@ class BookTestCase extends DrupalWebTestCase { } // Compare expected and got breadcrumbs. - $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, t('The breadcrumb is correctly displayed on the page.')); + $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.'); // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); - $this->assertText($node->title, t('Printer friendly title found.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Printer friendly body found.')); + $this->assertText($node->title, 'Printer friendly title found.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Printer friendly body found.'); $number++; } /** - * Create a regular expression to check for the sub-nodes in the outline. + * Creates a regular expression to check for the sub-nodes in the outline. + * + * @param array $nodes + * An array of nodes to check in outline. * - * @param array $nodes Nodes to check in outline. + * @return + * A regular expression that locates sub-nodes of the outline. */ function generateOutlinePattern($nodes) { $outline = ''; @@ -188,10 +217,12 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create book node. + * Creates a book node. * - * @param integer $book_nid Book node id or set to 'new' to create new book. - * @param integer $parent Parent book reference id. + * @param $book_nid + * A book node ID or set to 'new' to create a new book. + * @param $parent + * (optional) Parent book reference ID. Defaults to NULL. */ function createBookNode($book_nid, $parent = NULL) { // $number does not use drupal_static as it should not be reset @@ -216,7 +247,7 @@ class BookTestCase extends DrupalWebTestCase { // Check to make sure the book node was created. $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertNotNull(($node === FALSE ? NULL : $node), t('Book node found in database.')); + $this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.'); $number++; return $node; @@ -236,28 +267,28 @@ class BookTestCase extends DrupalWebTestCase { // Make sure each part of the book is there. foreach ($nodes as $node) { - $this->assertText($node->title, t('Node title found in printer friendly version.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Node body found in printer friendly version.')); + $this->assertText($node->title, 'Node title found in printer friendly version.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Node body found in printer friendly version.'); } // Make sure we can't export an unsupported format. $this->drupalGet('book/export/foobar/' . $this->book->nid); - $this->assertResponse('404', t('Unsupported export format returned "not found".')); + $this->assertResponse('404', 'Unsupported export format returned "not found".'); // Make sure we get a 404 on a not existing book node. $this->drupalGet('book/export/html/123'); - $this->assertResponse('404', t('Not existing book node returned "not found".')); + $this->assertResponse('404', 'Not existing book node returned "not found".'); // Make sure an anonymous user cannot view printer-friendly version. $this->drupalLogout(); // Load the book and verify there is no printer-friendly version link. $this->drupalGet('node/' . $this->book->nid); - $this->assertNoLink(t('Printer-friendly version'), t('Anonymous user is not shown link to printer-friendly version.')); + $this->assertNoLink(t('Printer-friendly version'), 'Anonymous user is not shown link to printer-friendly version.'); // Try getting the URL directly, and verify it fails. $this->drupalGet('book/export/html/' . $this->book->nid); - $this->assertResponse('403', t('Anonymous user properly forbidden.')); + $this->assertResponse('403', 'Anonymous user properly forbidden.'); // Now grant anonymous users permission to view the printer-friendly // version and verify that node access restrictions still prevent them from @@ -276,30 +307,30 @@ class BookTestCase extends DrupalWebTestCase { // Set block title to confirm that the interface is available. $block_title = $this->randomName(16); $this->drupalPost('admin/structure/block/manage/book/navigation/configure', array('title' => $block_title), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t("Permission 'node test view' successfully assigned to anonymous users.")); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Test correct display of the block. $nodes = $this->createBook(); $this->drupalGet('<front>'); - $this->assertText($block_title, t('Book navigation block is displayed.')); - $this->assertText($this->book->title, t('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); - $this->assertNoText($nodes[0]->title, t('No links to individual book pages are displayed.')); + $this->assertText($block_title, 'Book navigation block is displayed.'); + $this->assertText($this->book->title, format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); + $this->assertNoText($nodes[0]->title, 'No links to individual book pages are displayed.'); } /** - * Test the book navigation block when an access module is enabled. + * Tests the book navigation block when an access module is enabled. */ function testNavigationBlockOnAccessModuleEnabled() { $this->drupalLogin($this->admin_user); @@ -312,19 +343,19 @@ class BookTestCase extends DrupalWebTestCase { // Set block display to 'Show block only on book pages'. $edit['book_block_mode'] = 'book pages'; $this->drupalPost('admin/structure/block/manage/book/navigation/configure', $edit, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t('Permission \'node test view\' successfully assigned to anonymous users.')); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Create a book. $this->createBook(); @@ -332,12 +363,12 @@ class BookTestCase extends DrupalWebTestCase { // Test correct display of the block to registered users. $this->drupalLogin($this->web_user); $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to registered users.')); + $this->assertText($block_title, 'Book navigation block is displayed to registered users.'); $this->drupalLogout(); // Test correct display of the block to anonymous users. $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to anonymous users.')); + $this->assertText($block_title, 'Book navigation block is displayed to anonymous users.'); } /** @@ -350,10 +381,10 @@ class BookTestCase extends DrupalWebTestCase { // Test access to delete top-level and child book nodes. $this->drupalGet('node/' . $this->book->nid . '/outline/remove'); - $this->assertResponse('403', t('Deleting top-level book node properly forbidden.')); + $this->assertResponse('403', 'Deleting top-level book node properly forbidden.'); $this->drupalPost('node/' . $nodes[4]->nid . '/outline/remove', $edit, t('Remove')); $node4 = node_load($nodes[4]->nid, NULL, TRUE); - $this->assertTrue(empty($node4->book), t('Deleting child book node properly allowed.')); + $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.'); // Delete all child book nodes and retest top-level node deletion. foreach ($nodes as $node) { @@ -362,6 +393,6 @@ class BookTestCase extends DrupalWebTestCase { node_delete_multiple($nids); $this->drupalPost('node/' . $this->book->nid . '/outline/remove', $edit, t('Remove')); $node = node_load($this->book->nid, NULL, TRUE); - $this->assertTrue(empty($node->book), t('Deleting childless top-level book node properly allowed.')); + $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.'); } } diff --git a/modules/color/color.info b/modules/color/color.info index 4e70155277a24d99c11b8cf29dfb306c1bf79fb2..ba16536cb532056a0000bc67b9c31c7a3000ee42 100644 --- a/modules/color/color.info +++ b/modules/color/color.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = color.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc index 4f3d35071536658cda832478b20f6d99ffe2a5ef..43b53e27a3a4b254d3e4fcfb33cddbdadd2c3cbb 100644 --- a/modules/comment/comment.admin.inc +++ b/modules/comment/comment.admin.inc @@ -98,13 +98,14 @@ function comment_admin_overview($form, &$form_state, $arg) { // Remove the first node title from the node_titles array and attach to // the comment. $comment->node_title = array_shift($node_titles); + $comment_body = field_get_items('comment', $comment, 'comment_body'); $options[$comment->cid] = array( 'subject' => array( 'data' => array( '#type' => 'link', '#title' => $comment->subject, '#href' => 'comment/' . $comment->cid, - '#options' => array('attributes' => array('title' => truncate_utf8($comment->comment_body[LANGUAGE_NONE][0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), + '#options' => array('attributes' => array('title' => truncate_utf8($comment_body[0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), ), ), 'author' => theme('username', array('account' => $comment)), diff --git a/modules/comment/comment.info b/modules/comment/comment.info index fd2f91bf0a7de5be11893148ce6c67c168c209ff..7912dfec06277fa014a6fc0e5f1dcdcd40e748f4 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -9,8 +9,8 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 4241538a0353b248f5aef3b0c9c3f6141c847599..46115be042dac7005439580c74e88df9b02ff036 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -2041,7 +2041,8 @@ function comment_form($form, &$form_state, $comment) { // Attach fields. $comment->node_type = 'comment_node_' . $node->type; - field_attach_form('comment', $comment, $form, $form_state); + $langcode = entity_language('comment', $comment); + field_attach_form('comment', $comment, $form, $form_state, $langcode); return $form; } @@ -2066,7 +2067,8 @@ function comment_preview($comment) { $node = node_load($comment->nid); if (!form_get_errors()) { - $comment->format = $comment->comment_body[LANGUAGE_NONE][0]['format']; + $comment_body = field_get_items('comment', $comment, 'comment_body'); + $comment->format = $comment_body[0]['format']; // Attach the user and time information. if (!empty($comment->name)) { $account = user_load_by_name($comment->name); @@ -2190,7 +2192,9 @@ function comment_submit($comment) { // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. - $comment_body = $comment->comment_body[LANGUAGE_NONE][0]; + $field = field_info_field('comment_body'); + $langcode = field_is_translatable('comment', $field) ? entity_language('comment', $comment) : LANGUAGE_NONE; + $comment_body = $comment->comment_body[$langcode][0]; if (isset($comment_body['format'])) { $comment_text = check_markup($comment_body['value'], $comment_body['format']); } @@ -2284,8 +2288,16 @@ function template_preprocess_comment(&$variables) { $variables['comment'] = $comment; $variables['node'] = $node; $variables['author'] = theme('username', array('account' => $comment)); + $variables['created'] = format_date($comment->created); - $variables['changed'] = format_date($comment->changed); + + // Avoid calling format_date() twice on the same timestamp. + if ($comment->changed == $comment->created) { + $variables['changed'] = $variables['created']; + } + else { + $variables['changed'] = format_date($comment->changed); + } $variables['new'] = !empty($comment->new) ? t('new') : ''; $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', array('account' => $comment)) : ''; diff --git a/modules/contact/contact.info b/modules/contact/contact.info index 6a355df00c052f7fff504d7c98c5198c94ba5172..d78ba691e5660c55f2b314a48205a9509ccb3684 100644 --- a/modules/contact/contact.info +++ b/modules/contact/contact.info @@ -6,8 +6,8 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/contextual/contextual.info b/modules/contextual/contextual.info index cd3ba08b731f6a242ee2e057a5a9f8501e6beb17..dec089945d0915321c2f3d2fd8078908db0e47bd 100644 --- a/modules/contextual/contextual.info +++ b/modules/contextual/contextual.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/dashboard/dashboard.info b/modules/dashboard/dashboard.info index 448fdcc9cb05473c06e0b0b484b8dfdb42fbd693..6521eba5431a845ff442dadc17dcc71cfef09bc3 100644 --- a/modules/dashboard/dashboard.info +++ b/modules/dashboard/dashboard.info @@ -7,8 +7,8 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/dblog/dblog-rtl.css b/modules/dblog/dblog-rtl.css index 282fe971ddc3dde27a1121c5a3da8a3ada438563..0fab8d06500bcf27b76aa4a378ce067851e09bfd 100644 --- a/modules/dblog/dblog-rtl.css +++ b/modules/dblog/dblog-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Database Logging module. + */ .form-item-type, .form-item-severity { diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc index 0655e75644a110943228a50f16aa596a4879d4c8..7c1c0e20f3ea049d173d52a69e805d423e7a6fc6 100644 --- a/modules/dblog/dblog.admin.inc +++ b/modules/dblog/dblog.admin.inc @@ -2,14 +2,19 @@ /** * @file - * Administrative page callbacks for the dblog module. + * Administrative page callbacks for the Database Logging module. */ /** - * Menu callback; displays a listing of log messages. + * Page callback: Displays a listing of database log messages. * - * Messages are truncated at 56 chars. Full-length message could be viewed at - * the message details page. + * Messages are truncated at 56 chars. Full-length messages can be viewed on the + * message details page. + * + * @see dblog_clear_log_form() + * @see dblog_event() + * @see dblog_filter_form() + * @see dblog_menu() * * @ingroup logging_severity_levels */ @@ -81,12 +86,18 @@ function dblog_overview() { } /** - * Menu callback; generic function to display a page of the most frequent events. + * Page callback: Shows the most frequent log messages of a given event type. + * + * Messages are not truncated on this page because events detailed herein do not + * have links to a detailed view. + * + * @param string $type + * Type of database log events to display (e.g., 'search'). * - * Messages are not truncated because events from this page have no detail view. + * @return array + * A build array in the format expected by drupal_render(). * - * @param $type - * type of dblog events to display. + * @see dblog_menu() */ function dblog_top($type) { @@ -127,7 +138,16 @@ function dblog_top($type) { } /** - * Menu callback; displays details about a log message. + * Page callback: Displays details about a specific database log message. + * + * @param int $id + * Unique ID of the database log message. + * + * @return array|string + * If the ID is located in the Database Logging table, a build array in the + * format expected by drupal_render(); otherwise, an empty string. + * + * @see dblog_menu() */ function dblog_event($id) { $severity = watchdog_severity_levels(); @@ -184,7 +204,10 @@ function dblog_event($id) { } /** - * Build query for dblog administration filters based on session. + * Builds a query for database log administration filters based on session. + * + * @return array + * An associative array with keys 'where' and 'args'. */ function dblog_build_filter_query() { if (empty($_SESSION['dblog_overview_filter'])) { @@ -213,9 +236,16 @@ function dblog_build_filter_query() { ); } - /** - * List dblog administration filters that can be applied. + * Creates a list of database log administration filters that can be applied. + * + * @return array + * Associative array of filters. The top-level keys are used as the form + * element names for the filters, and the values are arrays with the following + * elements: + * - title: Title of the filter. + * - where: The filter condition. + * - options: Array of options for the select list for the filter. */ function dblog_filters() { $filters = array(); @@ -244,7 +274,7 @@ function dblog_filters() { /** * Returns HTML for a log message. * - * @param $variables + * @param array $variables * An associative array containing: * - event: An object with at least the message and variables properties. * - link: (optional) Format message as link, event->wid is required. @@ -274,11 +304,13 @@ function theme_dblog_message($variables) { } /** - * Return form for dblog administration filters. + * Form constructor for the database logging filter form. * - * @ingroup forms - * @see dblog_filter_form_submit() * @see dblog_filter_form_validate() + * @see dblog_filter_form_submit() + * @see dblog_overview() + * + * @ingroup forms */ function dblog_filter_form($form) { $filters = dblog_filters(); @@ -316,12 +348,13 @@ function dblog_filter_form($form) { '#value' => t('Reset') ); } - return $form; } /** - * Validate result from dblog administration filter form. + * Form validation handler for dblog_filter_form(). + * + * @see dblog_filter_form_submit() */ function dblog_filter_form_validate($form, &$form_state) { if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) { @@ -330,7 +363,9 @@ function dblog_filter_form_validate($form, &$form_state) { } /** - * Process result from dblog administration filter form. + * Form submission handler for dblog_filter_form(). + * + * @see dblog_filter_form_validate() */ function dblog_filter_form_submit($form, &$form_state) { $op = $form_state['values']['op']; @@ -351,10 +386,10 @@ function dblog_filter_form_submit($form, &$form_state) { } /** - * Return form for dblog clear button. + * Form constructor for the form that clears out the log. * - * @ingroup forms * @see dblog_clear_log_submit() + * @ingroup forms */ function dblog_clear_log_form($form) { $form['dblog_clear'] = array( @@ -374,7 +409,7 @@ function dblog_clear_log_form($form) { } /** - * Submit callback: clear database with log messages. + * Form submission handler for dblog_clear_log_form(). */ function dblog_clear_log_submit() { $_SESSION['dblog_overview_filter'] = array(); diff --git a/modules/dblog/dblog.css b/modules/dblog/dblog.css index 88f4ba01b8ad39facb296f7300c8eedadaacb422..b1278862aee81c86ba4c1ba03cea38a9bdafd411 100644 --- a/modules/dblog/dblog.css +++ b/modules/dblog/dblog.css @@ -1,3 +1,8 @@ +/** + * @file + * Admin styles for the Database Logging module. + */ + .form-item-type, .form-item-severity { float: left; /* LTR */ diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info index 1e36fc7fff069c44b3de0d3a01467f2ee95a7113..1655024d1b39656abb6ca6d82bd3e686fdb1998d 100644 --- a/modules/dblog/dblog.info +++ b/modules/dblog/dblog.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install index 1eedcb0d6e4b081965bc9abb25f670e652374bba..abfd9a2c9795b51e771e97c02a7f7e11bdbbe9ae 100644 --- a/modules/dblog/dblog.install +++ b/modules/dblog/dblog.install @@ -85,6 +85,7 @@ function dblog_schema() { 'indexes' => array( 'type' => array('type'), 'uid' => array('uid'), + 'severity' => array('severity'), ), ); @@ -140,3 +141,19 @@ function dblog_update_7001() { /** * @} End of "addtogroup updates-6.x-to-7.x". */ + +/** + * @addtogroup updates-7.x-extra + * @{ + */ + +/** + * Add an index to the severity column in the watchdog database table. + */ +function dblog_update_7002() { + db_add_index('watchdog', 'severity', array('severity')); +} + +/** + * @} End of "addtogroup updates-7.x-extra". + */ diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module index d831548c91cece0d9a4da35c158e4711e08ac06e..9183eed69aeb9dbf7be1aa73e5b32d23f31ed81f 100644 --- a/modules/dblog/dblog.module +++ b/modules/dblog/dblog.module @@ -4,9 +4,9 @@ * @file * System monitoring and logging for administrators. * - * The dblog module monitors your site and keeps a list of - * recorded events containing usage and performance data, errors, - * warnings, and similar operational information. + * The Database Logging module monitors your site and keeps a list of recorded + * events containing usage and performance data, errors, warnings, and similar + * operational information. * * @see watchdog() */ @@ -96,7 +96,7 @@ function dblog_init() { /** * Implements hook_cron(). * - * Remove expired log messages. + * Controls the size of the log table, paring it to 'dblog_row_limit' messages. */ function dblog_cron() { // Cleanup the watchdog table. @@ -121,6 +121,12 @@ function dblog_cron() { } } +/** + * Gathers a list of uniquely defined database log message types. + * + * @return array + * List of uniquely defined database log message types. + */ function _dblog_get_message_types() { $types = array(); @@ -135,7 +141,7 @@ function _dblog_get_message_types() { /** * Implements hook_watchdog(). * - * Note some values may be truncated for database column size restrictions. + * Note: Some values may be truncated to meet database column size restrictions. */ function dblog_watchdog(array $log_entry) { Database::getConnection('default', 'default')->insert('watchdog') @@ -155,7 +161,7 @@ function dblog_watchdog(array $log_entry) { } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_form_FORM_ID_alter() for system_logging_settings(). */ function dblog_form_system_logging_settings_alter(&$form, $form_state) { $form['dblog_row_limit'] = array( diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index ad01e97f5de3350511546a3305583261f25a4154..cd101930dc34a14213ffe9a8ae464b7735a189a1 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -5,8 +5,23 @@ * Tests for dblog.module. */ +/** + * Tests logging messages to the database. + */ class DBLogTestCase extends DrupalWebTestCase { + + /** + * A user with some relevent administrative permissions. + * + * @var object + */ protected $big_user; + + /** + * A user without any permissions. + * + * @var object + */ protected $any_user; public static function getInfo() { @@ -28,7 +43,11 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login users, create dblog events, and test dblog functionality through the admin and user interfaces. + * Tests Database Logging module functionality through interfaces. + * + * First logs in users, then creates database log events, and finally tests + * Database Logging module functionality through both the admin and user + * interfaces. */ function testDBLog() { // Login the admin user. @@ -46,12 +65,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify setting of the dblog row limit. + * Verifies setting of the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyRowLimit($row_limit) { - // Change the dblog row limit. + // Change the database log row limit. $edit = array(); $edit['dblog_row_limit'] = $row_limit; $this->drupalPost('admin/config/development/logging', $edit, t('Save configuration')); @@ -66,33 +86,35 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify cron applies the dblog row limit. + * Verifies that cron correctly applies the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyCron($row_limit) { // Generate additional log entries. $this->generateLogEntries($row_limit + 10); - // Verify dblog row count exceeds row limit. + // Verify that the database log row count exceeds the row limit. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count > $row_limit, t('Dblog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit))); - // Run cron job. + // Run a cron job. $this->cronRun(); - // Verify dblog row count equals row limit plus one because cron adds a record after it runs. + // Verify that the database log row count equals the row limit plus one + // because cron adds a record after it runs. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count == $row_limit + 1, t('Dblog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit))); } /** - * Generate dblog entries. + * Generates a number of random database log events. * - * @param integer $count - * Number of log entries to generate. - * @param $type - * The type of watchdog entry. - * @param $severity - * The severity of the watchdog entry. + * @param int $count + * Number of watchdog entries to generate. + * @param string $type + * (optional) The type of watchdog entry. Defaults to 'custom'. + * @param int $severity + * (optional) The severity of the watchdog entry. Defaults to WATCHDOG_NOTICE. */ private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { global $base_root; @@ -119,42 +141,43 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify the logged in user has the desired access to the various dblog nodes. + * Confirms that database log reports are displayed at the correct paths. * - * @param integer $response HTTP response code. + * @param int $response + * (optional) HTTP response code. Defaults to 200. */ private function verifyReports($response = 200) { $quote = '''; - // View dblog help node. + // View the database log help page. $this->drupalGet('admin/help/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Database logging'), t('DBLog help was displayed')); } - // View dblog report node. + // View the database log report page. $this->drupalGet('admin/reports/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Recent log messages'), t('DBLog report was displayed')); } - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), t('DBLog page-not-found report was displayed')); } - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), t('DBLog access-denied report was displayed')); } - // View dblog event node. + // View the database log event page. $this->drupalGet('admin/reports/event/1'); $this->assertResponse($response); if ($response == 200) { @@ -163,7 +186,7 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify events. + * Generates and then verifies various types of events. */ private function verifyEvents() { // Invoke events. @@ -179,14 +202,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify user events. - * + * Generates and then verifies some user events. */ private function doUser() { // Set user variables. $name = $this->randomName(); $pass = user_password(); - // Add user using form to generate add user event (which is not triggered by drupalCreateUser). + // Add a user using the form to generate an add user event (which is not + // triggered by drupalCreateUser). $edit = array(); $edit['name'] = $name; $edit['mail'] = $name . '@example.com'; @@ -195,15 +218,16 @@ class DBLogTestCase extends DrupalWebTestCase { $edit['status'] = 1; $this->drupalPost('admin/people/create', $edit, t('Create new account')); $this->assertResponse(200); - // Retrieve user object. + // Retrieve the user object. $user = user_load_by_name($name); $this->assertTrue($user != NULL, t('User @name was loaded', array('@name' => $name))); - $user->pass_raw = $pass; // Needed by drupalLogin. + // pass_raw property is needed by drupalLogin. + $user->pass_raw = $pass; // Login user. $this->drupalLogin($user); // Logout user. $this->drupalLogout(); - // Fetch row ids in watchdog that relate to the user. + // Fetch the row IDs in watchdog that relate to the user. $result = db_query('SELECT wid FROM {watchdog} WHERE uid = :uid', array(':uid' => $user->uid)); foreach ($result as $row) { $ids[] = $row->wid; @@ -213,17 +237,18 @@ class DBLogTestCase extends DrupalWebTestCase { // Login the admin user. $this->drupalLogin($this->big_user); - // Delete user. + // Delete the user created at the start of this test. // We need to POST here to invoke batch_process() in the internal browser. $this->drupalPost('user/' . $user->uid . '/cancel', array('user_cancel_method' => 'user_cancel_reassign'), t('Cancel account')); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. + // Verify that the expected events were recorded. // Add user. - // Default display includes name and email address; if too long then email is replaced by three periods. + // Default display includes name and email address; if too long, the email + // address is replaced by three periods. $this->assertLogMessage(t('New user: %name (%email).', array('%name' => $name, '%email' => $user->mail)), t('DBLog event was recorded: [add user]')); // Login user. $this->assertLogMessage(t('Session opened for %name.', array('%name' => $name)), t('DBLog event was recorded: [login user]')); @@ -232,7 +257,7 @@ class DBLogTestCase extends DrupalWebTestCase { // Delete user. $message = t('Deleted user: %name %email.', array('%name' => $name, '%email' => '<' . $user->mail . '>')); $message_text = truncate_utf8(filter_xss($message, array()), 56, TRUE, TRUE); - // Verify full message on details page. + // Verify that the full message displays on the details page. $link = FALSE; if ($links = $this->xpath('//a[text()="' . html_entity_decode($message_text) . '"]')) { // Found link with the message text. @@ -253,7 +278,7 @@ class DBLogTestCase extends DrupalWebTestCase { $not_found_url = $this->randomName(60); $this->drupalGet($not_found_url); $this->assertResponse(404); - // View dblog page-not-found report page. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); // Check that full-length URL displayed. @@ -261,9 +286,10 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify node events. + * Generates and then verifies some node events. * - * @param string $type Content type. + * @param string $type + * A node type (e.g., 'article', 'page' or 'poll'). */ private function doNode($type) { // Create user. @@ -272,61 +298,65 @@ class DBLogTestCase extends DrupalWebTestCase { // Login user. $this->drupalLogin($user); - // Create node using form to generate add content event (which is not triggered by drupalCreateNode). + // Create a node using the form in order to generate an add content event + // (which is not triggered by drupalCreateNode). $edit = $this->getContent($type); $langcode = LANGUAGE_NONE; $title = $edit["title"]; $this->drupalPost('node/add/' . $type, $edit, t('Save')); $this->assertResponse(200); - // Retrieve node object. + // Retrieve the node object. $node = $this->drupalGetNodeByTitle($title); $this->assertTrue($node != NULL, t('Node @title was loaded', array('@title' => $title))); - // Edit node. + // Edit the node. $edit = $this->getContentUpdate($type); $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertResponse(200); - // Delete node. + // Delete the node. $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete')); $this->assertResponse(200); - // View node (to generate page not found event). + // View the node (to generate page not found event). $this->drupalGet('node/' . $node->nid); $this->assertResponse(404); - // View the dblog report (to generate access denied event). + // View the database log report (to generate access denied event). $this->drupalGet('admin/reports/dblog'); $this->assertResponse(403); // Login the admin user. $this->drupalLogin($this->big_user); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. - // Content added. + // Verify that node events were recorded. + // Was node content added? $this->assertLogMessage(t('@type: added %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content added]')); - // Content updated. + // Was node content updated? $this->assertLogMessage(t('@type: updated %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content updated]')); - // Content deleted. + // Was node content deleted? $this->assertLogMessage(t('@type: deleted %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content deleted]')); - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse(200); - // Access denied. + // Verify that the 'access denied' event was recorded. $this->assertText(t('admin/reports/dblog'), t('DBLog event was recorded: [access denied]')); - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); - // Page not found. + // Verify that the 'page not found' event was recorded. $this->assertText(t('node/@nid', array('@nid' => $node->nid)), t('DBLog event was recorded: [page not found]')); } /** - * Create content based on content type. + * Creates random content based on node content type. * - * @param string $type Content type. - * @return array Content. + * @param string $type + * Node content type (e.g., 'article'). + * + * @return array + * Random content needed by various node types. */ private function getContent($type) { $langcode = LANGUAGE_NONE; @@ -350,10 +380,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Create content update based on content type. + * Creates random content as an update based on node content type. + * + * @param string $type + * Node content type (e.g., 'article'). * - * @param string $type Content type. - * @return array Content. + * @return array + * Random content needed by various node types. */ private function getContentUpdate($type) { switch ($type) { @@ -375,11 +408,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login an admin user, create dblog event, and test clearing dblog functionality through the admin interface. + * Tests the addition and clearing of log events through the admin interface. + * + * Logs in the admin user, creates a database log event, and tests the + * functionality of clearing the database log through the admin interface. */ protected function testDBLogAddAndClear() { global $base_root; - // Get a count of how many watchdog entries there are. + // Get a count of how many watchdog entries already exist. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $log = array( 'type' => 'custom', @@ -396,27 +432,27 @@ class DBLogTestCase extends DrupalWebTestCase { ); // Add a watchdog entry. dblog_watchdog($log); - // Make sure the table count has actually incremented. + // Make sure the table count has actually been incremented. $this->assertEqual($count + 1, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), t('dblog_watchdog() added an entry to the dblog :count', array(':count' => $count))); // Login the admin user. $this->drupalLogin($this->big_user); - // Now post to clear the db table. + // Post in order to clear the database table. $this->drupalPost('admin/reports/dblog', array(), t('Clear log messages')); - // Count rows in watchdog that previously related to the deleted user. + // Count the rows in watchdog that previously related to the deleted user. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $this->assertEqual($count, 0, t('DBLog contains :count records after a clear.', array(':count' => $count))); } /** - * Test the dblog filter on admin/reports/dblog. + * Tests the database log filter functionality at admin/reports/dblog. */ protected function testFilter() { $this->drupalLogin($this->big_user); - // Clear log to ensure that only generated entries are found. + // Clear the log to ensure that only generated entries will be found. db_delete('watchdog')->execute(); - // Generate watchdog entries. + // Generate 9 random watchdog entries. $type_names = array(); $types = array(); for ($i = 0; $i < 3; $i++) { @@ -432,10 +468,10 @@ class DBLogTestCase extends DrupalWebTestCase { } } - // View the dblog. + // View the database log page. $this->drupalGet('admin/reports/dblog'); - // Confirm all the entries are displayed. + // Confirm that all the entries are displayed. $count = $this->getTypeCount($types); foreach ($types as $key => $type) { $this->assertEqual($count[$key], $type['count'], 'Count matched'); @@ -461,8 +497,8 @@ class DBLogTestCase extends DrupalWebTestCase { $this->assertEqual(array_sum($count), $type_count, 'Count matched'); } - // Set filter to match each of the three type attributes and confirm the - // number of entries displayed. + // Set the filter to match each of the two filter-type attributes and + // confirm the correct number of entries are displayed. foreach ($types as $key => $type) { $edit = array( 'type[]' => array($type['type']), @@ -480,10 +516,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the log entry information form the page. + * Gets the database log event information from the browser page. * - * @return - * List of entries and their information. + * @return array + * List of log events where each event is an array with following keys: + * - severity: (int) A database log severity constant. + * - type: (string) The type of database log event. + * - message: (string) The message for this database log event. + * - user: (string) The user associated with this database log event. */ protected function getLogEntries() { $entries = array(); @@ -502,11 +542,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the count of entries per type. + * Gets the count of database log entries by database log event type. * - * @param $types + * @param array $types * The type information to compare against. - * @return + * + * @return array * The count of each type keyed by the key of the $types array. */ protected function getTypeCount(array $types) { @@ -524,11 +565,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the watchdog severity constant corresponding to the CSS class. + * Gets the watchdog severity constant corresponding to the CSS class. * - * @param $class + * @param string $class * CSS class attribute. - * @return + * + * @return int|null * The watchdog severity constant or NULL if not found. * * @ingroup logging_severity_levels @@ -557,11 +599,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Extract the text contained by the element. + * Extracts the text contained by the XHTML element. * - * @param $element + * @param SimpleXMLElement $element * Element to extract text from. - * @return + * + * @return string * Extracted text. */ protected function asText(SimpleXMLElement $element) { @@ -572,21 +615,22 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Assert messages appear on the log overview screen. + * Confirms that a log message appears on the database log overview screen. * - * This function should be used only for admin/reports/dblog page, because it - * check for the message link text truncated to 56 characters. Other dblog - * pages have no detail links so contains a full message text. + * This function should only be used for the admin/reports/dblog page, because + * it checks for the message link text truncated to 56 characters. Other log + * pages have no detail links so they contain the full message text. * - * @param $log_message - * The message to check. - * @param $message + * @param string $log_message + * The database log message to check. + * @param string $message * The message to pass to simpletest. */ protected function assertLogMessage($log_message, $message) { $message_text = truncate_utf8(filter_xss($log_message, array()), 56, TRUE, TRUE); - // After filter_xss() HTML entities should be converted to their characters - // because assertLink() uses this string in xpath() to query DOM. + // After filter_xss(), HTML entities should be converted to their character + // equivalents because assertLink() uses this string in xpath() to query the + // Document Object Model (DOM). $this->assertLink(html_entity_decode($message_text), 0, $message); } } diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 5f641173e79e52aebb2e194edc1ed2b4682e9627..822f537bc2ecdc8e21ad2d879db3a7b11e9109fd 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -873,7 +873,7 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); - return $element; + return array('value' => $element); } /** @@ -1735,11 +1735,14 @@ function hook_field_storage_details_alter(&$details, $field) { * loaded. */ function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 868d7bd756bc6f1aa4505a217f6449e46190d682..30a12d003c17df6213310ba9ebf54459d4f7cf8c 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -283,7 +283,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = 'language' => NULL, ); $options += $default_options; - $field_info = field_info_field_by_ids(); $fields = array(); $grouped_instances = array(); @@ -307,7 +306,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = foreach ($instances as $instance) { $field_id = $instance['field_id']; $field_name = $instance['field_name']; - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (function_exists($function)) { // Add the field to the list of fields to invoke the hook on. @@ -555,16 +554,23 @@ function _field_invoke_get_instances($entity_type, $bundle, $options) { * @param $langcode * The language the field values are going to be entered, if no language * is provided the default site language will be used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. * * @see field_form_get_state() * @see field_form_set_state() */ -function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL) { +function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Set #parents to 'top-level' by default. $form += array('#parents' => array()); // If no language is provided use the default site language. - $options = array('language' => field_valid_language($langcode)); + $options['language'] = field_valid_language($langcode); $form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options); // Add custom weight handling. @@ -614,7 +620,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod * non-deleted fields are operated on. */ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; // Merge default options. @@ -692,7 +697,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ } // Collect the storage backend if the field has not been loaded yet. if (!isset($skip_fields[$field_id])) { - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid; } } @@ -709,7 +714,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options); // Invoke hook_field_attach_load(): let other modules act on loading the - // entitiy. + // entity. module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options); // Build cache data. @@ -769,13 +774,21 @@ function field_attach_load_revision($entity_type, $entities, $options = array()) * If validation errors are found, a FieldValidationException is thrown. The * 'errors' property contains the array of errors, keyed by field name, * language and delta. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_validate($entity_type, $entity) { +function field_attach_validate($entity_type, $entity, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + $errors = array(); // Check generic, field-type-agnostic errors first. - _field_invoke_default('validate', $entity_type, $entity, $errors); + $null = NULL; + _field_invoke_default('validate', $entity_type, $entity, $errors, $null, $options); // Check field-type specific errors. - _field_invoke('validate', $entity_type, $entity, $errors); + _field_invoke('validate', $entity_type, $entity, $errors, $null, $options); // Let other modules validate the entity. // Avoid module_invoke_all() to let $errors be taken by reference. @@ -817,14 +830,21 @@ function field_attach_validate($entity_type, $entity) { * full form structure, or a sub-element of a larger form. * @param $form_state * An associative array containing the current state of the form. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_form_validate($entity_type, $entity, $form, &$form_state) { +function field_attach_form_validate($entity_type, $entity, $form, &$form_state, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Extract field values from submitted values. _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); // Perform field_level validation. try { - field_attach_validate($entity_type, $entity); + field_attach_validate($entity_type, $entity, $options); } catch (FieldValidationException $e) { // Pass field-level validation errors back to widgets for accurate error @@ -836,7 +856,7 @@ function field_attach_form_validate($entity_type, $entity, $form, &$form_state) field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state); } } - _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state); + _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state, $options); } } @@ -857,12 +877,19 @@ function field_attach_form_validate($entity_type, $entity, $form, &$form_state) * full form structure, or a sub-element of a larger form. * @param $form_state * An associative array containing the current state of the form. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_submit($entity_type, $entity, $form, &$form_state) { +function field_attach_submit($entity_type, $entity, $form, &$form_state, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Extract field values from submitted values. - _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); + _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state, $options); - _field_invoke_default('submit', $entity_type, $entity, $form, $form_state); + _field_invoke_default('submit', $entity_type, $entity, $form, $form_state, $options); // Let other modules act on submitting the entity. // Avoid module_invoke_all() to let $form_state be taken by reference. @@ -1093,9 +1120,16 @@ function field_attach_delete_revision($entity_type, $entity) { * @param $langcode * (Optional) The language the field values are to be shown in. If no language * is provided the current language is used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. */ -function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) { - $options = array('language' => array()); +function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + + $options['language'] = array(); // To ensure hooks are only run once per entity, only process items without // the _field_view_prepared flag. @@ -1167,14 +1201,21 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod * @param $langcode * The language the field values are to be shown in. If no language is * provided the current language is used. + * @param array $options + * An associative array of additional options. See _field_invoke() for + * details. * @return * A renderable array for the field values. */ -function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL) { +function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL, $options = array()) { + // Validate $options since this is a new parameter added after Drupal 7 was + // released. + $options = is_array($options) ? $options : array(); + // Determine the actual language to display for each field, given the // languages available in the field data. $display_language = field_language($entity_type, $entity, NULL, $langcode); - $options = array('language' => $display_language); + $options['language'] = $display_language; // Invoke field_default_view(). $null = NULL; diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index f9c96c92b020b98ab196a19d29fc8265d4776cdd..e1acdd53b948561dcda00194410ea7840f46f58d 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -319,7 +319,11 @@ function field_read_field($field_name, $include_additional = array()) { * Reads in fields that match an array of conditions. * * @param array $params - * An array of conditions to match against. + * An array of conditions to match against. Keys are columns from the + * 'field_config' table, values are conditions to match. Additionally, + * conditions on the 'entity_type' and 'bundle' columns from the + * 'field_config_instance' table are supported (select fields having an + * instance on a given bundle). * @param array $include_additional * The default behavior of this function is to not return fields that * are inactive or have been deleted. Setting @@ -337,8 +341,21 @@ function field_read_fields($params = array(), $include_additional = array()) { // Turn the conditions into a query. foreach ($params as $key => $value) { + // Allow filtering on the 'entity_type' and 'bundle' columns of the + // field_config_instance table. + if ($key == 'entity_type' || $key == 'bundle') { + if (empty($fci_join)) { + $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); + } + $key = 'fci.' . $key; + } + else { + $key = 'fc.' . $key; + } + $query->condition($key, $value); } + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { $query ->condition('fc.active', 1) diff --git a/modules/field/field.info b/modules/field/field.info index c194d6e57202ae2561ea97057cd3d7bf71cd69f6..280514eb2be7524bed4c8bdcb4bb68c68fe98e20 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -5,13 +5,14 @@ version = VERSION core = 7.x files[] = field.module files[] = field.attach.inc +files[] = field.info.class.inc files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc new file mode 100644 index 0000000000000000000000000000000000000000..3b89898fba874dec8b1becf23f5d7b0ed85399e1 --- /dev/null +++ b/modules/field/field.info.class.inc @@ -0,0 +1,668 @@ +<?php + +/* + * @file + * Definition of the FieldInfo class. + */ + +/** + * Provides field and instance definitions for the current runtime environment. + * + * A FieldInfo object is created and statically persisted through the request + * by the _field_info_field_cache() function. The object properties act as a + * "static cache" of fields and instances definitions. + * + * The preferred way to access definitions is through the getBundleInstances() + * method, which keeps cache entries per bundle, storing both fields and + * instances for a given bundle. Fields used in multiple bundles are duplicated + * in several cache entries, and are merged into a single list in the memory + * cache. Cache entries are loaded for bundles as a whole, optimizing memory + * and CPU usage for the most common pattern of iterating over all instances of + * a bundle rather than accessing a single instance. + * + * The getFields() and getInstances() methods, which return all existing field + * and instance definitions, are kept mainly for backwards compatibility, and + * should be avoided when possible, since they load and persist in memory a + * potentially large array of information. In many cases, the lightweight + * getFieldMap() method should be preferred. + */ +class FieldInfo { + + /** + * Lightweight map of fields across entity types and bundles. + * + * @var array + */ + protected $fieldMap; + + /** + * List of $field structures keyed by ID. Includes deleted fields. + * + * @var array + */ + protected $fieldsById = array(); + + /** + * Mapping of field names to the ID of the corresponding non-deleted field. + * + * @var array + */ + protected $fieldIdsByName = array(); + + /** + * Whether $fieldsById contains all field definitions or a subset. + * + * @var bool + */ + protected $loadedAllFields = FALSE; + + /** + * Separately tracks requested field names or IDs that do not exist. + * + * @var array + */ + protected $unknownFields = array(); + + /** + * Instance definitions by bundle. + * + * @var array + */ + protected $bundleInstances = array(); + + /** + * Whether $bundleInstances contains all instances definitions or a subset. + * + * @var bool + */ + protected $loadedAllInstances = FALSE; + + /** + * Separately tracks requested bundles that are empty (or do not exist). + * + * @var array + */ + protected $emptyBundles = array(); + + /** + * Extra fields by bundle. + * + * @var array + */ + protected $bundleExtraFields = array(); + + /** + * Clears the "static" and persistent caches. + */ + public function flush() { + $this->fieldMap = NULL; + + $this->fieldsById = array(); + $this->fieldIdsByName = array(); + $this->loadedAllFields = FALSE; + $this->unknownFields = array(); + + $this->bundleInstances = array(); + $this->loadedAllInstances = FALSE; + $this->emptyBundles = array(); + + $this->bundleExtraFields = array(); + + cache_clear_all('field_info:', 'cache_field', TRUE); + } + + /** + * Collects a lightweight map of fields across bundles. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with + * entity types as keys and the array of bundle names as values. + */ + public function getFieldMap() { + // Read from the "static" cache. + if ($this->fieldMap !== NULL) { + return $this->fieldMap; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:field_map', 'cache_field')) { + $map = $cached->data; + + // Save in "static" cache. + $this->fieldMap = $map; + + return $map; + } + + $map = array(); + + $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0'); + foreach ($query as $row) { + $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; + $map[$row->field_name]['type'] = $row->type; + } + + // Save in "static" and persistent caches. + $this->fieldMap = $map; + cache_set('field_info:field_map', $map, 'cache_field'); + + return $map; + } + + /** + * Returns all active fields, including deleted ones. + * + * @return + * An array of field definitions, keyed by field ID. + */ + public function getFields() { + // Read from the "static" cache. + if ($this->loadedAllFields) { + return $this->fieldsById; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:fields', 'cache_field')) { + $this->fieldsById = $cached->data; + } + else { + // Collect and prepare fields. + foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) { + $this->fieldsById[$field['id']] = $this->prepareField($field); + } + + // Store in persistent cache. + cache_set('field_info:fields', $this->fieldsById, 'cache_field'); + } + + // Fill the name/ID map. + foreach ($this->fieldsById as $field) { + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + $this->loadedAllFields = TRUE; + + return $this->fieldsById; + } + + /** + * Retrieves all active, non-deleted instances definitions. + * + * @param $entity_type + * (optional) The entity type. + * + * @return + * If $entity_type is not set, all instances keyed by entity type and bundle + * name. If $entity_type is set, all instances for that entity type, keyed + * by bundle name. + */ + public function getInstances($entity_type = NULL) { + // If the full list is not present in "static" cache yet. + if (!$this->loadedAllInstances) { + + // Read from persistent cache. + if ($cached = cache_get('field_info:instances', 'cache_field')) { + $this->bundleInstances = $cached->data; + } + else { + // Collect and prepare instances. + + // We also need to populate the static field cache, since it will not + // be set by subsequent getBundleInstances() calls. + $this->getFields(); + + // Initialize empty arrays for all existing entity types and bundles. + // This is not strictly needed, but is done to preserve the behavior of + // field_info_instances() before http://drupal.org/node/1915646. + foreach (field_info_bundles() as $existing_entity_type => $bundles) { + foreach ($bundles as $bundle => $bundle_info) { + $this->bundleInstances[$existing_entity_type][$bundle] = array(); + } + } + + foreach (field_read_instances() as $instance) { + $field = $this->getField($instance['field_name']); + $instance = $this->prepareInstance($instance, $field['type']); + $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; + } + + // Store in persistent cache. + cache_set('field_info:instances', $this->bundleInstances, 'cache_field'); + } + + $this->loadedAllInstances = TRUE; + } + + if (isset($entity_type)) { + return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array(); + } + else { + return $this->bundleInstances; + } + } + + /** + * Returns a field definition from a field name. + * + * This method only retrieves active, non-deleted fields. + * + * @param $field_name + * The field name. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getField($field_name) { + // Read from the "static" cache. + if (isset($this->fieldIdsByName[$field_name])) { + $field_id = $this->fieldIdsByName[$field_name]; + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_name])) { + return; + } + + // Do not check the (large) persistent cache, but read the definition. + + // Cache miss: read from definition. + if ($field = field_read_field($field_name)) { + $field = $this->prepareField($field); + + // Save in the "static" cache. + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + + return $field; + } + else { + $this->unknownFields[$field_name] = TRUE; + } + } + + /** + * Returns a field definition from a field ID. + * + * This method only retrieves active fields, deleted or not. + * + * @param $field_id + * The field ID. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getFieldById($field_id) { + // Read from the "static" cache. + if (isset($this->fieldsById[$field_id])) { + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_id])) { + return; + } + + // No persistent cache, fields are only persistently cached as part of a + // bundle. + + // Cache miss: read from definition. + if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) { + $field = current($fields); + $field = $this->prepareField($field); + + // Store in the static cache. + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + + return $field; + } + else { + $this->unknownFields[$field_id] = TRUE; + } + } + + /** + * Retrieves the instances for a bundle. + * + * The function also populates the corresponding field definitions in the + * "static" cache. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of instance definitions, keyed by field name. + */ + public function getBundleInstances($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleInstances[$entity_type][$bundle])) { + return $this->bundleInstances[$entity_type][$bundle]; + } + if (isset($this->emptyBundles[$entity_type][$bundle])) { + return array(); + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) { + $info = $cached->data; + + // Extract the field definitions and save them in the "static" cache. + foreach ($info['fields'] as $field) { + if (!isset($this->fieldsById[$field['id']])) { + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + } + unset($info['fields']); + + // Store the instance definitions in the "static" cache'. Empty (or + // non-existent) bundles are stored separately, so that they do not + // pollute the global list returned by getInstances(). + if ($info['instances']) { + $this->bundleInstances[$entity_type][$bundle] = $info['instances']; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + return $info['instances']; + } + + // Cache miss: collect from the definitions. + + $instances = array(); + + // Collect the fields in the bundle. + $params = array('entity_type' => $entity_type, 'bundle' => $bundle); + $fields = field_read_fields($params); + + // This iterates on non-deleted instances, so deleted fields are kept out of + // the persistent caches. + foreach (field_read_instances($params) as $instance) { + $field = $fields[$instance['field_name']]; + + $instance = $this->prepareInstance($instance, $field['type']); + $instances[$field['field_name']] = $instance; + + // If the field is not in our global "static" list yet, add it. + if (!isset($this->fieldsById[$field['id']])) { + $field = $this->prepareField($field); + + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + // Store in the 'static' cache'. Empty (or non-existent) bundles are stored + // separately, so that they do not pollute the global list returned by + // getInstances(). + if ($instances) { + $this->bundleInstances[$entity_type][$bundle] = $instances; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + // The persistent cache additionally contains the definitions of the fields + // involved in the bundle. + $cache = array( + 'instances' => $instances, + 'fields' => array() + ); + foreach ($instances as $instance) { + $cache['fields'][] = $this->fieldsById[$instance['field_id']]; + } + cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field'); + + return $instances; + } + + /** + * Retrieves the "extra fields" for a bundle. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of extra fields. + */ + public function getBundleExtraFields($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleExtraFields[$entity_type][$bundle])) { + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) { + $this->bundleExtraFields[$entity_type][$bundle] = $cached->data; + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Cache miss: read from hook_field_extra_fields(). Note: given the current + // shape of the hook, we have no other way than collecting extra fields on + // all bundles. + $info = array(); + $extra = module_invoke_all('field_extra_fields'); + drupal_alter('field_extra_fields', $extra); + // Merge in saved settings. + if (isset($extra[$entity_type][$bundle])) { + $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle); + } + + // Store in the 'static' and persistent caches. + $this->bundleExtraFields[$entity_type][$bundle] = $info; + cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field'); + + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + /** + * Prepares a field definition for the current run-time context. + * + * @param $field + * The raw field structure as read from the database. + * + * @return + * The field definition completed for the current runtime context. + */ + public function prepareField($field) { + // Make sure all expected field settings are present. + $field['settings'] += field_info_field_settings($field['type']); + $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); + + // Add storage details. + $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); + drupal_alter('field_storage_details', $details, $field); + $field['storage']['details'] = $details; + + // Populate the list of bundles using the field. + $field['bundles'] = array(); + if (!$field['deleted']) { + $map = $this->getFieldMap(); + if (isset($map[$field['field_name']])) { + $field['bundles'] = $map[$field['field_name']]['bundles']; + } + } + + return $field; + } + + /** + * Prepares an instance definition for the current run-time context. + * + * @param $instance + * The raw instance structure as read from the database. + * @param $field_type + * The field type. + * + * @return + * The field instance array completed for the current runtime context. + */ + public function prepareInstance($instance, $field_type) { + // Make sure all expected instance settings are present. + $instance['settings'] += field_info_instance_settings($field_type); + + // Set a default value for the instance. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { + $instance['default_value'] = NULL; + } + + // Prepare widget settings. + $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type); + + // Prepare display settings. + foreach ($instance['display'] as $view_mode => $display) { + $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type); + } + + // Fall back to 'hidden' for view modes configured to use custom display + // settings, and for which the instance has no explicit settings. + $entity_info = entity_get_info($instance['entity_type']); + $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); + $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); + foreach ($view_modes as $view_mode) { + if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { + if (!isset($instance['display'][$view_mode])) { + $instance['display'][$view_mode] = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + } + } + } + + return $instance; + } + + /** + * Prepares widget properties for the current run-time context. + * + * @param $widget + * Widget specifications as found in $instance['widget']. + * @param $field_type + * The field type. + * + * @return + * The widget properties completed for the current runtime context. + */ + public function prepareInstanceWidget($widget, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $widget += array( + 'type' => $field_type_info['default_widget'], + 'settings' => array(), + 'weight' => 0, + ); + + $widget_type_info = field_info_widget_types($widget['type']); + // Fall back to default formatter if formatter type is not available. + if (!$widget_type_info) { + $widget['type'] = $field_type_info['default_widget']; + $widget_type_info = field_info_widget_types($widget['type']); + } + $widget['module'] = $widget_type_info['module']; + // Fill in default settings for the widget. + $widget['settings'] += field_info_widget_settings($widget['type']); + + return $widget; + } + + /** + * Adapts display specifications to the current run-time context. + * + * @param $display + * Display specifications as found in $instance['display']['a_view_mode']. + * @param $field_type + * The field type. + * + * @return + * The display properties completed for the current runtime context. + */ + public function prepareInstanceDisplay($display, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $display += array( + 'label' => 'above', + 'type' => $field_type_info['default_formatter'], + 'settings' => array(), + 'weight' => 0, + ); + if ($display['type'] != 'hidden') { + $formatter_type_info = field_info_formatter_types($display['type']); + // Fall back to default formatter if formatter type is not available. + if (!$formatter_type_info) { + $display['type'] = $field_type_info['default_formatter']; + $formatter_type_info = field_info_formatter_types($display['type']); + } + $display['module'] = $formatter_type_info['module']; + // Fill in default settings for the formatter. + $display['settings'] += field_info_formatter_settings($display['type']); + } + + return $display; + } + + /** + * Prepares 'extra fields' for the current run-time context. + * + * @param $extra_fields + * The array of extra fields, as collected in hook_field_extra_fields(). + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The list of extra fields completed for the current runtime context. + */ + public function prepareExtraFields($extra_fields, $entity_type, $bundle) { + $entity_type_info = entity_get_info($entity_type); + $bundle_settings = field_bundle_settings($entity_type, $bundle); + $extra_fields += array('form' => array(), 'display' => array()); + + $result = array(); + // Extra fields in forms. + foreach ($extra_fields['form'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); + if (isset($settings['weight'])) { + $field_data['weight'] = $settings['weight']; + } + $result['form'][$name] = $field_data; + } + + // Extra fields in displayed entities. + $data = $extra_fields['display']; + foreach ($extra_fields['display'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); + $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); + foreach ($view_modes as $view_mode) { + if (isset($settings[$view_mode])) { + $field_data['display'][$view_mode] = $settings[$view_mode]; + } + else { + $field_data['display'][$view_mode] = array( + 'weight' => $field_data['weight'], + 'visible' => TRUE, + ); + } + } + unset($field_data['weight']); + $result['display'][$name] = $field_data; + } + + return $result; + } +} diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index 9e7ab938d5e11a470228b956e41e4e5147930368..396ad5fd19508d52e4a7cdc82be1624050179edc 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -5,6 +5,32 @@ * Field Info API, providing information about available fields and field types. */ +/** + * Retrieves the FieldInfo object for the current request. + * + * @return FieldInfo + * An instance of the FieldInfo class. + */ +function _field_info_field_cache() { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + + if (!isset($drupal_static_fast)) { + $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__); + } + $field_info = &$drupal_static_fast['field_info_field_cache']; + + if (!isset($field_info)) { + // @todo The registry should save the need for an explicit include, but not + // a couple upgrade tests (DisabledNodeTypeTestCase, + // FilterFormatUpgradePathTestCase...) break in a strange way without it. + include_once dirname(__FILE__) . '/field.info.class.inc'; + $field_info = new FieldInfo(); + } + + return $field_info; +} + /** * @defgroup field_info Field Info API * @{ @@ -34,7 +60,50 @@ function field_info_cache_clear() { entity_info_cache_clear(); _field_info_collate_types(TRUE); - _field_info_collate_fields(TRUE); + _field_info_field_cache()->flush(); +} + +/** + * Collates all information on existing fields and instances. + * + * Deprecated. This function is kept to ensure backwards compatibility, but has + * a serious performance impact, and should be absolutely avoided. + * See http://drupal.org/node/1915646. + * + * Use the regular field_info_*() API functions to access the information, or + * field_info_cache_clear() to clear the cached data. + */ +function _field_info_collate_fields($reset = FALSE) { + if ($reset) { + _field_info_field_cache()->flush(); + return; + } + + $cache = _field_info_field_cache(); + + // Collect fields, and build the array of IDs keyed by field_name. + $fields = $cache->getFields(); + $field_ids = array(); + foreach ($fields as $id => $field) { + if (!$field['deleted']) { + $field_ids[$field['field_name']] = $id; + } + } + + // Collect extra fields for all entity types. + $extra_fields = array(); + foreach (field_info_bundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle => $info) { + $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle); + } + } + + return array( + 'fields' => $fields, + 'field_ids' => $field_ids, + 'instances' => $cache->getInstances(), + 'extra_fields' => $extra_fields, + ); } /** @@ -161,282 +230,69 @@ function _field_info_collate_types($reset = FALSE) { return $info; } -/** - * Collates all information on existing fields and instances. - * - * @param $reset - * If TRUE, clear the cache. The information will be rebuilt from the - * database next time it is needed. Defaults to FALSE. - * - * @return - * If $reset is TRUE, nothing. - * If $reset is FALSE, an array containing the following elements: - * - fields: Array of existing fields, keyed by field ID. This element - * lists deleted and non-deleted fields, but not inactive ones. - * Each field has an additional element, 'bundles', which is an array - * of all non-deleted instances of that field. - * - field_ids: Array of field IDs, keyed by field name. This element - * only lists non-deleted, active fields. - * - instances: Array of existing instances, keyed by entity type, bundle - * name and field name. This element only lists non-deleted instances - * whose field is active. - */ -function _field_info_collate_fields($reset = FALSE) { - static $info; - - if ($reset) { - $info = NULL; - cache_clear_all('field_info_fields', 'cache_field'); - return; - } - - if (!isset($info)) { - if ($cached = cache_get('field_info_fields', 'cache_field')) { - $info = $cached->data; - } - else { - $definitions = array( - 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), - 'instances' => field_read_instances(), - ); - - // Populate 'fields' with all fields, keyed by ID. - $info['fields'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); - } - - // Build an array of field IDs for non-deleted fields, keyed by name. - $info['field_ids'] = array(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $info['field_ids'][$field['field_name']] = $key; - } - } - - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); - } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_id']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - } - - // Populate 'extra_fields'. - $extra = module_invoke_all('field_extra_fields'); - drupal_alter('field_extra_fields', $extra); - // Merge in saved settings. - foreach ($extra as $entity_type => $bundles) { - foreach ($bundles as $bundle => $extra_fields) { - $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle); - $info['extra_fields'][$entity_type][$bundle] = $extra_fields; - } - } - - cache_set('field_info_fields', $info, 'cache_field'); - } - } - - return $info; -} - /** * Prepares a field definition for the current run-time context. * - * Since the field was last saved or updated, new field settings can be - * expected. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. * - * @param $field - * The raw field structure as read from the database. + * @see FieldInfo::prepareField() */ function _field_info_prepare_field($field) { - // Make sure all expected field settings are present. - $field['settings'] += field_info_field_settings($field['type']); - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field, $instance); - $field['storage']['details'] = $details; - - // Initialize the 'bundles' list. - $field['bundles'] = array(); - - return $field; + $cache = _field_info_field_cache(); + return $cache->prepareField($field); } /** * Prepares an instance definition for the current run-time context. * - * Since the instance was last saved or updated, a number of things might have - * changed: widgets or formatters disabled, new settings expected, new view - * modes added... - * - * @param $instance - * The raw instance structure as read from the database. - * @param $field - * The field structure for the instance. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. * - * @return - * Field instance array. + * @see FieldInfo::prepareInstance() */ function _field_info_prepare_instance($instance, $field) { - // Make sure all expected instance settings are present. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set a default value for the instance. - if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { - $instance['default_value'] = NULL; - } - - $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']); - - foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); - } - - // Fallback to 'hidden' for view modes configured to use custom display - // settings, and for which the instance has no explicit settings. - $entity_info = entity_get_info($instance['entity_type']); - $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - foreach ($view_modes as $view_mode) { - if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { - if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = array( - 'type' => 'hidden', - 'label' => 'above', - 'settings' => array(), - 'weight' => 0, - ); - } - } - } - - return $instance; + $cache = _field_info_field_cache(); + return $cache->prepareInstance($instance, $field['type']); } /** * Adapts display specifications to the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $display - * Display specifications as found in - * $instance['display']['some_view_mode']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareInstanceDisplay() */ function _field_info_prepare_instance_display($field, $display) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $display += array( - 'label' => 'above', - 'type' => $field_type['default_formatter'], - 'settings' => array(), - 'weight' => 0, - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - // Fallback to default formatter if formatter type is not available. - if (!$formatter_type) { - $display['type'] = $field_type['default_formatter']; - $formatter_type = field_info_formatter_types($display['type']); - } - $display['module'] = $formatter_type['module']; - // Fill in default settings for the formatter. - $display['settings'] += field_info_formatter_settings($display['type']); - } - - return $display; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceDisplay($display, $field['type']); } /** * Prepares widget specifications for the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $widget - * Widget specifications as found in $instance['widget']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareInstanceWidget() */ function _field_info_prepare_instance_widget($field, $widget) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $widget += array( - 'type' => $field_type['default_widget'], - 'settings' => array(), - 'weight' => 0, - ); - - $widget_type = field_info_widget_types($widget['type']); - // Fallback to default formatter if formatter type is not available. - if (!$widget_type) { - $widget['type'] = $field_type['default_widget']; - $widget_type = field_info_widget_types($widget['type']); - } - $widget['module'] = $widget_type['module']; - // Fill in default settings for the widget. - $widget['settings'] += field_info_widget_settings($widget['type']); - - return $widget; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceWidget($widget, $field['type']); } /** * Prepares 'extra fields' for the current run-time context. * - * @param $extra_fields - * The array of extra fields, as collected in hook_field_extra_fields(). - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1915646. + * + * @see FieldInfo::prepareExtraFields() */ function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) { - $entity_type_info = entity_get_info($entity_type); - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $extra_fields += array('form' => array(), 'display' => array()); - - $result = array(); - // Extra fields in forms. - foreach ($extra_fields['form'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); - if (isset($settings['weight'])) { - $field_data['weight'] = $settings['weight']; - } - $result['form'][$name] = $field_data; - } - - // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => TRUE, - ); - } - } - unset($field_data['weight']); - $result['display'][$name] = $field_data; - } - - return $result; + $cache = _field_info_field_cache(); + return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle); } /** @@ -583,22 +439,51 @@ function field_info_bundles($entity_type = NULL) { return $bundles; } +/** + * Returns a lightweight map of fields across bundles. + * + * The function only returns active, non deleted fields. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with entity + * types as keys and the array of bundle names as values. + */ +function field_info_field_map() { + $cache = _field_info_field_cache(); + return $cache->getFieldMap(); +} + /** * Returns all field definitions. * + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. + * * @return * An array of field definitions, keyed by field name. Each field has an * additional property, 'bundles', which is an array of all the bundles to * which this field belongs keyed by entity type. + * + * @see field_info_field_map() */ function field_info_fields() { + $cache = _field_info_field_cache(); + $info = $cache->getFields(); + $fields = array(); - $info = _field_info_collate_fields(); - foreach ($info['fields'] as $key => $field) { + foreach ($info as $key => $field) { if (!$field['deleted']) { $fields[$field['field_name']] = $field; } } + return $fields; } @@ -620,10 +505,8 @@ function field_info_fields() { * @see field_info_field_by_id() */ function field_info_field($field_name) { - $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_name])) { - return $info['fields'][$info['field_ids'][$field_name]]; - } + $cache = _field_info_field_cache(); + return $cache->getField($field_name); } /** @@ -641,17 +524,19 @@ function field_info_field($field_name) { * @see field_info_field() */ function field_info_field_by_id($field_id) { - $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_id])) { - return $info['fields'][$field_id]; - } + $cache = _field_info_field_cache(); + return $cache->getFieldById($field_id); } /** * Returns the same data as field_info_field_by_id() for every field. * - * This function is typically used when handling all fields of some entities - * to avoid thousands of calls to field_info_field_by_id(). + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. * * @return * An array, each key is a field ID and the values are field arrays as @@ -662,41 +547,57 @@ function field_info_field_by_id($field_id) { * @see field_info_field_by_id() */ function field_info_field_by_ids() { - $info = _field_info_collate_fields(); - return $info['fields']; + $cache = _field_info_field_cache(); + return $cache->getFields(); } /** * Retrieves information about field instances. * + * Use of this function to retrieve instances across separate bundles (i.e. + * when the $bundle parameter is NULL) should be avoided when possible, since + * it loads and statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When retrieving the instances of a specific bundle (i.e. when both + * $entity_type and $bundle_name are provided), the function also populates a + * static cache with the corresponding field definitions, allowing fast + * retrieval of field_info_field() later in the request. + * * @param $entity_type - * The entity type for which to return instances. + * (optional) The entity type for which to return instances. * @param $bundle_name - * The bundle name for which to return instances. + * (optional) The bundle name for which to return instances. If $entity_type + * is NULL, the $bundle_name parameter is ignored. * * @return * If $entity_type is not set, return all instances keyed by entity type and * bundle name. If $entity_type is set, return all instances for that entity * type, keyed by bundle name. If $entity_type and $bundle_name are set, return * all instances for that bundle. + * + * @see field_info_field_map() */ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { - $info = _field_info_collate_fields(); + $cache = _field_info_field_cache(); - if (isset($entity_type) && isset($bundle_name)) { - return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array(); + if (!isset($entity_type)) { + return $cache->getInstances(); } - elseif (isset($entity_type)) { - return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array(); - } - else { - return $info['instances']; + if (!isset($bundle_name)) { + return $cache->getInstances($entity_type); } + + return $cache->getBundleInstances($entity_type, $bundle_name); } /** * Returns an array of instance data for a specific field and bundle. * + * The function populates a static cache with all fields and instances used in + * the bundle, allowing fast retrieval of field_info_field() or + * field_info_instance() later in the request. + * * @param $entity_type * The entity type for the instance. * @param $field_name @@ -709,9 +610,10 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { * NULL if the instance does not exist. */ function field_info_instance($entity_type, $field_name, $bundle_name) { - $info = _field_info_collate_fields(); - if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) { - return $info['instances'][$entity_type][$bundle_name][$field_name]; + $cache = _field_info_field_cache(); + $info = $cache->getBundleInstances($entity_type, $bundle_name); + if (isset($info[$field_name])) { + return $info[$field_name]; } } @@ -769,11 +671,10 @@ function field_info_instance($entity_type, $field_name, $bundle_name) { * The array of pseudo-field elements in the bundle. */ function field_info_extra_fields($entity_type, $bundle, $context) { - $info = _field_info_collate_fields(); - if (isset($info['extra_fields'][$entity_type][$bundle][$context])) { - return $info['extra_fields'][$entity_type][$bundle][$context]; - } - return array(); + $cache = _field_info_field_cache(); + $info = $cache->getBundleExtraFields($entity_type, $bundle); + + return isset($info[$context]) ? $info[$context] : array(); } /** diff --git a/modules/field/field.install b/modules/field/field.install index 34d28073d59ddf7c6544b30cd3fd9b9f441b59d4..a4b1534812cd730e596dd14daacd56ecc40b1921 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -459,6 +459,13 @@ function field_update_7002() { } } +/** + * Add the FieldInfo class to the class registry. + */ +function field_update_7003() { + // Empty update to force a rebuild of the registry. +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/field/field.module b/modules/field/field.module index b6cf05c9b691c0011a3d45337369430481cf61d5..4331cdf427262a48fb4e8277e1d5e1fee85451a7 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -873,7 +873,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array() if ($field = field_info_field($field_name)) { if (is_array($display)) { // When using custom display settings, fill in default values. - $display = _field_info_prepare_instance_display($field, $display); + $cache = _field_info_field_cache(); + $display = $cache->prepareInstanceDisplay($display, $field["type"]); } // Hook invocations are done through the _field_invoke() functions in @@ -1197,7 +1198,7 @@ function _element_validate_integer($element, &$form_state) { * Use element_validate_integer_positive() instead. * * @deprecated - * @see element_validate_number_positive() + * @see element_validate_integer_positive() */ function _element_validate_integer_positive($element, &$form_state) { element_validate_integer_positive($element, $form_state); diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info index 3dec0c44fd1471b0665f0372e469d6fd02a802a1..36c529f6a8c9a464f49a6e56de0f7427c403ae36 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index a756194273128bde83b98666e9315498cb1f2ab2..c639f38f2c0029a161a7e12d2359e7dd4a1dc95f 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -324,11 +324,14 @@ function field_sql_storage_field_storage_delete_field($field) { * Implements hook_field_storage_load(). */ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index ee82df0eb058bec6afd3f44a69cfb282a75d490a..3cc21c1fe08755c9e63bcdef1b52c26adae92f1e 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -7,8 +7,8 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index 4020c37ba2c16e317afffa87efb889e05e475bad..9587a74f0561cb4310c71895b94c0cc749913f0f 100644 --- a/modules/field/modules/list/tests/list_test.info +++ b/modules/field/modules/list/tests/list_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index efc5403795849c137a702f4ed3144bdf694beb7b..9aa713506afe105f733a244e63685b70efc82ab6 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 61cd19de7cbcf38a55bcd58b1f838b792d6e251a..904945d53a650ad6e46e93f9915c4b740afdb86c 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index 15f87cebdd7fefdf96d4d0c3ac170646b1ad5fc3..64f88c2320fec3f041580220494599f30481f5c3 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index d73814faaafa007f2f13499616ecd17edcf24b7f..9ba0051fce762218f9e34d1309fc776c0ac95b04 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -284,7 +284,9 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la case 'text_plain': foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => strip_tags($item['value'])); + // The text value has no text format assigned to it, so the user input + // should equal the output, including newlines. + $element[$delta] = array('#markup' => nl2br(check_plain($item['value']))); } break; } @@ -316,7 +318,12 @@ function _text_sanitize($instance, $langcode, $item, $column) { if (isset($item["safe_$column"])) { return $item["safe_$column"]; } - return $instance['settings']['text_processing'] ? check_markup($item[$column], $item['format'], $langcode) : check_plain($item[$column]); + if ($instance['settings']['text_processing']) { + return check_markup($item[$column], $item['format'], $langcode); + } + // Escape all HTML and retain newlines. + // @see text_field_formatter_view() + return nl2br(check_plain($item[$column])); } /** diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 8004178ebf28a13ae8a5247f4a30667c83b59fd3..2852f67df775dcf9a5b9269c04b31d67b7f66bb0 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -85,12 +85,28 @@ class FieldAttachTestCase extends FieldTestCase { } parent::setUp($modules); - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->field_id = $this->field['id']; - $this->instance = array( - 'field_name' => $this->field_name, + $this->createFieldWithInstance(); + } + + /** + * Create a field and an instance of it. + * + * @param string $suffix + * (optional) A string that should only contain characters that are valid in + * PHP variable names as well. + */ + function createFieldWithInstance($suffix = '') { + $field_name = 'field_name' . $suffix; + $field = 'field' . $suffix; + $field_id = 'field_id' . $suffix; + $instance = 'instance' . $suffix; + + $this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix); + $this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4); + $this->$field = field_create_field($this->$field); + $this->$field_id = $this->{$field}['id']; + $this->$instance = array( + 'field_name' => $this->$field_name, 'entity_type' => 'test_entity', 'bundle' => 'test_bundle', 'label' => $this->randomName() . '_label', @@ -107,7 +123,7 @@ class FieldAttachTestCase extends FieldTestCase { ) ) ); - field_create_instance($this->instance); + field_create_instance($this->$instance); } } @@ -641,13 +657,18 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { * Test field_attach_view() and field_attach_prepare_view(). */ function testFieldAttachView() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity_init = field_test_create_stub_entity(); $langcode = LANGUAGE_NONE; + $options = array('field_name' => $this->field_name_2); // Populate values to be displayed. $values = $this->_generateTestFieldValues($this->field['cardinality']); $entity_init->{$this->field_name}[$langcode] = $values; + $values_2 = $this->_generateTestFieldValues($this->field_2['cardinality']); + $entity_init->{$this->field_name_2}[$langcode] = $values_2; // Simple formatter, label displayed. $entity = clone($entity_init); @@ -662,15 +683,47 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { ), ); field_update_instance($this->instance); + $formatter_setting_2 = $this->randomName(); + $this->instance_2['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $formatter_setting_2, + ) + ), + ); + field_update_instance($this->instance_2); + // View all fields. field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; - $this->assertRaw($this->instance['label'], "Label is displayed."); + $this->assertRaw($this->instance['label'], "First field's label is displayed."); foreach ($values as $delta => $value) { $this->content = $output; $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); } + $this->assertRaw($this->instance_2['label'], "Second field's label is displayed."); + foreach ($values_2 as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } + // View single field (the second field). + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full', $langcode, $options); + $entity->content = field_attach_view($entity_type, $entity, 'full', $langcode, $options); + $output = drupal_render($entity->content); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "First field's label is not displayed."); + foreach ($values as $delta => $value) { + $this->content = $output; + $this->assertNoRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } + $this->assertRaw($this->instance_2['label'], "Second field's label is displayed."); + foreach ($values_2 as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } // Label hidden. $entity = clone($entity_init); @@ -697,7 +750,7 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { $this->content = $output; $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); foreach ($values as $delta => $value) { - $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); + $this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed."); } // Multiple formatter. @@ -907,11 +960,13 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { * hook_field_validate. */ function testFieldAttachValidate() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); $langcode = LANGUAGE_NONE; - // Set up values to generate errors + // Set up all but one values of the first field to generate errors. $values = array(); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { $values[$delta]['value'] = -1; @@ -920,6 +975,14 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { $values[1]['value'] = 1; $entity->{$this->field_name}[$langcode] = $values; + // Set up all values of the second field to generate errors. + $values_2 = array(); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + $values_2[$delta]['value'] = -1; + } + $entity->{$this->field_name_2}[$langcode] = $values_2; + + // Validate all fields. try { field_attach_validate($entity_type, $entity); } @@ -929,26 +992,57 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { foreach ($values as $delta => $value) { if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta"); + $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on first field's value $delta"); + $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on first field's value $delta"); unset($errors[$this->field_name][$langcode][$delta]); } else { - $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta"); + $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on first field's value $delta"); } } - $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set'); + foreach ($values_2 as $delta => $value) { + $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta"); + $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta"); + unset($errors[$this->field_name_2][$langcode][$delta]); + } + $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set for first field'); + $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field'); + + // Validate a single field. + $options = array('field_name' => $this->field_name_2); + try { + field_attach_validate($entity_type, $entity, $options); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + + foreach ($values_2 as $delta => $value) { + $this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta"); + $this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta"); + unset($errors[$this->field_name_2][$langcode][$delta]); + } + $this->assertFalse(isset($errors[$this->field_name]), 'No validation errors are set for the first field, despite it having errors'); + $this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field'); // Check that cardinality is validated. - $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); + $entity->{$this->field_name_2}[$langcode] = $this->_generateTestFieldValues($this->field_2['cardinality'] + 1); + // When validating all fields. try { field_attach_validate($entity_type, $entity); } catch (FieldValidationException $e) { $errors = $e->errors; } - $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.')); - + $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.'); + // When validating a single field (the second field). + try { + field_attach_validate($entity_type, $entity, $options); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + $this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.'); } /** @@ -958,34 +1052,59 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { * widgets show up. */ function testFieldAttachForm() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + // When generating form for all fields. $form = array(); $form_state = form_state_defaults(); field_attach_form($entity_type, $entity, $form, $form_state); - $langcode = LANGUAGE_NONE; - $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); + $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "First field's form title is {$this->instance['label']}"); + $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}"); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // field_test_widget uses 'textfield' - $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); - } + $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "First field's form delta $delta widget is textfield"); + } + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield"); + } + + // When generating form for a single field (the second field). + $options = array('field_name' => $this->field_name_2); + $form = array(); + $form_state = form_state_defaults(); + field_attach_form($entity_type, $entity, $form, $form_state, NULL, $options); + + $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form'); + $this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}"); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield"); + } } /** * Test field_attach_submit(). */ function testFieldAttachSubmit() { + $this->createFieldWithInstance('_2'); + $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $entity_init = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; - // Build the form. + // Build the form for all fields. $form = array(); $form_state = form_state_defaults(); - field_attach_form($entity_type, $entity, $form, $form_state); + field_attach_form($entity_type, $entity_init, $form, $form_state); // Simulate incoming values. + // First field. $values = array(); $weights = array(); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { @@ -999,22 +1118,59 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { } // Leave an empty value. 'field_test' fields are empty if empty(). $values[1]['value'] = 0; - - $langcode = LANGUAGE_NONE; + // Second field. + $values_2 = array(); + $weights_2 = array(); + for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) { + $values_2[$delta]['value'] = mt_rand(1, 127); + // Assign random weight. + do { + $weight = mt_rand(0, $this->field_2['cardinality']); + } while (in_array($weight, $weights_2)); + $weights_2[$delta] = $weight; + $values_2[$delta]['_weight'] = $weight; + } + // Leave an empty value. 'field_test' fields are empty if empty(). + $values_2[1]['value'] = 0; // Pretend the form has been built. drupal_prepare_form('field_test_entity_form', $form, $form_state); drupal_process_form('field_test_entity_form', $form, $form_state); $form_state['values'][$this->field_name][$langcode] = $values; + $form_state['values'][$this->field_name_2][$langcode] = $values_2; + + // Call field_attach_submit() for all fields. + $entity = clone($entity_init); field_attach_submit($entity_type, $entity, $form, $form_state); asort($weights); + asort($weights_2); $expected_values = array(); + $expected_values_2 = array(); foreach ($weights as $key => $value) { if ($key != 1) { $expected_values[] = array('value' => $values[$key]['value']); } } $this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values'); + foreach ($weights_2 as $key => $value) { + if ($key != 1) { + $expected_values_2[] = array('value' => $values_2[$key]['value']); + } + } + $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values'); + + // Call field_attach_submit() for a single field (the second field). + $options = array('field_name' => $this->field_name_2); + $entity = clone($entity_init); + field_attach_submit($entity_type, $entity, $form, $form_state, $options); + $expected_values_2 = array(); + foreach ($weights_2 as $key => $value) { + if ($key != 1) { + $expected_values_2[] = array('value' => $values_2[$key]['value']); + } + } + $this->assertFalse(isset($entity->{$this->field_name}), 'The first field does not exist in the entity object'); + $this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values'); } } @@ -1144,6 +1300,16 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.'); $instances = field_info_instances('user', 'user'); $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array."); + + // Test that querying for invalid entity types does not add entries in the + // list returned by field_info_instances(). + field_info_cache_clear(); + field_info_instances('invalid_entity', 'invalid_bundle'); + // Simulate new request by clearing static caches. + drupal_static_reset(); + field_info_instances('invalid_entity', 'invalid_bundle'); + $instances = field_info_instances(); + $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before'); } /** @@ -1253,6 +1419,80 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.')); } + /** + * Test field_info_field_map(). + */ + function testFieldMap() { + // We will overlook fields created by the 'standard' install profile. + $exclude = field_info_field_map(); + + // Create a new bundle for 'test_entity' entity type. + field_test_create_bundle('test_bundle_2'); + + // Create a couple fields. + $fields = array( + array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ), + array( + 'field_name' => 'field_2', + 'type' => 'hidden_test_field', + ), + ); + foreach ($fields as $field) { + field_create_field($field); + } + + // Create a couple instances. + $instances = array( + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle_2', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_cacheable_entity', + 'bundle' => 'test_bundle', + ), + ); + foreach ($instances as $instance) { + field_create_instance($instance); + } + + $expected = array( + 'field_1' => array( + 'type' => 'test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle', 'test_bundle_2'), + ), + ), + 'field_2' => array( + 'type' => 'hidden_test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle'), + 'test_cacheable_entity' => array('test_bundle'), + ), + ), + ); + + // Check that the field map is correct. + $map = field_info_field_map(); + $map = array_diff_key($map, $exclude); + $this->assertEqual($map, $expected); + } + /** * Test that the field_info settings convenience functions work. */ @@ -1277,6 +1517,31 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); } } + + /** + * Tests that the field info cache can be built correctly. + */ + function testFieldInfoCache() { + // Create a test field and ensure it's in the array returned by + // field_info_fields(). + $field_name = drupal_strtolower($this->randomName()); + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().'); + + // Now rebuild the field info cache, and set a variable which will cause + // the cache to be cleared while it's being rebuilt; see + // field_test_entity_info(). Ensure the test field is still in the returned + // array. + field_info_cache_clear(); + variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.'); + } } class FieldFormTestCase extends FieldTestCase { @@ -2178,6 +2443,41 @@ class FieldCrudTestCase extends FieldTestCase { $this->assertTrue($field_definition < $field, t('The field was properly read.')); } + /** + * Tests reading field definitions. + */ + function testReadFields() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Check that 'single column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + + // Check that 'multi column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo')); + $this->assertTrue(empty($fields), 'No field was found.'); + + // Create an instance of the field. + $instance_definition = array( + 'field_name' => $field_definition['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance_definition); + + // Check that criteria spanning over the field_config_instance table work. + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + } + /** * Test creation of indexes on data column. */ diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc index 95af3eeba175ba86268cfdc489e57deb88bb98a1..c6686ebc2a62dd731447d5cef8fb07b160c2aaa8 100644 --- a/modules/field/tests/field_test.entity.inc +++ b/modules/field/tests/field_test.entity.inc @@ -9,6 +9,12 @@ * Implements hook_entity_info(). */ function field_test_entity_info() { + // If requested, clear the field cache while this hook is being called. See + // testFieldInfoCache(). + if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) { + field_info_cache_clear(); + } + $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); $test_entity_modes = array( 'full' => array( diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index 81010dc1dd4768f1f1bb1794b87b4d0eeaa09148..2df6be5be0eac61c740c42177ff69398246a50a3 100644 --- a/modules/field/tests/field_test.info +++ b/modules/field/tests/field_test.info @@ -6,8 +6,8 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 44770acb9a680f97e7db97f8cb12a240fe73f91d..43bb20276e3bc8fecf6eb078378947a5a41e4b18 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -1534,7 +1534,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { // - locked fields, // - fields already in the current bundle, // - fields that cannot be added to the entity type, - // - fields that that shoud not be added via user interface. + // - fields that should not be added via user interface. if (empty($field['locked']) && !field_info_instance($entity_type, $field['field_name'], $bundle) @@ -1544,7 +1544,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { 'type' => $field['type'], 'type_label' => $field_types[$field['type']]['label'], 'field' => $field['field_name'], - 'label' => t($instance['label']), + 'label' => $instance['label'], 'widget_type' => $instance['widget']['type'], ); } diff --git a/modules/field_ui/field_ui.info b/modules/field_ui/field_ui.info index 4497bcb08140dfae71edde54b0719d6d32c34189..bf5d35913b359e699b7f5b794bbab705439e089b 100644 --- a/modules/field_ui/field_ui.info +++ b/modules/field_ui/field_ui.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = field_ui.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module index 93cbcccc74231243b998d0461c49ba14846b5fe6..5f8bc45ef2a8b702fc649c85541f132c59da0fd4 100644 --- a/modules/field_ui/field_ui.module +++ b/modules/field_ui/field_ui.module @@ -332,23 +332,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) { * Identifies inactive fields within a bundle. */ function field_ui_inactive_instances($entity_type, $bundle_name = NULL) { - if (!empty($bundle_name)) { - $inactive = array($bundle_name => array()); - $params = array('bundle' => $bundle_name); + $params = array('entity_type' => $entity_type); + + if (empty($bundle_name)) { + $active = field_info_instances($entity_type); + $inactive = array(); } else { - $inactive = array(); - $params = array(); + // Restrict to the specified bundle. For consistency with the case where + // $bundle_name is NULL, the $active and $inactive arrays are keyed by + // bundle name first. + $params['bundle'] = $bundle_name; + $active = array($bundle_name => field_info_instances($entity_type, $bundle_name)); + $inactive = array($bundle_name => array()); } - $params['entity_type'] = $entity_type; - $active_instances = field_info_instances($entity_type); + // Iterate on existing definitions, and spot those that do not appear in the + // $active list collected earlier. $all_instances = field_read_instances($params, array('include_inactive' => TRUE)); foreach ($all_instances as $instance) { - if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) { + if (!isset($active[$instance['bundle']][$instance['field_name']])) { $inactive[$instance['bundle']][$instance['field_name']] = $instance; } } + if (!empty($bundle_name)) { return $inactive[$bundle_name]; } diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test index d0a822a8243dcffb36ef4448b5fceab0ce256957..b67b70e2a4fde6a68bd0c7f4dc483956cad02edf 100644 --- a/modules/field_ui/field_ui.test +++ b/modules/field_ui/field_ui.test @@ -269,7 +269,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { */ function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') { // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Assert field settings. $field = field_info_field($field_name); $this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.')); @@ -360,7 +360,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.')); // Check that the field was not deleted @@ -370,7 +370,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.')); // Check that the field was deleted too. diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc index 1189704fd7e3bc1f6d44de8af841adaaa16ad9e7..319cd58407fe80dca5fe7c0d16d2ddb73c01ce05 100644 --- a/modules/file/file.field.inc +++ b/modules/file/file.field.inc @@ -261,8 +261,16 @@ function file_field_update($entity_type, $entity, $field, $instance, $langcode, $current_fids[] = $item['fid']; } - // Compare the original field values with the ones that are being saved. - $original = $entity->original; + // Compare the original field values with the ones that are being saved. Use + // $entity->original to check this when possible, but if it isn't available, + // create a bare-bones entity and load its previous values instead. + if (isset($entity->original)) { + $original = $entity->original; + } + else { + $original = entity_create_stub_entity($entity_type, array($id, $vid, $bundle)); + field_attach_load($entity_type, array($id => $original), FIELD_LOAD_CURRENT, array('field_id' => $field['id'])); + } $original_fids = array(); if (!empty($original->{$field['field_name']}[$langcode])) { foreach ($original->{$field['field_name']}[$langcode] as $original_item) { diff --git a/modules/file/file.info b/modules/file/file.info index 25b90339cf0cb2a3a420937cf84df6fdd277b71d..b4b3461d4945dd6d9943ffbf07b5b514b243b072 100644 --- a/modules/file/file.info +++ b/modules/file/file.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = tests/file.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/file/tests/file_module_test.info b/modules/file/tests/file_module_test.info index 7b2fcbb492f987ae26f2b587fba49dc192b0ec13..624aab6560d742687b015e6eeed0dce37c0b74f8 100644 --- a/modules/file/tests/file_module_test.info +++ b/modules/file/tests/file_module_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc index 5a21e6e2e61067df201f8f8b3505a034377dd6d4..60284d993498659318fe54f362e59722912b7460 100644 --- a/modules/filter/filter.admin.inc +++ b/modules/filter/filter.admin.inc @@ -2,13 +2,14 @@ /** * @file - * Admin page callbacks for the filter module. + * Administrative page callbacks for the Filter module. */ /** - * Menu callback; Displays a list of all text formats and allows them to be rearranged. + * Page callback: Form constructor for a form to list and reorder text formats. * * @ingroup forms + * @see filter_menu() * @see filter_admin_overview_submit() */ function filter_admin_overview($form) { @@ -45,6 +46,9 @@ function filter_admin_overview($form) { return $form; } +/** + * Form submission handler for filter_admin_overview(). + */ function filter_admin_overview_submit($form, &$form_state) { foreach ($form_state['values']['formats'] as $id => $data) { if (is_array($data) && isset($data['weight'])) { @@ -95,7 +99,26 @@ function theme_filter_admin_overview($variables) { } /** - * Menu callback; Display a text format form. + * Page callback: Displays the text format add/edit form. + * + * @param object|null $format + * (optional) An object representing a format, with the following properties: + * - format: A machine-readable name representing the ID of the text format + * to save. If this corresponds to an existing text format, that format + * will be updated; otherwise, a new format will be created. + * - name: The title of the text format. + * - cache: (optional) An integer indicating whether the text format is + * cacheable (1) or not (0). Defaults to 1. + * - status: (optional) An integer indicating whether the text format is + * enabled (1) or not (0). Defaults to 1. + * - weight: (optional) The weight of the text format, which controls its + * placement in text format lists. If omitted, the weight is set to 0. + * Defaults to NULL. + * + * @return + * A form array. + * + * @see filter_menu() */ function filter_admin_format_page($format = NULL) { if (!isset($format->name)) { @@ -109,11 +132,24 @@ function filter_admin_format_page($format = NULL) { } /** - * Generate a text format form. + * Form constructor for the text format add/edit form. + * + * @param $format + * A format object having the properties: + * - format: A machine-readable name representing the ID of the text format to + * save. If this corresponds to an existing text format, that format will be + * updated; otherwise, a new format will be created. + * - name: The title of the text format. + * - cache: An integer indicating whether the text format is cacheable (1) or + * not (0). Defaults to 1. + * - status: (optional) An integer indicating whether the text format is + * enabled (1) or not (0). Defaults to 1. + * - weight: (optional) The weight of the text format, which controls its + * placement in text format lists. If omitted, the weight is set to 0. * - * @ingroup forms * @see filter_admin_format_form_validate() * @see filter_admin_format_form_submit() + * @ingroup forms */ function filter_admin_format_form($form, &$form_state, $format) { $is_fallback = ($format->format == filter_fallback_format()); @@ -287,7 +323,9 @@ function theme_filter_admin_format_filter_order($variables) { } /** - * Validate text format form submissions. + * Form validation handler for filter_admin_format_form(). + * + * @see filter_admin_format_form_submit() */ function filter_admin_format_form_validate($form, &$form_state) { $format_format = trim($form_state['values']['format']); @@ -304,7 +342,9 @@ function filter_admin_format_form_validate($form, &$form_state) { } /** - * Process text format form submissions. + * Form submission handler for filter_admin_format_form(). + * + * @see filter_admin_format_form_validate() */ function filter_admin_format_form_submit($form, &$form_state) { // Remove unnecessary values. @@ -336,10 +376,14 @@ function filter_admin_format_form_submit($form, &$form_state) { } /** - * Menu callback; confirm deletion of a format. + * Form constructor for the text format deletion confirmation form. * - * @ingroup forms + * @param $format + * An object representing a text format. + * + * @see filter_menu() * @see filter_admin_disable_submit() + * @ingroup forms */ function filter_admin_disable($form, &$form_state, $format) { $form['#format'] = $format; @@ -353,7 +397,7 @@ function filter_admin_disable($form, &$form_state, $format) { } /** - * Process filter disable form submission. + * Form submission handler for filter_admin_disable(). */ function filter_admin_disable_submit($form, &$form_state) { $format = $form['#format']; @@ -362,4 +406,3 @@ function filter_admin_disable_submit($form, &$form_state) { $form_state['redirect'] = 'admin/config/content/formats'; } - diff --git a/modules/filter/filter.info b/modules/filter/filter.info index baf12a9f312ece288300312a5436f9ef1f800505..3323587874066bfb5c7cef232e46ba5079098287 100644 --- a/modules/filter/filter.info +++ b/modules/filter/filter.info @@ -7,8 +7,8 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/filter/filter.install b/modules/filter/filter.install index 9d17eb54bf838c78887204547350d4fc389f3de4..71ba97b08a5cd10cdaac8ed0e410d43a7c4cb2d1 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -2,7 +2,7 @@ /** * @file - * Install, update and uninstall functions for the filter module. + * Install, update, and uninstall functions for the Filter module. */ /** diff --git a/modules/filter/filter.module b/modules/filter/filter.module index 7b451b7e341c33098b607a9e040c1226b4499ba9..182033fe81c4e39e4dbf00155536c7e3024c2a0e 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -2,7 +2,7 @@ /** * @file - * Framework for handling filtering of content. + * Framework for handling the filtering of content. */ /** @@ -71,6 +71,7 @@ function filter_theme() { * Implements hook_element_info(). * * @see filter_process_format() + * @see text_format_wrapper() */ function filter_element_info() { $type['text_format'] = array( @@ -132,13 +133,16 @@ function filter_menu() { } /** - * Access callback for deleting text formats. + * Access callback: Checks access for disabling text formats. * * @param $format * A text format object. + * * @return * TRUE if the text format can be disabled by the current user, FALSE * otherwise. + * + * @see filter_menu() */ function _filter_disable_format_access($format) { // The fallback format can never be disabled. @@ -146,7 +150,7 @@ function _filter_disable_format_access($format) { } /** - * Load a text format object from the database. + * Loads a text format object from the database. * * @param $format_id * The format ID. @@ -164,29 +168,32 @@ function filter_format_load($format_id) { } /** - * Save a text format object to the database. + * Saves a text format object to the database. * * @param $format - * A format object using the properties: - * - 'format': A machine-readable name representing the ID of the text format + * A format object having the properties: + * - format: A machine-readable name representing the ID of the text format * to save. If this corresponds to an existing text format, that format * will be updated; otherwise, a new format will be created. - * - 'name': The title of the text format. - * - 'status': (optional) An integer indicating whether the text format is + * - name: The title of the text format. + * - status: (optional) An integer indicating whether the text format is * enabled (1) or not (0). Defaults to 1. - * - 'weight': (optional) The weight of the text format, which controls its + * - weight: (optional) The weight of the text format, which controls its * placement in text format lists. If omitted, the weight is set to 0. - * - 'filters': (optional) An associative, multi-dimensional array of filters + * - filters: (optional) An associative, multi-dimensional array of filters * assigned to the text format, keyed by the name of each filter and using * the properties: - * - 'weight': (optional) The weight of the filter in the text format. If + * - weight: (optional) The weight of the filter in the text format. If * omitted, either the currently stored weight is retained (if there is * one), or the filter is assigned a weight of 10, which will usually * put it at the bottom of the list. - * - 'status': (optional) A boolean indicating whether the filter is + * - status: (optional) A boolean indicating whether the filter is * enabled in the text format. If omitted, the filter will be disabled. - * - 'settings': (optional) An array of configured settings for the filter. + * - settings: (optional) An array of configured settings for the filter. * See hook_filter_info() for details. + * + * @return + * SAVED_NEW or SAVED_UPDATED. */ function filter_format_save($format) { $format->name = trim($format->name); @@ -271,7 +278,7 @@ function filter_format_save($format) { } /** - * Disable a text format. + * Disables a text format. * * There is no core facility to re-enable a disabled format. It is not deleted * to keep information for contrib and to make sure the format ID is never @@ -313,7 +320,15 @@ function filter_format_exists($format_id) { } /** - * Display a text format form title. + * Displays a text format form title. + * + * @param object $format + * A format object. + * + * @return string + * The name of the format. + * + * @see filter_menu() */ function filter_admin_format_title($format) { return $format->name; @@ -350,6 +365,7 @@ function filter_permission() { * * @param $format * An object representing a text format. + * * @return * The machine-readable permission name, or FALSE if the provided text format * is malformed or is the fallback format (which is available to all users). @@ -380,11 +396,13 @@ function filter_modules_disabled($modules) { } /** - * Retrieve a list of text formats, ordered by weight. + * Retrieves a list of text formats, ordered by weight. * * @param $account * (optional) If provided, only those formats that are allowed for this user - * account will be returned. All formats will be returned otherwise. + * account will be returned. All formats will be returned otherwise. Defaults + * to NULL. + * * @return * An array of text format objects, keyed by the format ID and ordered by * weight. @@ -427,7 +445,7 @@ function filter_formats($account = NULL) { } /** - * Resets text format caches. + * Resets the text format caches. * * @see filter_formats() */ @@ -443,6 +461,7 @@ function filter_formats_reset() { * * @param $format * An object representing the text format. + * * @return * An array of role names, keyed by role ID. */ @@ -461,6 +480,7 @@ function filter_get_roles_by_format($format) { * * @param $rid * The user role ID to retrieve text formats for. + * * @return * An array of text format objects that are allowed for the role, keyed by * the text format ID and ordered by weight. @@ -494,7 +514,8 @@ function filter_get_formats_by_role($rid) { * * @param $account * (optional) The user account to check. Defaults to the currently logged-in - * user. + * user. Defaults to NULL. + * * @return * The ID of the user's default text format. * @@ -525,15 +546,18 @@ function filter_default_format($account = NULL) { * format is initialized to output plain text. Installation profiles and site * administrators have the freedom to configure it further. * - * Note that the fallback format is completely distinct from the default - * format, which differs per user and is simply the first format which that - * user has access to. The default and fallback formats are only guaranteed to - * be the same for users who do not have access to any other format; otherwise, - * the fallback format's weight determines its placement with respect to the - * user's other formats. + * Note that the fallback format is completely distinct from the default format, + * which differs per user and is simply the first format which that user has + * access to. The default and fallback formats are only guaranteed to be the + * same for users who do not have access to any other format; otherwise, the + * fallback format's weight determines its placement with respect to the user's + * other formats. * - * Any modules implementing a format deletion functionality must not delete - * this format. + * Any modules implementing a format deletion functionality must not delete this + * format. + * + * @return + * The ID of the fallback text format. * * @see hook_filter_format_disable() * @see filter_default_format() @@ -550,6 +574,9 @@ function filter_fallback_format() { /** * Returns the title of the fallback text format. + * + * @return string + * The title of the fallback text format. */ function filter_fallback_format_title() { $fallback_format = filter_format_load(filter_fallback_format()); @@ -557,7 +584,10 @@ function filter_fallback_format_title() { } /** - * Return a list of all filters provided by modules. + * Returns a list of all filters provided by modules. + * + * @return array + * An array of filter formats. */ function filter_get_filters() { $filters = &drupal_static(__FUNCTION__, array()); @@ -588,14 +618,16 @@ function filter_get_filters() { } /** - * Helper function for sorting the filter list by filter name. + * Sorts an array of filters by filter name. + * + * Callback for uasort() within filter_get_filters(). */ function _filter_list_cmp($a, $b) { return strcmp($a['title'], $b['title']); } /** - * Check if text in a certain text format is allowed to be cached. + * Checks if the text in a certain text format is allowed to be cached. * * This function can be used to check whether the result of the filtering * process can be cached. A text format may allow caching depending on the @@ -603,6 +635,7 @@ function _filter_list_cmp($a, $b) { * * @param $format_id * The text format ID to check. + * * @return * TRUE if the given text format allows caching, FALSE otherwise. */ @@ -619,6 +652,7 @@ function filter_format_allowcache($format_id) { * * @param $format * The text format object to check. + * * @return * TRUE if all the filters enabled in the given text format allow caching, * FALSE otherwise. @@ -640,7 +674,7 @@ function _filter_format_is_cacheable($format) { } /** - * Retrieve a list of filters for a given text format. + * Retrieves a list of filters for a given text format. * * Note that this function returns all associated filters regardless of whether * they are enabled or disabled. All functions working with the filter @@ -694,7 +728,7 @@ function filter_list_format($format_id) { } /** - * Run all the enabled filters on a piece of text. + * Runs all the enabled filters on a piece of text. * * Note: Because filters can inject JavaScript or execute PHP code, security is * vital here. When a user supplies a text format, you should validate it using @@ -705,16 +739,20 @@ function filter_list_format($format_id) { * @param $text * The text to be filtered. * @param $format_id - * The format id of the text to be filtered. If no format is assigned, the - * fallback format will be used. + * (optional) The format ID of the text to be filtered. If no format is + * assigned, the fallback format will be used. Defaults to NULL. * @param $langcode - * Optional: the language code of the text to be filtered, e.g. 'en' for + * (optional) The language code of the text to be filtered, e.g. 'en' for * English. This allows filters to be language aware so language specific - * text replacement can be implemented. + * text replacement can be implemented. Defaults to an empty string. * @param $cache - * Boolean whether to cache the filtered output in the {cache_filter} table. - * The caller may set this to FALSE when the output is already cached - * elsewhere to avoid duplicate cache lookups and storage. + * (optional) A Boolean indicating whether to cache the filtered output in the + * {cache_filter} table. The caller may set this to FALSE when the output is + * already cached elsewhere to avoid duplicate cache lookups and storage. + * Defaults to FALSE. + * + * @return + * The filtered text. * * @ingroup sanitization */ @@ -784,8 +822,8 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) * the text format id specified in #format or the user's default format by * default, if NULL. * - * The resulting value for the element will be an array holding the value and the - * format. For example, the value for the body element will be: + * The resulting value for the element will be an array holding the value and + * the format. For example, the value for the body element will be: * @code * $form_state['values']['body']['value'] = 'foo'; * $form_state['values']['body']['format'] = 'foo'; @@ -795,7 +833,7 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) * The form element to process. Properties used: * - #base_type: The form element #type to use for the 'value' element. * 'textarea' by default. - * - #format: (optional) The text format id to preselect. If NULL or not set, + * - #format: (optional) The text format ID to preselect. If NULL or not set, * the default format for the current user will be used. * * @return @@ -933,7 +971,7 @@ function filter_process_format($element) { } /** - * #pre_render callback for #type 'text_format' to hide field value from prying eyes. + * Render API callback: Hides the field value of 'text_format' elements. * * To not break form processing and previews if a user does not have access to a * stored text format, the expanded form elements in filter_process_format() are @@ -976,7 +1014,7 @@ function theme_text_format_wrapper($variables) { * An object representing the text format. * @param $account * (optional) The user account to check access for; if omitted, the currently - * logged-in user is used. + * logged-in user is used. Defaults to NULL. * * @return * Boolean TRUE if the user is allowed to access the given format. @@ -998,7 +1036,20 @@ function filter_access($format, $account = NULL) { } /** - * Helper function for fetching filter tips. + * Retrieves the filter tips. + * + * @param $format_id + * The ID of the text format for which to retrieve tips, or -1 to return tips + * for all formats accessible to the current user. + * @param $long + * (optional) Boolean indicating whether the long form of tips should be + * returned. Defaults to FALSE. + * + * @return + * An associative array of filtering tips, keyed by filter name. Each + * filtering tip is an associative array with elements: + * - tip: Tip text. + * - id: Filter ID. */ function _filter_tips($format_id, $long = FALSE) { global $user; @@ -1032,14 +1083,14 @@ function _filter_tips($format_id, $long = FALSE) { /** * Parses an HTML snippet and returns it as a DOM object. * - * This function loads the body part of a partial (X)HTML document - * and returns a full DOMDocument object that represents this document. - * You can use filter_dom_serialize() to serialize this DOMDocument - * back to a XHTML snippet. + * This function loads the body part of a partial (X)HTML document and returns + * a full DOMDocument object that represents this document. You can use + * filter_dom_serialize() to serialize this DOMDocument back to a XHTML + * snippet. * * @param $text - * The partial (X)HTML snippet to load. Invalid mark-up - * will be corrected on import. + * The partial (X)HTML snippet to load. Invalid mark-up will be corrected on + * import. * @return * A DOMDocument that represents the loaded (X)HTML snippet. */ @@ -1054,15 +1105,14 @@ function filter_dom_load($text) { /** * Converts a DOM object back to an HTML snippet. * - * The function serializes the body part of a DOMDocument - * back to an XHTML snippet. - * - * The resulting XHTML snippet will be properly formatted - * to be compatible with HTML user agents. + * The function serializes the body part of a DOMDocument back to an XHTML + * snippet. The resulting XHTML snippet will be properly formatted to be + * compatible with HTML user agents. * * @param $dom_document * A DOMDocument object to serialize, only the tags below * the first <body> node will be converted. + * * @return * A valid (X)HTML snippet, as a string. */ @@ -1099,9 +1149,11 @@ function filter_dom_serialize($dom_document) { * @param $dom_element * The element potentially containing a CDATA node. * @param $comment_start - * String to use as a comment start marker to escape the CDATA declaration. + * (optional) A string to use as a comment start marker to escape the CDATA + * declaration. Defaults to '//'. * @param $comment_end - * String to use as a comment end marker to escape the CDATA declaration. + * (optional) A string to use as a comment end marker to escape the CDATA + * declaration. Defaults to an empty string. */ function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') { foreach ($dom_element->childNodes as $node) { @@ -1156,7 +1208,7 @@ function theme_filter_guidelines($variables) { /** * @defgroup standard_filters Standard filters * @{ - * Filters implemented by the filter.module. + * Filters implemented by the Filter module. */ /** @@ -1204,7 +1256,10 @@ function filter_filter_info() { } /** - * Settings callback for the HTML filter. + * Filter settings callback for the HTML content filter. + * + * See hook_filter_FILTER_settings() for documentation of parameters and return + * value. */ function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) { $filter->settings += $defaults; @@ -1230,7 +1285,7 @@ function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) } /** - * HTML filter. Provides filtering of input into accepted HTML. + * Provides filtering of input into accepted HTML. */ function _filter_html($text, $filter) { $allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY); @@ -1249,7 +1304,9 @@ function _filter_html($text, $filter) { } /** - * Filter tips callback for HTML filter. + * Filter tips callback: Provides help for the HTML filter. + * + * @see filter_filter_info() */ function _filter_html_tips($filter, $format, $long = FALSE) { global $base_url; @@ -1347,7 +1404,9 @@ function _filter_html_tips($filter, $format, $long = FALSE) { } /** - * Settings callback for URL filter. + * Filter URL settings callback: Provides settings for the URL filter. + * + * @see filter_filter_info() */ function _filter_url_settings($form, &$form_state, $filter, $format, $defaults) { $filter->settings += $defaults; @@ -1366,7 +1425,7 @@ function _filter_url_settings($form, &$form_state, $filter, $format, $defaults) } /** - * URL filter. Automatically converts text into hyperlinks. + * Converts text into hyperlinks automatically. * * This filter identifies and makes clickable three types of "links". * - URLs like http://example.com. @@ -1489,7 +1548,9 @@ function _filter_url($text, $filter) { } /** - * preg_replace callback to make links out of absolute URLs. + * Makes links out of absolute URLs. + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_full_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1502,7 +1563,9 @@ function _filter_url_parse_full_links($match) { } /** - * preg_replace callback to make links out of e-mail addresses. + * Makes links out of e-mail addresses. + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_email_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1515,7 +1578,9 @@ function _filter_url_parse_email_links($match) { } /** - * preg_replace callback to make links out of domain names starting with "www." + * Makes links out of domain names starting with "www." + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_partial_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1528,14 +1593,17 @@ function _filter_url_parse_partial_links($match) { } /** - * preg_replace callback to escape contents of HTML comments + * Escapes the contents of HTML comments. + * + * Callback for preg_replace_callback() within _filter_url(). * * @param $match * An array containing matches to replace from preg_replace_callback(), * whereas $match[1] is expected to contain the content to be filtered. * @param $escape - * (optional) Boolean whether to escape (TRUE) or unescape comments (FALSE). - * Defaults to neither. If TRUE, statically cached $comments are reset. + * (optional) A Boolean indicating whether to escape (TRUE) or unescape + * comments (FALSE). Defaults to NULL, indicating neither. If TRUE, statically + * cached $comments are reset. */ function _filter_url_escape_comments($match, $escape = NULL) { static $mode, $comments = array(); @@ -1582,21 +1650,24 @@ function _filter_url_trim($text, $length = NULL) { } /** - * Filter tips callback for URL filter. + * Filter tips callback: Provides help for the URL filter. + * + * @see filter_filter_info() */ function _filter_url_tips($filter, $format, $long = FALSE) { return t('Web page addresses and e-mail addresses turn into links automatically.'); } /** - * Scan input and make sure that all HTML tags are properly closed and nested. + * Scans the input and makes sure that HTML tags are properly closed. */ function _filter_htmlcorrector($text) { return filter_dom_serialize(filter_dom_load($text)); } /** - * Convert line breaks into <p> and <br> in an intelligent fashion. + * Converts line breaks into <p> and <br> in an intelligent fashion. + * * Based on: http://photomatt.net/scripts/autop */ function _filter_autop($text) { @@ -1662,7 +1733,9 @@ function _filter_autop($text) { } /** - * Filter tips callback for auto-paragraph filter. + * Filter tips callback: Provides help for the auto-paragraph filter. + * + * @see filter_filter_info() */ function _filter_autop_tips($filter, $format, $long = FALSE) { if ($long) { @@ -1681,7 +1754,9 @@ function _filter_html_escape($text) { } /** - * Filter tips callback for HTML escaping filter. + * Filter tips callback: Provides help for the HTML escaping filter. + * + * @see filter_filter_info() */ function _filter_html_escape_tips($filter, $format, $long = FALSE) { return t('No HTML tags allowed.'); diff --git a/modules/filter/filter.pages.inc b/modules/filter/filter.pages.inc index dbbbe4c5aac129498ed42a319c669c2281639fdd..50f81177fe0831c7694f95cf9698bd301a97b2bf 100644 --- a/modules/filter/filter.pages.inc +++ b/modules/filter/filter.pages.inc @@ -2,12 +2,17 @@ /** * @file - * User page callbacks for the filter module. + * User page callbacks for the Filter module. */ - /** - * Menu callback; show a page with long filter tips. + * Page callback: Displays a page with long filter tips. + * + * @return string + * An HTML-formatted string. + * + * @see filter_menu() + * @see theme_filter_tips() */ function filter_tips_long() { $format_id = arg(2); @@ -20,13 +25,12 @@ function filter_tips_long() { return $output; } - /** * Returns HTML for a set of filter tips. * * @param $variables * An associative array containing: - * - tips: An array containing descriptions and a CSS id in the form of + * - tips: An array containing descriptions and a CSS ID in the form of * 'module-name/filter-id' (only used when $long is TRUE) for each * filter in one or more text formats. Example: * @code diff --git a/modules/filter/filter.test b/modules/filter/filter.test index aa1693fba4a0c7f6a9693613cb7d20746c377f87..0c3949517b912fc1b2f11c7c3b1e35e0f6e64c7c 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -22,7 +22,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Test CRUD operations for text formats and filters. + * Tests CRUD operations for text formats and filters. */ function testTextFormatCRUD() { // Add a text format with minimum data only. @@ -73,7 +73,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Verify that a text format is properly stored. + * Verifies that a text format is properly stored. */ function verifyTextFormat($format) { $t_args = array('%format' => $format->name); @@ -111,7 +111,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Verify that filters are properly stored for a text format. + * Verifies that filters are properly stored for a text format. */ function verifyFilters($format) { // Verify filter database records. @@ -160,6 +160,9 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } } +/** + * Tests the administrative functionality of the Filter module. + */ class FilterAdminTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -185,6 +188,9 @@ class FilterAdminTestCase extends DrupalWebTestCase { $this->drupalLogin($this->admin_user); } + /** + * Tests the format administration functionality. + */ function testFormatAdmin() { // Add text format. $this->drupalGet('admin/config/content/formats'); @@ -249,7 +255,7 @@ class FilterAdminTestCase extends DrupalWebTestCase { } /** - * Test filter administration functionality. + * Tests filter administration functionality. */ function testFilterAdmin() { // URL filter. @@ -413,11 +419,43 @@ class FilterAdminTestCase extends DrupalWebTestCase { } } +/** + * Tests the filter format access functionality in the Filter module. + */ class FilterFormatAccessTestCase extends DrupalWebTestCase { + /** + * A user with administrative permissions. + * + * @var object + */ protected $admin_user; + + /** + * A user with 'administer filters' permission. + * + * @var object + */ protected $filter_admin_user; + + /** + * A user with permission to create and edit own content. + * + * @var object + */ protected $web_user; + + /** + * An object representing an allowed text format. + * + * @var object + */ protected $allowed_format; + + /** + * An object representing a disallowed text format. + * + * @var object + */ protected $disallowed_format; public static function getInfo() { @@ -471,6 +509,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { )); } + /** + * Tests the Filter format access permissions functionality. + */ function testFormatPermissions() { // Make sure that a regular user only has access to the text format they // were granted access to, as well to the fallback format. @@ -507,6 +548,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.')); } + /** + * Tests if text format is available to a role. + */ function testFormatRoles() { // Get the role ID assigned to the regular user; it must be the maximum. $rid = max(array_keys($this->web_user->roles)); @@ -528,13 +572,13 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } /** - * Test editing a page using a disallowed text format. + * Tests editing a page using a disallowed text format. * - * Verifies that regular users and administrators are able to edit a page, - * but not allowed to change the fields which use an inaccessible text - * format. Also verifies that fields which use a text format that does not - * exist can be edited by administrators only, but that the administrator is - * forced to choose a new format before saving the page. + * Verifies that regular users and administrators are able to edit a page, but + * not allowed to change the fields which use an inaccessible text format. + * Also verifies that fields which use a text format that does not exist can + * be edited by administrators only, but that the administrator is forced to + * choose a new format before saving the page. */ function testFormatWidgetPermissions() { $langcode = LANGUAGE_NONE; @@ -650,7 +694,7 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } /** - * Rebuild text format and permission caches in the thread running the tests. + * Rebuilds text format and permission caches in the thread running the tests. */ protected function resetFilterCaches() { filter_formats_reset(); @@ -658,6 +702,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } } +/** + * Tests the default filter functionality in the Filter module. + */ class FilterDefaultFormatTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -667,6 +714,9 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { ); } + /** + * Tests if the default text format is accessible to users. + */ function testDefaultTextFormats() { // Create two text formats, and two users. The first user has access to // both formats, but the second user only has access to the second one. @@ -710,7 +760,7 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { } /** - * Rebuild text format and permission caches in the thread running the tests. + * Rebuilds text format and permission caches in the thread running the tests. */ protected function resetFilterCaches() { filter_formats_reset(); @@ -718,6 +768,9 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { } } +/** + * Tests the behavior of check_markup() when it is called without text format. + */ class FilterNoFormatTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -727,6 +780,12 @@ class FilterNoFormatTestCase extends DrupalWebTestCase { ); } + /** + * Tests text without format. + * + * Tests if text with no format is filtered the same way as text in the + * fallback format. + */ function testCheckMarkupNoFormat() { // Create some text. Include some HTML and line breaks, so we get a good // test of the filtering that is applied to it. @@ -757,7 +816,10 @@ class FilterSecurityTestCase extends DrupalWebTestCase { } /** - * Test that filtered content is emptied when an actively used filter module is disabled. + * Tests removal of filtered content when an active filter is disabled. + * + * Tests that filtered content is emptied when an actively used filter module + * is disabled. */ function testDisableFilterModule() { // Create a new node. @@ -800,7 +862,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the line break filter. + * Tests the line break filter. */ function testLineBreakFilter() { // Setup dummy filter object. @@ -1060,7 +1122,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test filter settings, defaults, access restrictions and similar. + * Tests filter settings, defaults, access restrictions and similar. * * @todo This is for functions like filter_filter and check_markup, whose * functionality is not completely focused on filtering. Some ideas: @@ -1116,7 +1178,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the spam deterrent. + * Tests the spam deterrent. */ function testNoFollowFilter() { // Setup dummy filter object. @@ -1147,7 +1209,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the loose, admin HTML filter. + * Tests the loose, admin HTML filter. */ function testFilterXSSAdmin() { // DRUPAL-SA-2008-044 @@ -1541,7 +1603,7 @@ www.example.com with a newline in comments --> } /** - * Test the HTML corrector filter. + * Tests the HTML corrector filter. * * @todo This test could really use some validity checking function. */ @@ -1745,9 +1807,9 @@ body {color:red} * @param $needle * Lowercase, plain text to look for. * @param $message - * Message to display if failed. + * (optional) Message to display if failed. Defaults to an empty string. * @param $group - * The group this message belongs to, defaults to 'Other'. + * (optional) The group this message belongs to. Defaults to 'Other'. * @return * TRUE on pass, FALSE on fail. */ @@ -1769,9 +1831,9 @@ body {color:red} * @param $needle * Lowercase, plain text to look for. * @param $message - * Message to display if failed. + * (optional) Message to display if failed. Defaults to an empty string. * @param $group - * The group this message belongs to, defaults to 'Other'. + * (optional) The group this message belongs to. Defaults to 'Other'. * @return * TRUE on pass, FALSE on fail. */ @@ -1781,7 +1843,7 @@ body {color:red} } /** - * Tests for filter hook invocation. + * Tests for Filter's hook invocations. */ class FilterHooksTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1799,7 +1861,10 @@ class FilterHooksTestCase extends DrupalWebTestCase { } /** - * Test that hooks run correctly on creating, editing, and deleting a text format. + * Tests hooks on format management. + * + * Tests that hooks run correctly on creating, editing, and deleting a text + * format. */ function testFilterHooks() { // Add a text format. @@ -1846,6 +1911,11 @@ class FilterHooksTestCase extends DrupalWebTestCase { * Tests filter settings. */ class FilterSettingsTestCase extends DrupalWebTestCase { + /** + * The installation profile to use with this test class. + * + * @var string + */ protected $profile = 'testing'; public static function getInfo() { diff --git a/modules/forum/forum-rtl.css b/modules/forum/forum-rtl.css index b475e42864321ed6e6c926856582bcd0601bebfb..3f2a88b3433390e4a4fa10754ae7c1317fae1645 100644 --- a/modules/forum/forum-rtl.css +++ b/modules/forum/forum-rtl.css @@ -7,6 +7,10 @@ float: right; margin: 0 0 0 9px; } +#forum div.indent { + margin-left: 0; + margin-right: 20px; +} .forum-topic-navigation { padding: 1em 3em 0 0; } diff --git a/modules/forum/forum.css b/modules/forum/forum.css index a758bc6669400be8b93203eb1cfdded090e23b69..480e07bfac36ea4129668f1e82db17e30c647d6d 100644 --- a/modules/forum/forum.css +++ b/modules/forum/forum.css @@ -29,7 +29,7 @@ } #forum div.indent { - margin-left: 20px; + margin-left: 20px; /* LTR */ } #forum .icon div { background-image: url(../../misc/forum-icons.png); diff --git a/modules/forum/forum.info b/modules/forum/forum.info index b2fac60787f426e2f171fa1fb8a151880753b3db..9edb60e99786f8989319a0eae26ea3d76bacc1b2 100644 --- a/modules/forum/forum.info +++ b/modules/forum/forum.info @@ -9,8 +9,8 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/forum/forum.module b/modules/forum/forum.module index fe0ef7920dcdc1314f18357d9bf7cad03558f660..18d9a7ecc61e38f499c016dd1095e92c9b55726a 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -658,7 +658,12 @@ function forum_block_info() { * Implements hook_block_configure(). */ function forum_block_configure($delta = '') { - $form['forum_block_num_' . $delta] = array('#type' => 'select', '#title' => t('Number of topics'), '#default_value' => variable_get('forum_block_num_' . $delta, '5'), '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))); + $form['forum_block_num_' . $delta] = array( + '#type' => 'select', + '#title' => t('Number of topics'), + '#default_value' => variable_get('forum_block_num_' . $delta, '5'), + '#options' => drupal_map_assoc(range(2, 20)) + ); return $form; } diff --git a/modules/forum/forum.test b/modules/forum/forum.test index 6937c623dbcf88a6766a7351f089b6ef1b728e08..a658377fe4cbb1ef7ccee3d5d939eca90c6b88d3 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -677,6 +677,7 @@ class ForumIndexTestCase extends DrupalWebTestCase { 'status' => FALSE, ); $this->drupalPost("node/{$node->nid}/edit", $edit, t('Save')); + $this->drupalGet("node/{$node->nid}"); $this->assertText(t('Access denied'), 'Unpublished node is no longer accessible.'); // Verify that the node no longer appears on the index. diff --git a/modules/help/help.info b/modules/help/help.info index 91c34eeb8fd010c4d6ecfde6a863616ea4a2164f..d4fe68d397113e4ddaa0607d1630bd9ac535e759 100644 --- a/modules/help/help.info +++ b/modules/help/help.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = help.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/image/image.admin.inc b/modules/image/image.admin.inc index ab99a49e8c32c910eeb6150798f7128112f5d4eb..9f0fab25484996440382c7fbd917a92cc84ef1b4 100644 --- a/modules/image/image.admin.inc +++ b/modules/image/image.admin.inc @@ -326,7 +326,7 @@ function image_style_delete_form_submit($form, &$form_state) { /** * Confirmation form to revert a database style to its default. */ -function image_style_revert_form($form, $form_state, $style) { +function image_style_revert_form($form, &$form_state, $style) { $form_state['image_style'] = $style; return confirm_form( diff --git a/modules/image/image.field.inc b/modules/image/image.field.inc index e2a0249ed622bf20543ba6596836ad92db449a7b..60c0f5ac00ff2e0e3bf20dae85a54020ee68b8b0 100644 --- a/modules/image/image.field.inc +++ b/modules/image/image.field.inc @@ -620,12 +620,6 @@ function theme_image_formatter($variables) { $image['title'] = $item['title']; } - /* UNL change: patch http://drupal.org/files/issues/1025796.patch http://drupal.org/node/1025796#comment-4298698 */ - // Load the attributes into the image. - if (isset($item['attributes'])) { - $image['attributes'] = $item['attributes']; - } - if ($variables['image_style']) { $image['style_name'] = $variables['image_style']; $output = theme('image_style', $image); diff --git a/modules/image/image.info b/modules/image/image.info index fdbc98c045c12426b5341a9c9a3be0e3e4ef4984..05be96a67d40c084d8818879e088f2a0623b819e 100644 --- a/modules/image/image.info +++ b/modules/image/image.info @@ -7,8 +7,8 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/image/image.install b/modules/image/image.install index b7aac7152982d5ef8fed836b4d1bd23d36ac9294..1d7bd4ef4d7c5e3db334d7dcb35b737c3e251065 100644 --- a/modules/image/image.install +++ b/modules/image/image.install @@ -391,7 +391,8 @@ function image_update_7002(array &$sandbox) { } // Process the table at the top of the list. - $table = reset(array_keys($sandbox['tables'])); + $keys = array_keys($sandbox['tables']); + $table = reset($keys); $sandbox['processed'] += _image_update_7002_populate_dimensions($table, $sandbox['tables'][$table], $sandbox['last_fid']); // Has the table been fully processed? diff --git a/modules/image/image.module b/modules/image/image.module index a9cc1a545938c483d1706a538a26606a95b84cb9..b7d6cdda3b9484e206e2bf6d704bb0b2ca0371fa 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -254,7 +254,7 @@ function image_form_system_file_system_settings_alter(&$form, &$form_state) { } /** - * Submit handler for the file system settings form. + * Form submission handler for system_file_system_settings(). * * Adds a menu rebuild after the public file path has been changed, so that the * menu router item depending on that file path will be regenerated. @@ -312,9 +312,9 @@ function image_file_download($uri) { return -1; } - // Private file access for the original files. Note that we only - // check access for non-temporary images, since file.module will - // grant access for all temporary files. + // Private file access for the original files. Note that we only check access + // for non-temporary images, since file.module will grant access for all + // temporary files. $files = file_load_multiple(array(), array('uri' => $uri)); if (count($files)) { $file = reset($files); @@ -537,7 +537,7 @@ function image_field_update_instance($instance, $prior_instance) { } /** - * Clear cached versions of a specific file in all styles. + * Clears cached versions of a specific file in all styles. * * @param $path * The Drupal file path to the original image. @@ -553,7 +553,7 @@ function image_path_flush($path) { } /** - * Get an array of all styles and their settings. + * Gets an array of all styles and their settings. * * @return * An array of styles keyed by the image style ID (isid). @@ -614,7 +614,9 @@ function image_styles() { } /** - * Load a style by style name or ID. May be used as a loader for menu items. + * Loads a style by style name or ID. + * + * May be used as a loader for menu items. * * @param $name * The name of the style. @@ -623,6 +625,7 @@ function image_styles() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image style array containing the following keys: * - "isid": The unique image style ID. @@ -660,12 +663,20 @@ function image_style_load($name = NULL, $isid = NULL, $include = NULL) { } /** - * Save an image style. + * Saves an image style. * - * @param style - * An image style array. - * @return - * An image style array. In the case of a new style, 'isid' will be populated. + * @param array $style + * An image style array containing: + * - name: A unique name for the style. + * - isid: (optional) An image style ID. + * + * @return array + * An image style array containing: + * - name: An unique name for the style. + * - old_name: The original name for the style. + * - isid: An image style ID. + * - is_new: TRUE if this is a new style, and FALSE if it is an existing + * style. */ function image_style_save($style) { if (isset($style['isid']) && is_numeric($style['isid'])) { @@ -692,13 +703,14 @@ function image_style_save($style) { } /** - * Delete an image style. + * Deletes an image style. * * @param $style * An image style array. * @param $replacement_style_name * (optional) When deleting a style, specify a replacement style name so * that existing settings (if any) may be converted to a new style. + * * @return * TRUE on success. */ @@ -717,14 +729,17 @@ function image_style_delete($style, $replacement_style_name = '') { } /** - * Load all the effects for an image style. + * Loads all the effects for an image style. * - * @param $style - * An image style array. - * @return + * @param array $style + * An image style array containing: + * - isid: The unique image style ID that contains this image effect. + * + * @return array * An array of image effects associated with specified image style in the * format array('isid' => array()), or an empty array if the specified style * has no effects. + * @see image_effects() */ function image_style_effects($style) { $effects = image_effects(); @@ -739,10 +754,11 @@ function image_style_effects($style) { } /** - * Get an array of image styles suitable for using as select list options. + * Gets an array of image styles suitable for using as select list options. * * @param $include_empty * If TRUE a <none> option will be inserted in the options array. + * * @return * Array of image styles both key and value are set to style name. */ @@ -763,7 +779,7 @@ function image_style_options($include_empty = TRUE) { } /** - * Menu callback; Given a style and image path, generate a derivative. + * Page callback: Generates a derivative, given a style and image path. * * After generating an image, transfer it to the requesting agent. * @@ -931,7 +947,7 @@ function image_style_transform_dimensions($style_name, array &$dimensions) { } /** - * Flush cached media for a style. + * Flushes cached media for a style. * * @param $style * An image style array. @@ -963,12 +979,13 @@ function image_style_flush($style) { } /** - * Return the URL for an image derivative given a style and image path. + * Returns the URL for an image derivative given a style and image path. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. + * * @return * The absolute URL where a style image can be downloaded, suitable for use * in an <img> tag. Requesting the URL will cause the image to be created. @@ -979,7 +996,7 @@ function image_style_url($style_name, $path) { // The token query is added even if the 'image_allow_insecure_derivatives' // variable is TRUE, so that the emitted links remain valid if it is changed // back to the default FALSE. - $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $path)); + $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($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 @@ -1017,7 +1034,7 @@ function image_style_path_token($style_name, $uri) { } /** - * Return the URI of an image when using a style. + * Returns the URI of an image when using a style. * * The path returned by this function may not exist. The default generation * method only creates images when they are requested by a user's browser. @@ -1026,6 +1043,7 @@ function image_style_path_token($style_name, $uri) { * The name of the style to be used with this image. * @param $uri * The URI or path to the image. + * * @return * The URI to an image style image. * @see image_style_url() @@ -1043,10 +1061,11 @@ function image_style_path($style_name, $uri) { } /** - * Save a default image style to the database. + * Saves a default image style to the database. * * @param style * An image style array provided by a module. + * * @return * An image style array. The returned style array will include the new 'isid' * assigned to the style. @@ -1064,7 +1083,7 @@ function image_default_style_save($style) { } /** - * Revert the changes made by users to a default image style. + * Reverts the changes made by users to a default image style. * * @param style * An image style array. @@ -1081,7 +1100,10 @@ function image_default_style_revert($style) { } /** - * Pull in image effects exposed by modules implementing hook_image_effect_info(). + * Returns a set of image effects. + * + * These image effects are exposed by modules implementing + * hook_image_effect_info(). * * @return * An array of image effects to be used when transforming images. @@ -1123,7 +1145,7 @@ function image_effect_definitions() { } /** - * Load the definition for an image effect. + * Loads the definition for an image effect. * * The effect definition is a set of core properties for an image effect, not * containing any user-settings. The definition defines various functions to @@ -1135,6 +1157,7 @@ function image_effect_definitions() { * The name of the effect definition to load. * @param $style * An image style array to which this effect will be added. + * * @return * An array containing the image effect definition with the following keys: * - "effect": The unique name for the effect being performed. Usually prefixed @@ -1162,7 +1185,7 @@ function image_effect_definition_load($effect, $style_name = NULL) { } /** - * Load all image effects from the database. + * Loads all image effects from the database. * * @return * An array of all image effects. @@ -1194,7 +1217,7 @@ function image_effects() { } /** - * Load a single image effect. + * Loads a single image effect. * * @param $ieid * The image effect ID. @@ -1203,6 +1226,7 @@ function image_effects() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image effect array, consisting of the following keys: * - "ieid": The unique image effect ID. @@ -1224,10 +1248,11 @@ function image_effect_load($ieid, $style_name, $include = NULL) { } /** - * Save an image effect. + * Saves an image effect. * * @param $effect * An image effect array. + * * @return * An image effect array. In the case of a new effect, 'ieid' will be set. */ @@ -1244,7 +1269,7 @@ function image_effect_save($effect) { } /** - * Delete an image effect. + * Deletes an image effect. * * @param $effect * An image effect array. @@ -1256,12 +1281,13 @@ function image_effect_delete($effect) { } /** - * Given an image object and effect, perform the effect on the file. + * Applies an image effect to the image object. * * @param $image * An image object returned by image_load(). * @param $effect * An image effect array. + * * @return * TRUE on success. FALSE if unable to perform the image effect on the image. */ @@ -1312,7 +1338,7 @@ function theme_image_style($variables) { } /** - * Accept a keyword (center, top, left, etc) and return it as a pixel offset. + * Accepts a keyword (center, top, left, etc) and returns it as a pixel offset. * * @param $value * @param $current_pixels diff --git a/modules/image/image.test b/modules/image/image.test index 25fddf64c131861854d048946e338bc971282d5e..8b79d851e3aeaff8e5f1d271878897cdbb64a2eb 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -167,10 +167,17 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->_testImageStyleUrlAndPath('private', FALSE); } + /** + * Test image_style_url() with a file URL that has an extra slash in it. + */ + function testImageStyleUrlExtraSlash() { + $this->_testImageStyleUrlAndPath('public', TRUE, TRUE); + } + /** * Test image_style_url(). */ - function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) { + function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) { // Make the default scheme neither "public" nor "private" to verify the // functions work for other than the default scheme. variable_set('file_default_scheme', 'temporary'); @@ -196,6 +203,15 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $generate_url = image_style_url($this->style_name, $original_uri); + // Ensure that the tests still pass when the file is generated by accessing + // a poorly constructed (but still valid) file URL that has an extra slash + // in it. + if ($extra_slash) { + $modified_uri = str_replace('://', ':///', $original_uri); + $this->assertNotEqual($original_uri, $modified_uri, 'An extra slash was added to the generated file URI.'); + $generate_url = image_style_url($this->style_name, $modified_uri); + } + if (!$clean_url) { $this->assertTrue(strpos($generate_url, '?q=') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); } @@ -224,6 +240,12 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->drupalGet($generate_url); $this->assertResponse(200, t('Image was generated at the URL.')); + // Make sure that access is denied for existing style files if we do not + // have access. + variable_del('image_module_test_file_download'); + $this->drupalGet($generate_url); + $this->assertResponse(403, 'Confirmed that access is denied for the private image style.'); + // Repeat this with a different file that we do not have access to and // make sure that access is denied. $file_noaccess = array_shift($files); @@ -805,7 +827,6 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { // Only verify HTTP headers when using private scheme and the headers are // sent by Drupal. $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png', 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.')); // Log out and try to access the file. diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info index 40b45abf43a9e4df72bbd81768c3a1aa4c9b5941..3486df8c353e850dca63e2ae6c2fde01196c40e4 100644 --- a/modules/image/tests/image_module_test.info +++ b/modules/image/tests/image_module_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index 2535357513274a27830415a227a6de6c6e0ba4d1..f1a71ddfe2a72c606fdf22d43dea69ef3c8b78ca 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -388,13 +388,13 @@ function locale_languages_edit_form_validate($form, &$form_state) { form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.')); } if (!empty($form_state['values']['domain']) && $duplicate = db_query("SELECT language FROM {languages} WHERE domain = :domain AND language <> :language", array(':domain' => $form_state['values']['domain'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate->language))); + form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate))); } if (empty($form_state['values']['prefix']) && language_default('language') != $form_state['values']['langcode'] && empty($form_state['values']['domain'])) { form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.')); } if (!empty($form_state['values']['prefix']) && $duplicate = db_query("SELECT language FROM {languages} WHERE prefix = :prefix AND language <> :language", array(':prefix' => $form_state['values']['prefix'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate->language))); + form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate))); } } diff --git a/modules/locale/locale.info b/modules/locale/locale.info index fcacaec5bc0dc4caef8abdb58246964fa51c4157..1ba1277702145e89fbc5be4f694809234f1becb7 100644 --- a/modules/locale/locale.info +++ b/modules/locale/locale.info @@ -6,8 +6,8 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 94e7cd151a345d51bc2ba471488374e7bbb0bfb8..b60c531204cc0c01a2f0d5ba6ed26f2ff528a226 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -386,20 +386,53 @@ function locale_form_node_form_alter(&$form, &$form_state) { /** * Form submit handler for node_form(). * - * Checks if Locale is registered as a translation handler and handle possible - * node language changes. - * * This submit handler needs to run before entity_form_submit_build_entity() * is invoked by node_form_submit_build_node(), because it alters the values of * attached fields. Therefore, it cannot be a hook_node_submit() implementation. */ function locale_field_node_form_submit($form, &$form_state) { - if (field_has_translation_handler('node', 'locale')) { - $node = (object) $form_state['values']; - $current_language = entity_language('node', $node); - list(, , $bundle) = entity_extract_ids('node', $node); + locale_field_entity_form_submit('node', $form, $form_state); +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { + // If a content type has multilingual support we set the content language as + // comment language. + if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { + global $language_content; + $form['language']['#value'] = $language_content->language; + $submit_callback = 'locale_field_comment_form_submit'; + array_unshift($form['actions']['preview']['#submit'], $submit_callback); + array_unshift($form['#submit'], $submit_callback); + } +} - foreach (field_info_instances('node', $bundle) as $instance) { +/** + * Form submit handler for comment_form(). + * + * This submit handler needs to run before entity_form_submit_build_entity() + * is invoked by comment_form_submit_build_comment(), because it alters the + * values of attached fields. + */ +function locale_field_comment_form_submit($form, &$form_state) { + locale_field_entity_form_submit('comment', $form, $form_state); +} + +/** + * Handles field language on submit for the given entity type. + * + * Checks if Locale is registered as a translation handler and handle possible + * language changes. + */ +function locale_field_entity_form_submit($entity_type, $form, &$form_state ) { + if (field_has_translation_handler($entity_type, 'locale')) { + $entity = (object) $form_state['values']; + $current_language = entity_language($entity_type, $entity); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + foreach (field_info_instances($entity_type, $bundle) as $instance) { $field_name = $instance['field_name']; $field = field_info_field($field_name); $previous_language = $form[$field_name]['#language']; @@ -407,7 +440,7 @@ 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_language != $current_language) { - $form_state['values'][$field_name][$current_language] = $node->{$field_name}[$previous_language]; + $form_state['values'][$field_name][$current_language] = $entity->{$field_name}[$previous_language]; $form_state['values'][$field_name][$previous_language] = array(); } } @@ -491,6 +524,9 @@ function locale_field_language_fallback(&$display_language, $entity, $langcode) */ function locale_entity_info_alter(&$entity_info) { $entity_info['node']['translation']['locale'] = TRUE; + if (isset($entity_info['comment'])) { + $entity_info['comment']['translation']['locale'] = TRUE; + } } /** @@ -1060,15 +1096,3 @@ function locale_url_outbound_alter(&$path, &$options, $original_path) { } } } - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { - // If a content type has multilingual support we set the content language as - // comment language. - if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { - global $language_content; - $form['language']['#value'] = $language_content->language; - } -} diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 632506e131dff78c3f2c07f2a48ebdaae75ae8c0..d88634e19993a6d79d1fca3fe66881bfb1934f60 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -2758,7 +2758,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { parent::setUp('locale', 'locale_test'); // Create and login user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content')); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'administer comments', 'create article content')); $this->drupalLogin($admin_user); // Add language. @@ -2787,6 +2787,12 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { // French no matter what path prefix the URLs have. $edit = array('language' => 'fr'); $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save')); + + // Make comment body translatable. + $field = field_info_field('comment_body'); + $field['translatable'] = TRUE; + field_update_field($field); + $this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.'); } /** @@ -2817,22 +2823,46 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { foreach (language_list() as $langcode => $language) { // Post a comment with content language $langcode. $prefix = empty($language->prefix) ? '' : $language->prefix . '/'; - $edit = array("comment_body[$language_none][0][value]" => $this->randomName()); - $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); + $comment_values[$node_langcode][$langcode] = $this->randomName(); + // Initially field form widgets have no language. + $edit = array( + 'subject' => $this->randomName(), + "comment_body[$language_none][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Preview')); + // After the first submit the submitted entity language is taken into + // account. + $edit = array( + 'subject' => $edit['subject'], + "comment_body[$langcode][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost(NULL, $edit, t('Save')); // Check that comment language matches the current content language. - $comment = db_select('comment', 'c') - ->fields('c') + $cid = db_select('comment', 'c') + ->fields('c', array('cid')) ->condition('nid', $node->nid) ->orderBy('cid', 'DESC') + ->range(0, 1) ->execute() - ->fetchObject(); + ->fetchField(); + $comment = comment_load($cid); $comment_langcode = entity_language('comment', $comment); $args = array('%node_language' => $node_langcode, '%comment_language' => $comment_langcode, '%langcode' => $langcode); $this->assertEqual($comment_langcode, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args)); + $this->assertEqual($comment->comment_body[$langcode][0]['value'], $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.'); + } + } + + // Check that comment bodies appear in the administration UI. + $this->drupalGet('admin/content/comment'); + foreach ($comment_values as $node_values) { + foreach ($node_values as $value) { + $this->assertRaw($value); } } } + } /** diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index afad3657800f38af070bbd364c03f46eae4e88f7..4fba05447147f800f6efca676d5b7e61c897a52c 100644 --- a/modules/locale/tests/locale_test.info +++ b/modules/locale/tests/locale_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 5ac755e582caccbbaf512352c0af6d3dbac128b1..68d8e12dc4e6af2eb448680a4585fc5ee5685c79 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -395,7 +395,7 @@ function menu_edit_item_validate($form, &$form_state) { else { unset($item['options']['fragment']); } - if ($item['link_path'] != $parsed_link['path']) { + if (isset($parsed_link['path']) && $item['link_path'] != $parsed_link['path']) { $item['link_path'] = $parsed_link['path']; } } diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 3f3818e17256eddd8607b7ab396fe7d245ee9d74..22d93ef3ee300f3555019f0532eae739818a9e70 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -11,7 +11,7 @@ */ /** - * Informs modules that a custom menu was created. + * Respond to a custom menu creation. * * This hook is used to notify modules that a custom menu has been created. * Contributed modules may use the information to perform actions based on the @@ -34,7 +34,7 @@ function hook_menu_insert($menu) { } /** - * Informs modules that a custom menu was updated. + * Respond to a custom menu update. * * This hook is used to notify modules that a custom menu has been updated. * Contributed modules may use the information to perform actions based on the @@ -59,14 +59,14 @@ function hook_menu_update($menu) { } /** - * Informs modules that a custom menu was deleted. + * Respond to a custom menu deletion. * * This hook is used to notify modules that a custom menu along with all links * contained in it (if any) has been deleted. Contributed modules may use the * information to perform actions based on the information entered into the menu * system. * - * @param $link + * @param $menu * An array representing a custom menu: * - menu_name: The unique name of the custom menu. * - title: The human readable menu title. diff --git a/modules/menu/menu.info b/modules/menu/menu.info index f4e0cc9596d96c94cfdd7bfd13546db671dbb670..1b229cc5201980500517161b129774b65c59a218 100644 --- a/modules/menu/menu.info +++ b/modules/menu/menu.info @@ -6,8 +6,8 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/menu/menu.test b/modules/menu/menu.test index 324ba6766bcf14169312d417aa8a41d39d52ee61..5fa27c7d18b1a15cd571c81b8d9a2273ae3809aa 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -318,7 +318,7 @@ class MenuTestCase extends DrupalWebTestCase { * @param string $menu_name Menu name. */ function addInvalidMenuLink($menu_name = 'navigation') { - foreach (array('-&-', 'admin/people/permissions') as $link_path) { + foreach (array('-&-', 'admin/people/permissions', '#') as $link_path) { $edit = array( 'link_path' => $link_path, 'link_title' => 'title', diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 72adc3491fb9287ed18f74c17d53948889cd4f07..4b722ee021fcd73dc1b6c6dfa396e800910ed312 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -2,7 +2,7 @@ /** * @file - * Content type editing UI. + * Content type editing user interface. */ /** @@ -388,8 +388,7 @@ function node_node_type_update($info) { } /** - * Resets all of the relevant fields of a module-defined node type to their - * default values. + * Resets relevant fields of a module-defined node type to their default values. * * @param $type * The node type to reset. The node type is passed back by reference with its @@ -410,6 +409,8 @@ function node_type_reset($type) { /** * Menu callback; delete a single content type. + * + * @ingroup forms */ function node_type_delete_confirm($form, &$form_state, $type) { $form['type'] = array('#type' => 'value', '#value' => $type->type); @@ -430,6 +431,8 @@ function node_type_delete_confirm($form, &$form_state, $type) { /** * Process content type delete confirm submissions. + * + * @see node_type_delete_confirm() */ function node_type_delete_confirm_submit($form, &$form_state) { node_type_delete($form_state['values']['type']); diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index 1508bc0548e9ec2a6c77ec305ce022d77f235519..be09b37cca163b21930fcffcf6d03dd3673bde2b 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -7,6 +7,10 @@ /** * Menu callback: confirm rebuilding of permissions. + * + * @see node_configure_rebuild_confirm_submit() + * @see node_menu() + * @ingroup forms */ function node_configure_rebuild_confirm() { return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'), @@ -15,6 +19,8 @@ function node_configure_rebuild_confirm() { /** * Handler for wipe confirmation + * + * @see node_configure_rebuild_confirm() */ function node_configure_rebuild_confirm_submit($form, &$form_state) { node_access_rebuild(TRUE); @@ -66,6 +72,9 @@ function node_node_operations() { /** * List node administration filters that can be applied. + * + * @return + * An associative array of filters. */ function node_filters() { // Regular filters @@ -110,7 +119,7 @@ function node_filters() { } /** - * Apply filters for node administration filters based on session. + * Applies filters for node administration filters based on session. * * @param $query * A SelectQuery to which the filters should be applied. @@ -133,7 +142,16 @@ function node_build_filter_query(SelectQueryInterface $query) { } /** - * Return form for node administration filters. + * Returns the node administration filters form array to node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_filter_form() { $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); @@ -208,7 +226,15 @@ function node_filter_form() { } /** - * Process result from node administration filter form. + * Form submission handler for node_filter_form(). + * + * @see node_admin_content() + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_filter_form_submit($form, &$form_state) { $filters = node_filters(); @@ -240,15 +266,15 @@ function node_filter_form_submit($form, &$form_state) { * Make mass update of nodes, changing all nodes in the $nodes array * to update them with the field values in $updates. * - * IMPORTANT NOTE: This function is intended to work when called - * from a form submit handler. Calling it outside of the form submission - * process may not work correctly. + * IMPORTANT NOTE: This function is intended to work when called from a form + * submission handler. Calling it outside of the form submission process may not + * work correctly. * * @param array $nodes * Array of node nids to update. * @param array $updates - * Array of key/value pairs with node field names and the - * value to update that field to. + * Array of key/value pairs with node field names and the value to update that + * field to. */ function node_mass_update($nodes, $updates) { // We use batch processing to prevent timeout when updating a large number @@ -279,7 +305,17 @@ function node_mass_update($nodes, $updates) { } /** - * Node Mass Update - helper function. + * Updates individual nodes when fewer than 10 are queued. + * + * @param $nid + * ID of node to update. + * @param $updates + * Associative array of updates. + * + * @return object + * An updated node object. + * + * @see node_mass_update() */ function _node_mass_update_helper($nid, $updates) { $node = node_load($nid, NULL, TRUE); @@ -293,7 +329,14 @@ function _node_mass_update_helper($nid, $updates) { } /** - * Node Mass Update Batch operation + * Executes a batch operation for node_mass_update(). + * + * @param array $nodes + * An array of node IDs. + * @param array $updates + * Associative array of updates. + * @param array $context + * An array of contextual key/values. */ function _node_mass_update_batch_process($nodes, $updates, &$context) { if (!isset($context['sandbox']['progress'])) { @@ -324,7 +367,15 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) { } /** - * Node Mass Update Batch 'finished' callback. + * Menu callback: Reports the status of batch operation for node_mass_update(). + * + * @param bool $success + * A boolean indicating whether the batch mass update operation successfully + * concluded. + * @param int $results + * The number of nodes updated via the batch mode process. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_mass_update_batch_finished($success, $results, $operations) { if ($success) { @@ -339,7 +390,17 @@ function _node_mass_update_batch_finished($success, $results, $operations) { } /** - * Menu callback: content administration. + * Page callback: Form constructor for the content administration form. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_menu() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms */ function node_admin_content($form, $form_state) { if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') { @@ -354,6 +415,15 @@ function node_admin_content($form, $form_state) { /** * Form builder: Builds the node administration overview. + * + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_admin_nodes() { $admin_access = user_access('administer nodes'); @@ -525,8 +595,15 @@ function node_admin_nodes() { /** * Validate node_admin_nodes form submissions. * - * Check if any nodes have been selected to perform the chosen - * 'Update option' on. + * Checks whether any nodes have been selected to perform the chosen 'Update + * option' on. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_validate($form, &$form_state) { // Error if there are no items to select. @@ -538,7 +615,14 @@ function node_admin_nodes_validate($form, &$form_state) { /** * Process node_admin_nodes form submissions. * - * Execute the chosen 'Update option' on the selected nodes. + * Executes the chosen 'Update option' on the selected nodes. + * + * @see node_admin_nodes() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_submit($form, &$form_state) { $operations = module_invoke_all('node_operations'); @@ -564,6 +648,17 @@ function node_admin_nodes_submit($form, &$form_state) { } } +/** + * Multiple node deletion confirmation form for node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms + */ function node_multiple_delete_confirm($form, &$form_state, $nodes) { $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE); // array_filter returns only elements with TRUE values @@ -587,6 +682,16 @@ function node_multiple_delete_confirm($form, &$form_state, $nodes) { t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_multiple_delete_confirm(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + */ function node_multiple_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { node_delete_multiple(array_keys($form_state['values']['nodes'])); diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 052effc9ba86b84eae5d2570ab15fba66a83c339..f8dcfdeff57919b552dd930d8866ee3a529f7b60 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -11,8 +11,8 @@ * Functions to define and modify content types. * * Each content type is maintained by a primary module, which is either - * node.module (for content types created in the user interface) or the - * module that implements hook_node_info() to define the content type. + * node.module (for content types created in the user interface) or the module + * that implements hook_node_info() to define the content type. * * During node operations (create, update, view, delete, etc.), there are * several sets of hooks that get invoked to allow modules to modify the base @@ -22,10 +22,10 @@ * function prefix. For example, poll.module defines the base for the Poll * content type as "poll", so during creation of a poll node, hook_insert() is * only invoked by calling poll_insert(). - * - All-module hooks: This set of hooks is invoked on all implementing - * modules, to allow other modules to modify what the primary node module is - * doing. For example, hook_node_insert() is invoked on all modules when - * creating a poll node. + * - All-module hooks: This set of hooks is invoked on all implementing modules, + * to allow other modules to modify what the primary node module is doing. For + * example, hook_node_insert() is invoked on all modules when creating a poll + * node. * - Field hooks: Hooks related to the fields attached to the node. These are * invoked from the field operations functions described below, and can be * either field-type-specific or all-module hooks. @@ -56,16 +56,15 @@ * - hook_entity_update() (all) * - hook_node_access_records() (all) * - hook_node_access_records_alter() (all) - * - Loading a node (calling node_load(), node_load_multiple(), or - * entity_load() 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() * - hook_entity_load() (all) * - hook_node_load() (all) * - Viewing a single node (calling node_view() - note that the input to - * node_view() is a loaded node, so the Loading steps above are already - * done): + * node_view() is a loaded node, so the Loading steps above are already done): * - hook_view() (node-type-specific) * - field_attach_prepare_view() * - hook_entity_prepare_view() (all) @@ -97,9 +96,8 @@ * - Revision information is deleted from database * - hook_node_revision_delete() (all) * - field_attach_delete_revision() - * - Preparing a node for editing (calling node_form() - note that if it's - * an existing node, it will already be loaded; see the Loading section - * above): + * - Preparing a node for editing (calling node_form() - note that if it is an + * existing node, it will already be loaded; see the Loading section above): * - hook_prepare() (node-type-specific) * - hook_node_prepare() (all) * - hook_form() (node-type-specific) @@ -137,16 +135,16 @@ * associated with permission to view, edit, and delete individual nodes. * * The realms and grant IDs can be arbitrarily defined by your node access - * module; it is common to use role IDs as grant IDs, but that is not - * required. Your module could instead maintain its own list of users, where - * each list has an ID. In that case, the return value of this hook would be - * an array of the list IDs that this user is a member of. + * module; it is common to use role IDs as grant IDs, but that is not required. + * Your module could instead maintain its own list of users, where each list has + * an ID. In that case, the return value of this hook would be an array of the + * list IDs that this user is a member of. * - * A node access module may implement as many realms as necessary to - * properly define the access privileges for the nodes. Note that the system - * makes no distinction between published and unpublished nodes. It is the - * module's responsibility to provide appropriate realms to limit access to - * unpublished content. + * A node access module may implement as many realms as necessary to properly + * define the access privileges for the nodes. Note that the system makes no + * distinction between published and unpublished nodes. It is the module's + * responsibility to provide appropriate realms to limit access to unpublished + * content. * * Node access records are stored in the {node_access} table and define which * grants are required to access a node. There is a special case for the view @@ -183,7 +181,7 @@ * @param $account * The user object whose grants are requested. * @param $op - * The node operation to be performed, such as "view", "update", or "delete". + * The node operation to be performed, such as 'view', 'update', or 'delete'. * * @return * An array whose keys are "realms" of grants, and whose values are arrays of @@ -264,6 +262,7 @@ function hook_node_grants($account, $op) { * @return * An array of grants as defined above. * + * @see hook_node_access_records_alter() * @ingroup node_access */ function hook_node_access_records($node) { @@ -350,12 +349,11 @@ function hook_node_access_records_alter(&$grants, $node) { * Alter user access rules when trying to view, edit or delete a node. * * Node access modules establish rules for user access to content. - * hook_node_grants() defines permissions for a user to view, edit or - * delete nodes by building a $grants array that indicates the permissions - * assigned to the user by each node access module. This hook is called to allow - * modules to modify the $grants array by reference, so the interaction of - * multiple node access modules can be altered or advanced business logic can be - * applied. + * hook_node_grants() defines permissions for a user to view, edit or delete + * nodes by building a $grants array that indicates the permissions assigned to + * the user by each node access module. This hook is called to allow modules to + * modify the $grants array by reference, so the interaction of multiple node + * access modules can be altered or advanced business logic can be applied. * * @see hook_node_grants() * @@ -374,8 +372,8 @@ function hook_node_access_records_alter(&$grants, $node) { * @param $op * The operation being performed, 'view', 'update' or 'delete'. * - * Developers may use this hook to either add additional grants to a user - * or to remove existing grants. These rules are typically based on either the + * Developers may use this hook to either add additional grants to a user or to + * remove existing grants. These rules are typically based on either the * permissions assigned to a user role, or specific attributes of a user * account. * @@ -412,10 +410,10 @@ function hook_node_grants_alter(&$grants, $account, $op) { * @return * An array of operations. Each operation is an associative array that may * contain the following key-value pairs: - * - 'label': Required. The label for the operation, displayed in the dropdown + * - label: (required) The label for the operation, displayed in the dropdown * menu. - * - 'callback': Required. The function to call for the operation. - * - 'callback arguments': Optional. An array of additional arguments to pass + * - callback: (required) The function to call for the operation. + * - callback arguments: (optional) An array of additional arguments to pass * to the callback function. */ function hook_node_operations() { @@ -528,11 +526,10 @@ function hook_node_insert($node) { /** * Act on arbitrary nodes being loaded from the database. * - * This hook should be used to add information that is not in the node or - * node revisions table, not to replace information that is in these tables - * (which could interfere with the entity cache). For performance reasons, - * information for all available nodes should be loaded in a single query where - * possible. + * This hook should be used to add information that is not in the node or node + * revisions table, not to replace information that is in these tables (which + * could interfere with the entity cache). For performance reasons, information + * for all available nodes should be loaded in a single query where possible. * * This hook is invoked during node loading, which is handled by entity_load(), * via classes NodeController and DrupalDefaultEntityController. After the node @@ -572,15 +569,15 @@ function hook_node_load($nodes, $types) { * Modules may implement this hook if they want to have a say in whether or not * a given user has access to perform a given operation on a node. * - * The administrative account (user ID #1) always passes any access check, - * so this hook is not called in that case. Users with the "bypass node access" + * The administrative account (user ID #1) always passes any access check, so + * this hook is not called in that case. Users with the "bypass node access" * permission may always view and edit content through the administrative * interface. * - * Note that not all modules will want to influence access on all - * node types. If your module does not want to actively grant or - * block access, return NODE_ACCESS_IGNORE or simply return nothing. - * Blindly returning FALSE will break other node access modules. + * Note that not all modules will want to influence access on all node types. If + * your module does not want to actively grant or block access, return + * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will + * break other node access modules. * * Also note that this function isn't called for node listings (e.g., RSS feeds, * the default home page at path 'node', a recent content block, etc.) See @@ -651,17 +648,17 @@ function hook_node_prepare($node) { /** * Act on a node being displayed as a search result. * - * This hook is invoked from node_search_execute(), after node_load() - * and node_view() have been called. + * This hook is invoked from node_search_execute(), after node_load() and + * node_view() have been called. * * @param $node * The node being displayed in a search result. * * @return array * Extra information to be displayed with search result. This information - * should be presented as an associative array. It will be concatenated - * with the post information (last updated, author) in the default search - * result theming. + * should be presented as an associative array. It will be concatenated with + * the post information (last updated, author) in the default search result + * theming. * * @see template_preprocess_search_result() * @see search-result.tpl.php @@ -724,8 +721,8 @@ function hook_node_update($node) { /** * Act on a node being indexed for searching. * - * 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. + * 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 $node * The node being indexed. @@ -756,8 +753,8 @@ function hook_node_update_index($node) { * * Note: Changes made to the $node object within your hook implementation will * have no effect. The preferred method to change a node's content is to use - * hook_node_presave() instead. If it is really necessary to change - * the node at the validate stage, you can use form_set_value(). + * hook_node_presave() instead. If it is really necessary to change the node at + * the validate stage, you can use form_set_value(). * * @param $node * The node being validated. @@ -874,8 +871,8 @@ function hook_node_view_alter(&$build) { * * This hook allows a module to define one or more of its own node types. For * example, the blog module uses it to define a blog node-type named "Blog - * entry." The name and attributes of each desired node type are specified in - * an array returned by the hook. + * entry." The name and attributes of each desired node type are specified in an + * array returned by the hook. * * Only module-provided node types should be defined through this hook. User- * provided (or 'custom') node types should be defined only in the 'node_type' @@ -887,22 +884,22 @@ function hook_node_view_alter(&$build) { * contains a sub-array for each node type, with the machine-readable type * name as the key. Each sub-array has up to 10 attributes. Possible * attributes: - * - "name": the human-readable name of the node type. Required. - * - "base": the base string used to construct callbacks corresponding to - * this node type. - * (i.e. if base is defined as example_foo, then example_foo_insert will - * be called when inserting a node of that type). This string is usually - * the name of the module, but not always. Required. - * - "description": a brief description of the node type. Required. - * - "help": help information shown to the user when creating a node of - * this type.. Optional (defaults to ''). - * - "has_title": boolean indicating whether or not this node type has a title - * field. Optional (defaults to TRUE). - * - "title_label": the label for the title field of this content type. - * Optional (defaults to 'Title'). - * - "locked": boolean indicating whether the administrator can change the - * machine name of this type. FALSE = changeable (not locked), - * TRUE = unchangeable (locked). Optional (defaults to TRUE). + * - name: (required) The human-readable name of the node type. + * - base: (required) The base string used to construct callbacks + * corresponding to this node type (for example, if base is defined as + * example_foo, then example_foo_insert will be called when inserting a node + * of that type). This string is usually the name of the module, but not + * always. + * - description: (required) A brief description of the node type. + * - help: (optional) Help information shown to the user when creating a node + * of this type. + * - has_title: (optional) A Boolean indicating whether or not this node type + * has a title field. + * - title_label: (optional) The label for the title field of this content + * type. + * - locked: (optional) A Boolean indicating whether the administrator can + * change the machine name of this type. FALSE = changeable (not locked), + * TRUE = unchangeable (locked). * * The machine name of a node type should contain only letters, numbers, and * underscores. Underscores will be converted into hyphens for the purpose of @@ -950,20 +947,20 @@ function hook_node_info() { * corresponding to the internal name of the ranking mechanism, such as * 'recent', or 'comments'. The values should be arrays themselves, with the * following keys available: - * - "title": the human readable name of the ranking mechanism. Required. - * - "join": part of a query string to join to any additional necessary - * table. This is not necessary if the table required is already joined to - * by the base query, such as for the {node} table. Other tables should use - * the full table name as an alias to avoid naming collisions. Optional. - * - "score": part of a query string to calculate the score for the ranking - * mechanism based on values in the database. This does not need to be - * wrapped in parentheses, as it will be done automatically; it also does - * not need to take the weighted system into account, as it will be done - * automatically. It does, however, need to calculate a decimal between + * - title: (required) The human readable name of the ranking mechanism. + * - join: (optional) The part of a query string to join to any additional + * necessary table. This is not necessary if the table required is already + * joined to by the base query, such as for the {node} table. Other tables + * should use the full table name as an alias to avoid naming collisions. + * - score: (required) The part of a query string to calculate the score for + * the ranking mechanism based on values in the database. This does not need + * to be wrapped in parentheses, as it will be done automatically; it also + * does not need to take the weighted system into account, as it will be + * done automatically. It does, however, need to calculate a decimal between * 0 and 1; be careful not to cast the entire score to an integer by - * inadvertently introducing a variable argument. Required. - * - "arguments": if any arguments are required for the score, they can be - * specified in an array here. + * inadvertently introducing a variable argument. + * - arguments: (optional) If any arguments are required for the score, they + * can be specified in an array here. * * @ingroup node_api_hooks */ @@ -990,8 +987,8 @@ function hook_ranking() { /** * Respond to node type creation. * - * This hook is invoked from node_type_save() after the node type is added - * to the database. + * This hook is invoked from node_type_save() after the node type is added to + * the database. * * @param $info * The node type object that is being created. @@ -1003,8 +1000,8 @@ function hook_node_type_insert($info) { /** * Respond to node type updates. * - * This hook is invoked from node_type_save() after the node type is updated - * in the database. + * This hook is invoked from node_type_save() after the node type is updated in + * the database. * * @param $info * The node type object that is being updated. @@ -1258,25 +1255,24 @@ function hook_validate($node, $form, &$form_state) { * This hook is invoked only on the module that defines the node's content type * (use hook_node_view() to act on all node views). * - * This hook is invoked during node viewing after the node is fully loaded, - * so that the node type module can define a custom method for display, or - * add to the default display. + * This hook is invoked during node viewing after the node is fully loaded, so + * that the node type module can define a custom method for display, or add to + * the default display. * * @param $node * The node to be displayed, as returned by node_load(). * @param $view_mode * View mode, e.g. 'full', 'teaser', ... * @return - * $node. The passed $node parameter should be modified as necessary and - * returned so it can be properly presented. Nodes are prepared for display - * by assembling a structured array, formatted as in the Form API, in - * $node->content. As with Form API arrays, the #weight property can be - * used to control the relative positions of added elements. After this - * hook is invoked, node_view() calls field_attach_view() to add field - * views to $node->content, and then invokes hook_node_view() and - * hook_node_view_alter(), so if you want to affect the final - * view of the node, you might consider implementing one of these hooks - * instead. + * The passed $node parameter should be modified as necessary and returned so + * it can be properly presented. Nodes are prepared for display by assembling + * a structured array, formatted as in the Form API, in $node->content. As + * with Form API arrays, the #weight property can be used to control the + * relative positions of added elements. After this hook is invoked, + * node_view() calls field_attach_view() to add field views to $node->content, + * and then invokes hook_node_view() and hook_node_view_alter(), so if you + * want to affect the final view of the node, you might consider implementing + * one of these hooks instead. * * @ingroup node_api_hooks */ diff --git a/modules/node/node.info b/modules/node/node.info index c02a537dd627e0b36b3fd6de7c7cccb03fe8e597..b33383c21ad029f8e10bac8f0dc338f4d1bc92ae 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/node/node.install b/modules/node/node.install index 16d3dbaa0432e6aa3184a2e9feeaa1839318057c..43bfd531f4673691c8f5fedbef7bd2c903e88a68 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -914,6 +914,7 @@ function node_update_7012() { * Change {node}.vid default value from 0 to NULL to avoid deadlock issues on MySQL. */ function node_update_7013() { + db_drop_unique_key('node', 'vid'); db_change_field('node', 'vid', 'vid', array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', @@ -921,6 +922,7 @@ function node_update_7013() { 'not null' => FALSE, 'default' => NULL, )); + db_add_unique_key('node', 'vid', array('vid')); } /** diff --git a/modules/node/node.module b/modules/node/node.module index d86c74d2d211c4d0ea23094cdb3825ec5702cb2a..378193d2b959eef464d86d9d767297848fbf8f55 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -141,6 +141,7 @@ function node_theme() { ), 'node_admin_overview' => array( 'variables' => array('name' => NULL, 'type' => NULL), + 'file' => 'content_types.inc', ), 'node_recent_block' => array( 'variables' => array('nodes' => NULL), @@ -200,8 +201,8 @@ function node_entity_info() { ), ); - // Search integration is provided by node.module, so search-related - // view modes for nodes are defined here and not in search.module. + // Search integration is provided by node.module, so search-related view modes + // for nodes are defined here and not in search.module. if (module_exists('search')) { $return['node']['view modes'] += array( 'search_index' => array( @@ -244,6 +245,12 @@ function node_field_display_node_alter(&$display, $context) { /** * Entity URI callback. + * + * @param $node + * A node entity. + * + * @return array + * An array with 'path' as the key and the path to the node as its value. */ function node_uri($node) { return array( @@ -296,7 +303,7 @@ function node_title_list($result, $title = NULL) { } /** - * Update the 'last viewed' timestamp of the specified node for current user. + * Updates the 'last viewed' timestamp of the specified node for current user. * * @param $node * A node object. @@ -315,8 +322,14 @@ function node_tag_new($node) { } /** - * Retrieves the timestamp at which the current user last viewed the - * specified node. + * Retrieves the timestamp for the current user's last view of a specified node. + * + * @param $nid + * A node ID. + * + * @return + * If a node has been previously viewed by the user, the timestamp in seconds + * of when the last view occurred; otherwise, zero. */ function node_last_viewed($nid) { global $user; @@ -330,12 +343,13 @@ function node_last_viewed($nid) { } /** - * Decide on the type of marker to be displayed for a given node. + * Determines the type of marker to be displayed for a given node. * * @param $nid * Node ID whose history supplies the "last viewed" timestamp. * @param $timestamp * Time which is compared against node's "last viewed" timestamp. + * * @return * One of the MARK constants. */ @@ -359,7 +373,7 @@ function node_mark($nid, $timestamp) { } /** - * Extract the type name. + * Extracts the type name. * * @param $node * Either a string or object, containing the node type information. @@ -461,6 +475,8 @@ function node_type_get_name($node) { * node_type_save(), and obsolete ones are deleted via a call to * node_type_delete(). See _node_types_build() for an explanation of the new * and obsolete types. + * + * @see _node_types_build() */ function node_types_rebuild() { _node_types_build(TRUE); @@ -483,11 +499,34 @@ function node_type_load($name) { /** * Saves a node type to the database. * - * @param $info - * The node type to save, as an object. - * - * @return - * Status flag indicating outcome of the operation. + * @param object $info + * The node type to save; an object with the following properties: + * - type: A string giving the machine name of the node type. + * - name: A string giving the human-readable name of the node type. + * - base: A string that indicates the base string for hook functions. For + * example, 'node_content' is the value used by the UI when creating a new + * node type. + * - description: A string that describes the node type. + * - help: A string giving the help information shown to the user when + * creating a node of this type. + * - custom: TRUE or FALSE indicating whether this type is defined by a module + * (FALSE) or by a user (TRUE) via Add Content Type. + * - modified: TRUE or FALSE indicating whether this type has been modified by + * an administrator. Currently not used in any way. + * - locked: TRUE or FALSE indicating whether the administrator can change the + * machine name of this type. + * - disabled: TRUE or FALSE indicating whether this type has been disabled. + * - has_title: TRUE or FALSE indicating whether this type uses the node title + * field. + * - title_label: A string containing the label for the title. + * - module: A string giving the module defining this type of node. + * - orig_type: A string giving the original machine-readable name of this + * node type. This may be different from the current type name if the + * 'locked' key is FALSE. + * + * @return int + * A status flag indicating the outcome of the operation, either SAVED_NEW or + * SAVED_UPDATED. */ function node_type_save($info) { $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; @@ -540,7 +579,7 @@ function node_type_save($info) { } /** - * Add default body field to a node type. + * Adds default body field to a node type. * * @param $type * A node type object. @@ -655,6 +694,7 @@ function node_type_update_nodes($old_type, $type) { * * @param $rebuild * TRUE to rebuild node types. Equivalent to calling node_types_rebuild(). + * * @return * An object with two properties: * - names: Associative array of the names of node types, keyed by the type. @@ -761,8 +801,9 @@ function node_type_cache_reset() { * which prevents users from changing the machine name of the type. * * @param $info - * An object or array containing values to override the defaults. See - * hook_node_info() for details on what the array elements mean. + * (optional) An object or array containing values to override the defaults. + * See hook_node_info() for details on what the array elements mean. Defaults + * to an empty array. * * @return * A node type object, with missing values in $info set to their defaults. @@ -845,12 +886,13 @@ function node_rdf_mapping() { } /** - * Determine whether a node hook exists. + * Determines whether a node hook exists. * * @param $node * A node object or a string containing the node type. * @param $hook * A string containing the name of the hook. + * * @return * TRUE if the $hook exists in the node type of $node. */ @@ -860,7 +902,7 @@ function node_hook($node, $hook) { } /** - * Invoke a node hook. + * Invokes a node hook. * * @param $node * A node object or a string containing the node type. @@ -868,6 +910,7 @@ function node_hook($node, $hook) { * A string containing the name of the hook. * @param $a2, $a3, $a4 * Arguments to pass on to the hook, after the $node argument. + * * @return * The returned value of the invoked hook. */ @@ -880,11 +923,11 @@ function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { } /** - * Load node entities from the database. + * Loads node entities from the database. * * This function should be used whenever you need to load more than one node - * from the database. Nodes are loaded into memory and will not require - * database access if loaded again during the same page request. + * from the database. Nodes are loaded into memory and will not require database + * access if loaded again during the same page request. * * @see entity_load() * @see EntityFieldQuery @@ -910,7 +953,7 @@ function node_load_multiple($nids = array(), $conditions = array(), $reset = FAL } /** - * Load a node object from the database. + * Loads a node object from the database. * * @param $nid * The node ID. @@ -934,6 +977,9 @@ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { * * Fills in a few default values, and then invokes hook_prepare() on the node * type module, and hook_node_prepare() on all modules. + * + * @param $node + * A node object. */ function node_object_prepare($node) { // Set up default values, if required. @@ -963,11 +1009,11 @@ function node_object_prepare($node) { } /** - * Perform validation checks on the given node. + * Implements hook_validate(). + * + * Performs validation checks on the given node. */ function node_validate($node, $form, &$form_state) { - $type = node_type_get_type($node); - if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) { form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.')); } @@ -1000,7 +1046,13 @@ function node_validate($node, $form, &$form_state) { } /** - * Prepare node for saving by populating author and creation date. + * Prepares node for saving by populating author and creation date. + * + * @param $node + * A node object. + * + * @return + * An updated node object. */ function node_submit($node) { // A user might assign the node author by entering a user name in the node @@ -1021,7 +1073,7 @@ function node_submit($node) { } /** - * Save changes to a node or add a new node. + * Saves changes to a node or adds a new node. * * @param $node * The $node object to be saved. If $node->nid is @@ -1159,6 +1211,13 @@ function node_save($node) { * Helper function to save a revision with the uid of the current user. * * The resulting revision ID is available afterward in $node->vid. + * + * @param $node + * A node object. + * @param $uid + * The current user's UID. + * @param $update + * (optional) An array of primary keys' field names to update. */ function _node_save_revision($node, $uid, $update = NULL) { $temp_uid = $node->uid; @@ -1174,7 +1233,7 @@ function _node_save_revision($node, $uid, $update = NULL) { } /** - * Delete a node. + * Deletes a node. * * @param $nid * A node ID. @@ -1184,7 +1243,7 @@ function node_delete($nid) { } /** - * Delete multiple nodes. + * Deletes multiple nodes. * * @param $nids * An array of node IDs. @@ -1237,7 +1296,7 @@ function node_delete_multiple($nids) { } /** - * Delete a node revision. + * Deletes a node revision. * * @param $revision_id * The revision ID to delete. @@ -1262,7 +1321,7 @@ function node_revision_delete($revision_id) { } /** - * Generate an array for rendering the given node. + * Generates an array for rendering the given node. * * @param $node * A node object. @@ -1367,8 +1426,8 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { entity_prepare_view('node', array($node->nid => $node), $langcode); $node->content += field_attach_view('node', $node, $view_mode, $langcode); - // Always display a read more link on teasers because we have no way - // to know when a teaser view is different than a full view. + // Always display a read more link on teasers because we have no way to know + // when a teaser view is different than a full view. $links = array(); $node->content['links'] = array( '#theme' => 'links__node', @@ -1400,12 +1459,13 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { } /** - * Generate an array which displays a node detail page. + * Generates an array which displays a node detail page. * * @param $node * A node object. * @param $message * A flag which sets a page title relevant to the revision being viewed. + * * @return * A $page element suitable for use by drupal_render(). */ @@ -1428,6 +1488,9 @@ function node_show($node, $message = FALSE) { * * @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) { $page_node = menu_get_object(); @@ -1435,7 +1498,7 @@ function node_is_page($node) { } /** - * Process variables for node.tpl.php + * Processes variables for node.tpl.php * * Most themes utilize their own copy of node.tpl.php. The default is located * inside "modules/node/node.tpl.php". Look in there for the full list of @@ -1557,7 +1620,7 @@ function node_permission() { } /** - * Gather the rankings from the the hook_ranking implementations. + * Gathers the rankings from the the hook_ranking() implementations. * * @param $query * A query object that has been extended with the Search DB Extender. @@ -1804,6 +1867,7 @@ function node_user_delete($account) { * An associative array containing: * - form: A render element representing the form. * + * @see node_search_admin() * @ingroup themeable */ function theme_node_search_admin($variables) { @@ -1872,11 +1936,11 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $node_current_revision = node_load($node->nid); $is_current_revision = $node_current_revision->vid == $node->vid; - // There should be at least two revisions. If the vid of the given node - // and the vid of the current revision differ, then we already have two + // There should be at least two revisions. If the vid of the given node and + // the vid of the current revision differ, then we already have two // different revisions so there is no need for a separate database check. - // Also, if you try to revert to or delete the current revision, that's - // not good. + // Also, if you try to revert to or delete the current revision, that's not + // good. if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { $access[$cid] = FALSE; } @@ -1884,8 +1948,8 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $access[$cid] = TRUE; } else { - // First check the access to the current revision and finally, if the - // node passed in is not the current revision then access to that, too. + // First check the access to the current revision and finally, if the node + // passed in is not the current revision then access to that, too. $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account)); } } @@ -1893,6 +1957,14 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { return $access[$cid]; } +/** + * Access callback: Checks whether the user has permission to add a node. + * + * @return + * TRUE if the user has add permission, otherwise FALSE. + * + * @see node_menu() + */ function _node_add_access() { $types = node_type_get_types(); foreach ($types as $type) { @@ -2110,14 +2182,30 @@ function node_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Title callback for a node type. + * Title callback: Returns the unsanitized title of the node type edit form. + * + * @param $type + * The node type object. + * + * @return string + * An unsanitized string that is the title of the node type edit form. + * + * @see node_menu() */ function node_type_page_title($type) { return $type->name; } /** - * Title callback. + * Title callback: Returns the title of the node. + * + * @param $node + * The node object. + * + * @return + * An unsanitized string that is the title of the node. + * + * @see node_menu() */ function node_page_title($node) { return $node->title; @@ -2137,7 +2225,13 @@ function node_last_changed($nid) { } /** - * Return a list of all the existing revision numbers. + * Returns a list of all the existing revision numbers. + * + * @param Drupal\node\Node $node + * The node entity. + * + * @return + * An associative array keyed by node revision number. */ function node_revision_list($node) { $revisions = array(); @@ -2223,16 +2317,16 @@ function node_block_save($delta = '', $edit = array()) { * (optional) The maximum number of nodes to find. Defaults to 10. * * @return - * An array of partial node objects or an empty array if there are no recent - * nodes visible to the current user. + * An array of node entities or an empty array if there are no recent nodes + * visible to the current user. */ function node_get_recent($number = 10) { $query = db_select('node', 'n'); if (!user_access('bypass node access')) { - // If the user is able to view their own unpublished nodes, allow them - // to see these in addition to published nodes. Check that they actually - // have some unpublished nodes to view before adding the condition. + // If the user is able to view their own unpublished nodes, allow them to + // see these in addition to published nodes. Check that they actually have + // some unpublished nodes to view before adding the condition. if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { $query->condition(db_or() ->condition('n.status', NODE_PUBLISHED) @@ -2362,7 +2456,7 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) { } /** - * Form submit handler for block configuration form. + * Form submission handler for node_form_block_admin_configure_alter(). * * @see node_form_block_admin_configure_alter() */ @@ -2394,7 +2488,7 @@ function node_form_block_custom_block_delete_alter(&$form, &$form_state) { } /** - * Form submit handler for custom block delete form. + * Form submission handler for node_form_block_custom_block_delete_alter(). * * @see node_form_block_custom_block_delete_alter() */ @@ -2419,8 +2513,8 @@ function node_modules_uninstalled($modules) { /** * Implements hook_block_list_alter(). * - * Check the content type specific visibilty settings. - * Remove the block if the visibility conditions are not met. + * Check the content type specific visibilty settings. Remove the block if the + * visibility conditions are not met. */ function node_block_list_alter(&$blocks) { global $theme_key; @@ -2485,7 +2579,8 @@ function node_block_list_alter(&$blocks) { * @param $channel * An associative array containing title, link, description and other keys, * to be parsed by format_rss_channel() and format_xml_elements(). - * A list of channel elements can be found at the @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink + * A list of channel elements can be found at the + * @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink * The link should be an absolute URL. */ function node_feed($nids = FALSE, $channel = array()) { @@ -2505,7 +2600,6 @@ function node_feed($nids = FALSE, $channel = array()) { $item_length = variable_get('feed_item_length', 'fulltext'); $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/'); - $teaser = ($item_length == 'teaser'); // Load all nodes to be rendered. $nodes = node_load_multiple($nids); @@ -2559,7 +2653,7 @@ function node_feed($nids = FALSE, $channel = array()) { } /** - * Construct a drupal_render() style array from an array of loaded nodes. + * Constructs a drupal_render() style array from an array of loaded nodes. * * @param $nodes * An array of nodes as returned by node_load_multiple(). @@ -2568,8 +2662,8 @@ function node_feed($nids = FALSE, $channel = array()) { * @param $weight * An integer representing the weight of the first node in the list. * @param $langcode - * (optional) A language code to use for rendering. Defaults to the global - * content language of the current request. + * (optional) A language code to use for rendering. Defaults to NULL which is + * the global content language of the current request. * * @return * An array in the format expected by drupal_render(). @@ -2588,7 +2682,12 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcod } /** - * Menu callback; Generate a listing of promoted nodes. + * Menu callback: Generates a listing of promoted nodes. + * + * @return array + * An array in the format expected by drupal_render(). + * + * @see node_menu() */ function node_page_default() { $select = db_select('node', 'n') @@ -2638,7 +2737,15 @@ function node_page_default() { } /** - * Menu callback; view a single node. + * Menu callback: Displays a single node. + * + * @param $node + * The node object. + * + * @return + * A page array suitable for use by drupal_render(). + * + * @see node_menu() */ function node_page_view($node) { // If there is a menu link to this node, the link becomes the last part @@ -2667,7 +2774,7 @@ function node_update_index() { } /** - * Index a single node. + * Indexes a single node. * * @param $node * The node to index. @@ -2771,7 +2878,7 @@ function node_form_search_form_alter(&$form, $form_state) { } /** - * Form API callback for the search form. Registered in node_form_alter(). + * Form validation handler for node_form_alter(). */ function node_search_validate($form, &$form_state) { // Initialize using any existing basic search keywords. @@ -2819,8 +2926,8 @@ function node_search_validate($form, &$form_state) { * @{ * The node access system determines who can do what to which nodes. * - * In determining access rights for a node, node_access() first checks - * whether the user has the "bypass node access" permission. Such users have + * In determining access rights for a node, node_access() first checks whether + * the user has the "bypass node access" permission. Such users have * unrestricted access to all nodes. user 1 will always pass this check. * * Next, all implementations of hook_node_access() will be called. Each @@ -2858,8 +2965,7 @@ function node_search_validate($form, &$form_state) { */ /** - * Determine whether the current user may perform the given operation on the - * specified node. + * Determines whether the current user may perform the operation on the node. * * @param $op * The operation to be performed on the node. Possible values are: @@ -2873,6 +2979,7 @@ function node_search_validate($form, &$form_state) { * @param $account * Optional, a user object representing the user for whom the operation is to * be performed. Determines access for a user other than the current user. + * * @return * TRUE if the operation may be performed, FALSE otherwise. */ @@ -3005,6 +3112,7 @@ function node_node_access($node, $op, $account) { * * @param $type * The machine-readable name of the node type. + * * @return array * An array of permission names and descriptions. */ @@ -3038,11 +3146,11 @@ function node_list_permissions($type) { * * By default, this will include all node types in the system. To exclude a * specific node from getting permissions defined for it, set the - * node_permissions_$type variable to 0. Core does not provide an interface - * for doing so, however, contrib modules may exclude their own nodes in + * node_permissions_$type variable to 0. Core does not provide an interface for + * doing so. However, contrib modules may exclude their own nodes in * hook_install(). Alternatively, contrib modules may configure all node types - * at once, or decide to apply some other hook_node_access() implementation - * to some or all node types. + * at once, or decide to apply some other hook_node_access() implementation to + * some or all node types. * * @return * An array of node types managed by this module. @@ -3061,21 +3169,22 @@ function node_permissions_get_configured_types() { } /** - * Fetch an array of permission IDs granted to the given user ID. + * Fetches an array of permission IDs granted to the given user ID. * * The implementation here provides only the universal "all" grant. A node - * access module should implement hook_node_grants() to provide a grant - * list for the user. + * access module should implement hook_node_grants() to provide a grant list for + * the user. * - * After the default grants have been loaded, we allow modules to alter - * the grants array by reference. This hook allows for complex business - * logic to be applied when integrating multiple node access modules. + * After the default grants have been loaded, we allow modules to alter the + * grants array by reference. This hook allows for complex business logic to be + * applied when integrating multiple node access modules. * * @param $op * The operation that the user is trying to perform. * @param $account * The user object for the user performing the operation. If omitted, the * current user is used. + * * @return * An associative array in which the keys are realms, and the values are * arrays of grants for those realms. @@ -3163,11 +3272,10 @@ function node_access_view_all_nodes($account = NULL) { /** * Implements hook_query_TAG_alter(). * - * This is the hook_query_alter() for queries tagged with 'node_access'. - * It adds node access checks for the user account given by the 'account' - * meta-data (or global $user if not provided), for an operation given by - * the 'op' meta-data (or 'view' if not provided; other possible values are - * 'update' and 'delete'). + * This is the hook_query_alter() for queries tagged with 'node_access'. It adds + * node access checks for the user account given by the 'account' meta-data (or + * global $user if not provided), for an operation given by the 'op' meta-data + * (or 'view' if not provided; other possible values are 'update' and 'delete'). */ function node_query_node_access_alter(QueryAlterableInterface $query) { _node_query_node_access_alter($query, 'node'); @@ -3206,8 +3314,8 @@ function _node_query_node_access_alter($query, $type) { } // If $account can bypass node access, or there are no node access modules, - // or the operation is 'view' and the $acount has a global view grant (i.e., - // a view grant for node ID 0), we don't need to alter the query. + // or the operation is 'view' and the $account has a global view grant + // (such as a view grant for node ID 0), we don't need to alter the query. if (user_access('bypass node access', $account)) { return; } @@ -3394,15 +3502,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * * If a realm is provided, it will only delete grants from that realm, but it * will always delete a grant from the 'all' realm. Modules that utilize - * node_access can use this function when doing mass updates due to widespread + * node_access() can use this function when doing mass updates due to widespread * permission changes. * * Note: Don't call this function directly from a contributed module. Call * node_access_acquire_grants() instead. * * @param $node - * The $node being written to. All that is necessary is that it contains a - * nid. + * The node whose grants are being written. * @param $grants * A list of grants to write. Each grant is an array that must contain the * following keys: realm, gid, grant_view, grant_update, grant_delete. @@ -3410,10 +3517,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * is a module-defined id to define grant privileges. each grant_* field * is a boolean value. * @param $realm - * If provided, only read/write grants for that realm. + * (optional) If provided, read/write grants for that realm only. Defaults to + * NULL. * @param $delete - * If false, do not delete records. This is only for optimization purposes, - * and assumes the caller has already performed a mass delete of some form. + * (optional) If false, does not delete records. This is only for optimization + * purposes, and assumes the caller has already performed a mass delete of + * some form. Defaults to TRUE. + * + * @see node_access_acquire_grants() */ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { if ($delete) { @@ -3442,21 +3553,23 @@ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) } /** - * Flag / unflag the node access grants for rebuilding, or read the current - * value of the flag. + * Flags or unflags the node access grants for rebuilding. * + * If the argument isn't specified, the current value of the flag is returned. * When the flag is set, a message is displayed to users with 'access * administration pages' permission, pointing to the 'rebuild' confirm form. * This can be used as an alternative to direct node_access_rebuild calls, * allowing administrators to decide when they want to perform the actual - * (possibly time consuming) rebuild. - * When unsure the current user is an administrator, node_access_rebuild - * should be used instead. + * (possibly time consuming) rebuild. When unsure if the current user is an + * administrator, node_access_rebuild() should be used instead. * * @param $rebuild * (Optional) The boolean value to be written. - * @return - * (If no value was provided for $rebuild) The current value of the flag. + * + * @return + * The current value of the flag if no value was provided for $rebuild. + * + * @see node_access_rebuild() */ function node_access_needs_rebuild($rebuild = NULL) { if (!isset($rebuild)) { @@ -3471,15 +3584,15 @@ function node_access_needs_rebuild($rebuild = NULL) { } /** - * Rebuild the node access database. This is occasionally needed by modules - * that make system-wide changes to access levels. + * Rebuilds the node access database. * - * When the rebuild is required by an admin-triggered action (e.g module - * settings form), calling node_access_needs_rebuild(TRUE) instead of + * This is occasionally needed by modules that make system-wide changes to + * access levels. When the rebuild is required by an admin-triggered action (e.g + * module settings form), calling node_access_needs_rebuild(TRUE) instead of * node_access_rebuild() lets the user perform his changes and actually * rebuild only once he is done. * - * Note : As of Drupal 6, node access modules are not required to (and actually + * Note: As of Drupal 6, node access modules are not required to (and actually * should not) call node_access_rebuild() in hook_enable/disable anymore. * * @see node_access_needs_rebuild() @@ -3543,11 +3656,14 @@ function node_access_rebuild($batch_mode = FALSE) { } /** - * Batch operation for node_access_rebuild_batch. + * Performs batch operation for node_access_rebuild(). + * + * This is a multistep operation: we go through all nodes by packs of 20. The + * batch processing engine interrupts processing and sends progress feedback + * after 1 second execution time. * - * This is a multistep operation : we go through all nodes by packs of 20. - * The batch processing engine interrupts processing and sends progress - * feedback after 1 second execution time. + * @param array $context + * An array of contextual key/value information for rebuild batch process. */ function _node_access_rebuild_batch_operation(&$context) { if (empty($context['sandbox'])) { @@ -3578,7 +3694,14 @@ function _node_access_rebuild_batch_operation(&$context) { } /** - * Post-processing for node_access_rebuild_batch. + * Performs post-processing for node_access_rebuild(). + * + * @param bool $success + * A boolean indicating whether the re-build process has completed. + * @param array $results + * An array of results information. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_access_rebuild_batch_finished($success, $results, $operations) { if ($success) { @@ -3595,7 +3718,6 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { * @} End of "defgroup node_access". */ - /** * @defgroup node_content Hook implementations for user-created content types * @{ @@ -3631,6 +3753,7 @@ function node_content_form($node, $form_state) { /** * Implements hook_forms(). + * * All node forms share the same form handler. */ function node_forms() { @@ -3715,6 +3838,12 @@ function node_action_info() { /** * Sets the status of a node to 1 (published). * + * @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, $context = array()) { @@ -3725,6 +3854,12 @@ function node_publish_action($node, $context = array()) { /** * Sets the status of a node to 0 (unpublished). * + * @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, $context = array()) { @@ -3735,6 +3870,12 @@ function node_unpublish_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 1. * + * @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, $context = array()) { @@ -3745,6 +3886,12 @@ function node_make_sticky_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 0. * + * @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, $context = array()) { @@ -3755,6 +3902,12 @@ function node_make_unsticky_action($node, $context = array()) { /** * Sets the promote property of a node to 1. * + * @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, $context = array()) { @@ -3765,6 +3918,12 @@ function node_promote_action($node, $context = array()) { /** * Sets the promote property of a node to 0. * + * @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, $context = array()) { @@ -3775,6 +3934,9 @@ function node_unpromote_action($node, $context = array()) { /** * Saves a node. * + * @param $node + * The node to be saved. + * * @ingroup actions */ function node_save_action($node) { @@ -3791,6 +3953,9 @@ function node_save_action($node) { * Array with the following elements: * - 'owner_uid': User ID to assign to the node. * + * @see node_assign_owner_action_form() + * @see node_assign_owner_action_validate() + * @see node_assign_owner_action_submit() * @ingroup actions */ function node_assign_owner_action($node, $context) { @@ -3801,6 +3966,16 @@ function node_assign_owner_action($node, $context) { /** * Generates the settings form for node_assign_owner_action(). + * + * @param $context + * Array of additional information about what triggered the action. Includes + * the following elements: + * - 'owner_uid': User ID to assign to the node. + * + * @see node_assign_owner_action_submit() + * @see node_assign_owner_action_validate() + * + * @ingroup forms */ function node_assign_owner_action_form($context) { $description = t('The username of the user to which you would like to assign ownership.'); @@ -3841,6 +4016,8 @@ function node_assign_owner_action_form($context) { /** * Validates settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_submit() */ function node_assign_owner_action_validate($form, $form_state) { $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField(); @@ -3851,6 +4028,8 @@ function node_assign_owner_action_validate($form, $form_state) { /** * Saves settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_validate() */ function node_assign_owner_action_submit($form, $form_state) { // Username can change, so we need to store the ID, not the username. @@ -3860,6 +4039,14 @@ function node_assign_owner_action_submit($form, $form_state) { /** * Generates settings form for node_unpublish_by_keyword_action(). + * + * @param array $context + * Array of additional information about what triggered this action. + * + * @return array + * A form array. + * + * @see node_unpublish_by_keyword_action_submit() */ function node_unpublish_by_keyword_action_form($context) { $form['keywords'] = array( diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index 1f3e725de08557c14a5bfaf6eb1ced0c344a3165..9ee1e85e750668fd56c2c405f5026da2d83aa0dd 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -5,7 +5,6 @@ * Page callbacks for adding, editing, deleting, and revisions management for content. */ - /** * Menu callback; presents the node editing form. */ @@ -63,6 +62,12 @@ function theme_node_add_list($variables) { /** * Returns a node submission form. + * + * @param $type + * The node type for the submitted node. + * + * @return + * The themed form. */ function node_add($type) { global $user; @@ -75,6 +80,12 @@ function node_add($type) { return $output; } +/** + * Form validation handler for node_form(). + * + * @see node_form() + * @see node_form_submit() + */ 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 @@ -85,7 +96,13 @@ function node_form_validate($form, &$form_state) { } /** - * Generate the node add/edit form array. + * Form constructor for the node add/edit form. + * + * @see node_form_validate() + * @see node_form_submit() + * @see node_form_build_preview() + * @see node_form_delete_submit() + * @ingroup forms */ function node_form($form, &$form_state, $node) { global $user; @@ -311,7 +328,12 @@ function node_form($form, &$form_state, $node) { } /** - * Button submit function: handle the 'Delete' button on the node form. + * Form submission handler for node_form(). + * + * Handles the 'Delete' button on the node form. + * + * @see node_form() + * @see node_form_validate() */ function node_form_delete_submit($form, &$form_state) { $destination = array(); @@ -323,7 +345,14 @@ function node_form_delete_submit($form, &$form_state) { $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } - +/** + * Form submission handler for node_form(). + * + * Handles the 'Preview' button on the node form. + * + * @see node_form() + * @see node_form_validate() + */ function node_form_build_preview($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $form_state['node_preview'] = node_preview($node); @@ -331,7 +360,15 @@ function node_form_build_preview($form, &$form_state) { } /** - * Generate a node preview. + * Generates a node preview. + * + * @param $node + * The node to preview. + * + * @return + * An HTML-formatted string of a node preview. + * + * @see node_form_build_preview() */ function node_preview($node) { if (node_access('create', $node) || node_access('update', $node)) { @@ -377,6 +414,7 @@ function node_preview($node) { * An associative array containing: * - node: The node object which is being previewed. * + * @see node_preview() * @ingroup themeable */ function theme_node_preview($variables) { @@ -407,6 +445,12 @@ function theme_node_preview($variables) { return $output; } +/** + * Form submission handler for node_form(). + * + * @see node_form() + * @see node_form_validate() + */ function node_form_submit($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); @@ -426,7 +470,7 @@ function node_form_submit($form, &$form_state) { if ($node->nid) { $form_state['values']['nid'] = $node->nid; $form_state['nid'] = $node->nid; - $form_state['redirect'] = 'node/' . $node->nid; + $form_state['redirect'] = node_access('view', $node) ? 'node/' . $node->nid : '<front>'; } else { // In the unlikely case something went wrong on save, the node will be @@ -472,7 +516,9 @@ function node_form_submit_build_node($form, &$form_state) { } /** - * Menu callback -- ask for confirmation of node deletion + * Form constructor for the node deletion confirmation form. + * + * @see node_delete_confirm_submit() */ function node_delete_confirm($form, &$form_state, $node) { $form['#node'] = $node; @@ -488,7 +534,9 @@ function node_delete_confirm($form, &$form_state, $node) { } /** - * Execute node deletion + * Executes node deletion. + * + * @see node_delete_confirm() */ function node_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { @@ -502,7 +550,15 @@ function node_delete_confirm_submit($form, &$form_state) { } /** - * Generate an overview table of older revisions of a node. + * Generates an overview table of older revisions of a node. + * + * @param $node + * A node object. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() */ function node_revision_overview($node) { drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH); @@ -553,13 +609,26 @@ function node_revision_overview($node) { } /** - * Ask for confirmation of the reversion to prevent against CSRF attacks. + * Asks for confirmation of the reversion to prevent against CSRF attacks. + * + * @param int $node_revision + * The node revision ID. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_revert_confirm_submit() + * @ingroup forms */ function node_revision_revert_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel')); } +/** + * Form submission handler for node_revision_revert_confirm(). + */ function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; $node_revision->revision = 1; @@ -572,11 +641,29 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions'; } +/** + * Form constructor for the revision deletion confirmation form. + * + * This form prevents against CSRF attacks. + * + * @param $node_revision + * The node revision ID. + * + * @return + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_delete_confirm_submit() + * @ingroup forms + */ function node_revision_delete_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_revision_delete_confirm(). + */ function node_revision_delete_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; node_revision_delete($node_revision->vid); diff --git a/modules/node/node.test b/modules/node/node.test index d789d3c0e654ad61287a0bbb42709981c6f3f28b..0256fecfdcc0c05be0277e11821699e3e1fb4323 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -149,6 +149,9 @@ class NodeLoadHooksTestCase extends DrupalWebTestCase { } } +/** + * Tests the node revision functionality. + */ class NodeRevisionsTestCase extends DrupalWebTestCase { protected $nodes; protected $logs; @@ -198,7 +201,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } /** - * Check node revision related operations. + * Checks node revision related operations. */ function testRevisions() { $nodes = $this->nodes; @@ -282,6 +285,9 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } } +/** + * Tests the node edit functionality. + */ class PageEditTestCase extends DrupalWebTestCase { protected $web_user; protected $admin_user; @@ -302,7 +308,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check node edit functionality. + * Checks node edit functionality. */ function testPageEdit() { $this->drupalLogin($this->web_user); @@ -369,7 +375,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check changing node authored by fields. + * Tests changing a node's "authored by" field. */ function testPageAuthoredBy() { $this->drupalLogin($this->admin_user); @@ -414,6 +420,9 @@ class PageEditTestCase extends DrupalWebTestCase { } } +/** + * Tests the node entity preview functionality. + */ class PagePreviewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -431,7 +440,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality. + * Checks the node preview functionality. */ function testPagePreview() { $langcode = LANGUAGE_NONE; @@ -455,7 +464,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality, when using revisions. + * Checks the node preview functionality, when using revisions. */ function testPagePreviewWithRevisions() { $langcode = LANGUAGE_NONE; @@ -485,6 +494,9 @@ class PagePreviewTestCase extends DrupalWebTestCase { } } +/** + * Tests creating and saving a node. + */ class NodeCreationTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -503,7 +515,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a "Basic page" node and verify its consistency in the database. + * Creates a "Basic page" node and verifies its consistency in the database. */ function testNodeCreation() { // Create a node. @@ -522,7 +534,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a page node and verify that a transaction rolls back the failed creation + * Verifies that a transaction rolls back the failed creation. */ function testFailedPageCreation() { // Create a node. @@ -561,8 +573,30 @@ class NodeCreationTestCase extends DrupalWebTestCase { $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll(); $this->assertTrue(count($records) > 0, t('Rollback explanatory error logged to watchdog.')); } + + /** + * Create an unpublished node and confirm correct redirect behavior. + */ + function testUnpublishedNodeCreation() { + // Set "Basic page" content type to be unpublished by default. + variable_set('node_options_page', array()); + // Set the front page to the default "node" page. + variable_set('site_frontpage', 'node'); + + // Create a node. + $edit = array(); + $edit["title"] = $this->randomName(8); + $edit["body[" . LANGUAGE_NONE . "][0][value]"] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the user was redirected to the home page. + $this->assertText(t('Welcome to Drupal'), t('The user is redirected to the home page.')); + } } +/** + * Tests the functionality of node entity edit permissions. + */ class PageViewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -573,7 +607,7 @@ class PageViewTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests an anonymous and unpermissioned user attempting to edit the node. */ function testPageView() { // Create a node to view. @@ -602,6 +636,9 @@ class PageViewTestCase extends DrupalWebTestCase { } } +/** + * Tests the summary length functionality. + */ class SummaryLengthTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -612,7 +649,7 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests the node summary length functionality. */ function testSummaryLength() { // Create a node to view. @@ -644,6 +681,9 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } } +/** + * Tests XSS functionality with a node entity. + */ class NodeTitleXSSTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -653,6 +693,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { ); } + /** + * Tests XSS functionality with a node entity. + */ function testNodeTitleXSS() { // Prepare a user to do the stuff. $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); @@ -678,6 +721,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { } } +/** + * Tests the availability of the syndicate block. + */ class NodeBlockTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -709,7 +755,7 @@ class NodeBlockTestCase extends DrupalWebTestCase { } /** - * Check that the post information displays when enabled for a content type. + * Checks that the post information displays when enabled for a content type. */ class NodePostSettingsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -728,7 +774,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to display post information and confirm its presence on a new node. + * Confirms "Basic page" content type and post information is on a new node. */ function testPagePostInfo() { @@ -751,7 +797,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to not display post information and confirm its absence on a new node. + * Confirms absence of post information on a new node. */ function testPageNotPostInfo() { @@ -774,7 +820,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Ensure that data added to nodes by other modules appears in RSS feeds. + * Ensures that data added to nodes by other modules appears in RSS feeds. * * Create a node, enable the node_test module to ensure that extra data is * added to the node->content array, then verify that the data appears on the @@ -801,8 +847,7 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Create a new node and ensure that it includes the custom data when added - * to an RSS feed. + * Ensures that a new node includes the custom data when added to an RSS feed. */ function testNodeRSSContent() { // Create a node. @@ -841,9 +886,11 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Test case to verify basic node_access functionality. + * Tests basic node_access functionality. + * + * Note that hook_node_access_records() is covered in another test class. + * * @todo Cover hook_node_access in a separate test class. - * hook_node_access_records is covered in another test class. */ class NodeAccessTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -855,7 +902,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Asserts node_access correctly grants or denies access. + * Asserts node_access() correctly grants or denies access. */ function assertNodeAccess($ops, $node, $account) { foreach ($ops as $op => $result) { @@ -910,7 +957,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Test case to verify hook_node_access_records functionality. + * Tests hook_node_access_records() functionality. */ class NodeAccessRecordsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -929,7 +976,7 @@ class NodeAccessRecordsTestCase extends DrupalWebTestCase { } /** - * Create a node and test the creation of node access rules. + * Creates a node and tests the creation of node access rules. */ function testNodeAccessRecords() { // Create an article node. @@ -1005,9 +1052,6 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { ); } - /** - * Enable modules and create user with specific permissions. - */ public function setUp() { parent::setUp('node_access_test'); node_access_rebuild(); @@ -1015,7 +1059,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test the "private" node access. + * Tests the "private" node access functionality. * * - Create 2 users with "access content" and "create article" permissions. * - Each user creates one private and one not private article. @@ -1152,7 +1196,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test case to check node save related functionality, including import-save + * Tests node save related functionality, including import-save. */ class NodeSaveTestCase extends DrupalWebTestCase { @@ -1173,7 +1217,8 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Import test, to check if custom node ids are saved properly. + * Checks whether custom node IDs are saved properly during an import operation. + * * Workflow: * - first create a piece of content * - save the content @@ -1207,8 +1252,7 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Check that the "created" and "changed" timestamps are set correctly when - * saving a new node or updating an existing node. + * Verifies accuracy of the "created" and "changed" timestamp functionality. */ function testTimestamps() { // Use the default timestamps. @@ -1307,7 +1351,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Ensure that node type functions (node_type_get_*) work correctly. + * Ensures that node type functions (node_type_get_*) work correctly. * * Load available node types and validate the returned data. */ @@ -1326,7 +1370,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test creating a content type programmatically and via a form. + * Tests creating a content type programmatically and via a form. */ function testNodeTypeCreation() { // Create a content type programmaticaly. @@ -1356,7 +1400,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test editing a node type using the UI. + * Tests editing a node type using the UI. */ function testNodeTypeEditing() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); @@ -1409,7 +1453,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test that node_types_rebuild() correctly handles the 'disabled' flag. + * Tests that node_types_rebuild() correctly handles the 'disabled' flag. */ function testNodeTypeStatus() { // Enable all core node modules, and all types should be active. @@ -1470,7 +1514,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Test node type customizations persist through disable and uninstall. + * Tests that node type customizations persist through disable and uninstall. */ function testNodeTypeCustomizationPersistence() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); @@ -1534,7 +1578,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Rebuild the node_access table. + * Verifies the rebuild functionality for the node_access table. */ class NodeAccessRebuildTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1553,6 +1597,9 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { $this->web_user = $web_user; } + /** + * Tests rebuilding the node access permissions table. + */ function testNodeAccessRebuild() { $this->drupalGet('admin/reports/status'); $this->clickLink(t('Rebuild permissions')); @@ -1562,7 +1609,7 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { } /** - * Test node administration page functionality. + * Tests node administration page functionality. */ class NodeAdminTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1630,6 +1677,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { * Tests content overview with different user permissions. * * Taxonomy filters are tested separately. + * * @see TaxonomyNodeFilterTestCase */ function testContentAdminPages() { @@ -1727,7 +1775,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { } /** - * Test node title. + * Tests node title functionality. */ class NodeTitleTestCase extends DrupalWebTestCase { protected $admin_user; @@ -1747,7 +1795,7 @@ class NodeTitleTestCase extends DrupalWebTestCase { } /** - * Create one node and test if the node title has the correct value. + * Creates one node and tests if the node title has the correct value. */ function testNodeTitle() { // Create "Basic page" content with title. @@ -1790,7 +1838,7 @@ class NodeFeedTestCase extends DrupalWebTestCase { } /** - * Ensure that node_feed accepts and prints extra channel elements. + * Ensures that node_feed() accepts and prints extra channel elements. */ function testNodeFeedExtraChannelElements() { ob_start(); @@ -1822,7 +1870,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } /** - * Test the recent comments block. + * Tests the recent comments block. */ function testRecentNodeBlock() { $this->drupalLogin($this->admin_user); @@ -1935,7 +1983,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } } /** - * Test multistep node forms basic options. + * Tests basic options of multi-step node forms. */ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { public static function getInfo() { @@ -1953,7 +2001,7 @@ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { } /** - * Change the default values of basic options to ensure they persist. + * Tests changing the default values of basic options to ensure they persist. */ function testMultiStepNodeFormBasicOptions() { $edit = array( @@ -1985,7 +2033,7 @@ class NodeBuildContent extends DrupalWebTestCase { } /** - * Test to ensure that a node's content array is rebuilt on every call to node_build_content(). + * Ensures that content array is rebuilt on every call to node_build_content(). */ function testNodeRebuildContent() { $node = $this->drupalCreateNode(); @@ -2065,10 +2113,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user with access. + * Tests 'node_access' query alter, for user with access. * - * Verifies that a non-standard table alias can be used, and that a - * user with node access can view the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * node access can view the nodes. */ function testNodeQueryAlterLowLevelWithAccess() { // User with access should be able to view 4 nodes. @@ -2088,10 +2136,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user without access. + * Tests 'node_access' query alter, for user without access. * - * Verifies that a non-standard table alias can be used, and that a - * user without node access cannot view the nodes. + * Verifies that a non-standard table alias can be used, and that a user + * without node access cannot view the nodes. */ function testNodeQueryAlterLowLevelNoAccess() { // User without access should be able to view 0 nodes. @@ -2111,10 +2159,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for edit access. + * Tests 'node_access' query alter, for edit access. * - * Verifies that a non-standard table alias can be used, and that a - * user with view-only node access cannot edit the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * view-only node access cannot edit the nodes. */ function testNodeQueryAlterLowLevelEditAccess() { // User with view-only access should not be able to edit nodes. @@ -2136,13 +2184,13 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter override. + * Tests 'node_access' query alter override. * * Verifies that node_access_view_all_nodes() is called from - * node_query_node_access_alter(). We do this by checking that - * a user which normally would not have view privileges is able - * to view the nodes when we add a record to {node_access} paired - * with a corresponding privilege in hook_node_grants(). + * node_query_node_access_alter(). We do this by checking that a user who + * normally would not have view privileges is able to view the nodes when we + * add a record to {node_access} paired with a corresponding privilege in + * hook_node_grants(). */ function testNodeQueryAlterOverride() { $record = array( diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index bb7a5a78f8123038947119bc318f46f907046fe6..ae5e14b4190453e1ae961ae04b5ae6432f9cc55c 100644 --- a/modules/node/tests/node_access_test.info +++ b/modules/node/tests/node_access_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/node/tests/node_access_test.module b/modules/node/tests/node_access_test.module index 813bf929b48a6ed84ab9f7a74c4e9ed86d459fda..ec35c41f1f34b0662b2e2205ab3311a65bdb7aab 100644 --- a/modules/node/tests/node_access_test.module +++ b/modules/node/tests/node_access_test.module @@ -2,7 +2,9 @@ /** * @file - * Dummy module implementing node access related hooks to test API interaction + * A dummy module implementing node access related hooks for testing purposes. + * + * A dummy module implementing node access related hooks to test API interaction * with the Node module. This module restricts view permission to those with * a special 'node test view' permission. */ @@ -140,6 +142,8 @@ function node_access_test_page() { * database query is shown, and a list of the node IDs, for debugging purposes. * And if there is a query exception, the page says "Exception" and gives the * error. + * + * @see node_access_test_menu() */ function node_access_entity_test_page() { $output = ''; diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index 005bb4fa5ce11d08072c9b503b349844ba2a2133..d9a7907d352d80cc91ebb56658541a0f13f317dd 100644 --- a/modules/node/tests/node_test.info +++ b/modules/node/tests/node_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/node/tests/node_test.module b/modules/node/tests/node_test.module index a52c1fad002246012a7f0f22f2a289ec80152607..fb6678521742227a6fa574ffc901b88cf4d60a64 100644 --- a/modules/node/tests/node_test.module +++ b/modules/node/tests/node_test.module @@ -2,8 +2,10 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A dummy module for testing node related hooks. + * + * This is a dummy module that implements node related hooks to test API + * interaction with the Node module. */ /** diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index afd1ec761d6775ab6704a8bcc517d1b1a46a0935..cb9ce549425eec721f1eedf4c7f5fe3913e86d3d 100644 --- a/modules/node/tests/node_test_exception.info +++ b/modules/node/tests/node_test_exception.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/node/tests/node_test_exception.module b/modules/node/tests/node_test_exception.module index 0fe9f35ea564e70e15f9c730183c56453488b812..66bc71747100a62379dc97e842a9060fb62746ca 100644 --- a/modules/node/tests/node_test_exception.module +++ b/modules/node/tests/node_test_exception.module @@ -2,8 +2,7 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A module implementing node related hooks to test API interaction. */ /** diff --git a/modules/openid/openid.info b/modules/openid/openid.info index e5ff5015a41a6427801d911bba178cd17e967692..e6f7ad2c64b675e57188a73806c6fc9ffa4a853a 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -5,8 +5,8 @@ package = Core core = 7.x files[] = openid.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/openid/openid.module b/modules/openid/openid.module index a3f4fc8e0e9d97cea352bd42cf828a3782b78786..1f764e04bc691d8fc369a0d2cb95269a05627c58 100644 --- a/modules/openid/openid.module +++ b/modules/openid/openid.module @@ -787,7 +787,21 @@ function openid_authentication_request($claimed_id, $identity, $return_to = '', $request = array_merge($request, module_invoke_all('openid', 'request', $request)); - return $request; + // module_invoke_all() uses array_merge_recursive() which might return nested + // arrays if two or more modules alter a given parameter, resulting in an + // invalid request format. To ensure this doesn't happen, we flatten the returned + // value by taking the last entry in the array if an array is returned. + $flattened_request = array(); + foreach ($request as $key => $value) { + if (is_array($value)) { + $flattened_request[$key] = end($value); + } + else { + $flattened_request[$key] = $value; + } + } + + return $flattened_request; } /** diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index 0205ca82a0f992797c32d8528fddfdbf28ec9347..69b12d1233f9665e6d93977c470c8c67e9d50e5f 100644 --- a/modules/openid/tests/openid_test.info +++ b/modules/openid/tests/openid_test.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 413475977b6587687f56eeda5c0f82ca5d60f05c..7452a5154007ebcea39e7e68ec9c1ac7eca80753 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -630,8 +630,11 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) { $target.attr('href', $.param.querystring(href, { destination: fragmentizedDestination })); } - // Make the link open in the immediate parent of the frame. - $target.attr('target', '_parent'); + // Make the link open in the immediate parent of the frame, unless the + // link already has a different target. + if (!$target.attr('target')) { + $target.attr('target', '_parent'); + } } } } diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index 6dd4b950ec500c023bbbc7705e083cbd092369c9..7e04236df9a043402900fd3abd23a71379f6d05e 100644 --- a/modules/overlay/overlay.info +++ b/modules/overlay/overlay.info @@ -4,8 +4,8 @@ package = Core version = VERSION core = 7.x -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index c07cc6cfa91824e61de84e7aaa4b3cb073718acc..728198680951d794538ccbf3d702171f1cf60c55 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -704,7 +704,7 @@ function overlay_overlay_child_initialize() { } /** - * Requests that the overlay overlay closes when the page is displayed. + * Requests that the overlay closes when the page is displayed. * * @param $redirect * (optional) The path that should open in the parent window after the diff --git a/modules/path/path.info b/modules/path/path.info index 4abfa036bb58a78a83bbb2655ac250ed2bfebd32..acae995aa602e20b7bc7d097f176cef28d800b2c 100644 --- a/modules/path/path.info +++ b/modules/path/path.info @@ -6,8 +6,8 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/php/php.info b/modules/php/php.info index c5e57d5cba14e4145d400a31596eff782316a368..ebf1e36abdc2d24dd2cbb7f41cbc4ba271391ca7 100644 --- a/modules/php/php.info +++ b/modules/php/php.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = php.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/php/php.module b/modules/php/php.module index 996f746ec12a1dc611481c453d4cb0c59705c592..d5ea15287520cb94043e1328b1427543d95fb359 100644 --- a/modules/php/php.module +++ b/modules/php/php.module @@ -122,7 +122,7 @@ else { print t(\'Welcome visitor! Thank you for visiting.\'); } </pre>') . '</li></ul>'; - $output .= '<p>' . t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://http://drupal.org/documentation/customization/php-snippets'))) . '</p>'; + $output .= '<p>' . t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '</p>'; return $output; } else { diff --git a/modules/poll/poll.info b/modules/poll/poll.info index 2ae2ac2a5b6e553cc47b8831398ac9b3a784f849..e86c8701ddedcb4c814dd5cdcfd796cf458a7332 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -6,8 +6,8 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 4867fd3ec06726cd01e9d0b1a98bf629cf8463ab..fb2d2be60b636b6dca622f777eb149dec6ef2ab6 100644 --- a/modules/profile/profile.info +++ b/modules/profile/profile.info @@ -11,8 +11,8 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index 2877b2d0e165f0e40451687e4d4e234ecfef6fa2..3f9b3c6aa1556644cba7ffc855ac686b13592b38 100644 --- a/modules/rdf/rdf.info +++ b/modules/rdf/rdf.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/rdf/rdf.module b/modules/rdf/rdf.module index e491c31d1d59c63d7f690bc78b772ca80eea2179..e22d5a93f40da9371791f052da229c31d1c48826 100644 --- a/modules/rdf/rdf.module +++ b/modules/rdf/rdf.module @@ -646,10 +646,12 @@ function rdf_preprocess_username(&$variables) { if (!empty($rdf_mapping['rdftype'])) { $attributes['typeof'] = $rdf_mapping['rdftype']; } - // Annotate the user name in RDFa. The property attribute is used here - // because the user name is a literal. + // Annotate the username in RDFa. A property attribute is used with an empty + // datatype attribute to ensure the username is parsed as a plain literal + // in RDFa 1.0 and 1.1. if (!empty($rdf_mapping['name'])) { $attributes['property'] = $rdf_mapping['name']['predicates']; + $attributes['datatype'] = ''; } // Add the homepage RDFa markup if present. if (!empty($variables['homepage']) && !empty($rdf_mapping['homepage'])) { @@ -757,7 +759,10 @@ function rdf_field_attach_view_alter(&$output, $context) { $element[$delta]['#options']['attributes']['typeof'] = $term->rdf_mapping['rdftype']; } if (!empty($term->rdf_mapping['name']['predicates'])) { + // A property attribute is used with an empty datatype attribute so + // the term name is parsed as a plain literal in RDFa 1.0 and 1.1. $element[$delta]['#options']['attributes']['property'] = $term->rdf_mapping['name']['predicates']; + $element[$delta]['#options']['attributes']['datatype'] = ''; } } } diff --git a/modules/rdf/rdf.test b/modules/rdf/rdf.test index b8aeb90a1312be0e533aabc0f77d944f056dbe7d..4d73377f5501516c88a5f2ee6e6974287f3ab796 100644 --- a/modules/rdf/rdf.test +++ b/modules/rdf/rdf.test @@ -24,17 +24,17 @@ class RdfMappingHookTestCase extends DrupalWebTestCase { function testMapping() { // Test that the mapping is returned correctly by the hook. $mapping = rdf_mapping_load('test_entity', 'test_bundle'); - $this->assertIdentical($mapping['rdftype'], array('sioc:Post'), t('Mapping for rdftype is sioc:Post.')); - $this->assertIdentical($mapping['title'], array('predicates' => array('dc:title')), t('Mapping for title is dc:title.')); + $this->assertIdentical($mapping['rdftype'], array('sioc:Post'), 'Mapping for rdftype is sioc:Post.'); + $this->assertIdentical($mapping['title'], array('predicates' => array('dc:title')), 'Mapping for title is dc:title.'); $this->assertIdentical($mapping['created'], array( 'predicates' => array('dc:created'), 'datatype' => 'xsd:dateTime', 'callback' => 'date_iso8601', ), t('Mapping for created is dc:created with datatype xsd:dateTime and callback date_iso8601.')); - $this->assertIdentical($mapping['uid'], array('predicates' => array('sioc:has_creator', 'dc:creator'), 'type' => 'rel'), t('Mapping for uid is sioc:has_creator and dc:creator, and type is rel.')); + $this->assertIdentical($mapping['uid'], array('predicates' => array('sioc:has_creator', 'dc:creator'), 'type' => 'rel'), 'Mapping for uid is sioc:has_creator and dc:creator, and type is rel.'); $mapping = rdf_mapping_load('test_entity', 'test_bundle_no_mapping'); - $this->assertEqual($mapping, array(), t('Empty array returned when an entity type, bundle pair has no mapping.')); + $this->assertEqual($mapping, array(), 'Empty array returned when an entity type, bundle pair has no mapping.'); } } @@ -179,13 +179,13 @@ class RdfRdfaMarkupTestCase extends DrupalWebTestCase { $file_rel = $this->xpath('//div[contains(@about, :node-uri)]//div[contains(@rel, "rdfs:seeAlso") and contains(@resource, ".txt")]', array( ':node-uri' => 'node/' . $nid, )); - $this->assertTrue(!empty($file_rel), t('Attribute \'rel\' set on file field. Attribute \'resource\' is also set.')); + $this->assertTrue(!empty($file_rel), "Attribute 'rel' set on file field. Attribute 'resource' is also set."); $image_rel = $this->xpath('//div[contains(@about, :node-uri)]//div[contains(@rel, "rdfs:seeAlso") and contains(@resource, :image)]//img[contains(@typeof, "foaf:Image")]', array( ':node-uri' => 'node/' . $nid, ':image' => $image_filename, )); - $this->assertTrue(!empty($image_rel), t('Attribute \'rel\' set on image field. Attribute \'resource\' is also set.')); + $this->assertTrue(!empty($image_rel), "Attribute 'rel' set on image field. Attribute 'resource' is also set."); // Edits the node to add tags. $tag1 = $this->randomName(8); @@ -195,16 +195,16 @@ class RdfRdfaMarkupTestCase extends DrupalWebTestCase { $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); // Ensures the RDFa markup for the relationship between the node and its // tags is correct. - $term_rdfa_meta = $this->xpath('//div[@about=:node-url and contains(@typeof, "sioc:Item") and contains(@typeof, "foaf:Document")]//ul[@class="links"]/li[@rel="dc:subject"]/a[@typeof="skos:Concept" and text()=:term-name]', array( + $term_rdfa_meta = $this->xpath('//div[@about=:node-url and contains(@typeof, "sioc:Item") and contains(@typeof, "foaf:Document")]//ul[@class="links"]/li[@rel="dc:subject"]/a[@typeof="skos:Concept" and @datatype="" and text()=:term-name]', array( ':node-url' => url('node/' . $node->nid), ':term-name' => $tag1, )); - $this->assertTrue(!empty($term_rdfa_meta), t('Property dc:subject is present for the tag1 field item.')); - $term_rdfa_meta = $this->xpath('//div[@about=:node-url and contains(@typeof, "sioc:Item") and contains(@typeof, "foaf:Document")]//ul[@class="links"]/li[@rel="dc:subject"]/a[@typeof="skos:Concept" and text()=:term-name]', array( + $this->assertTrue(!empty($term_rdfa_meta), 'Property dc:subject is present for the tag1 field item.'); + $term_rdfa_meta = $this->xpath('//div[@about=:node-url and contains(@typeof, "sioc:Item") and contains(@typeof, "foaf:Document")]//ul[@class="links"]/li[@rel="dc:subject"]/a[@typeof="skos:Concept" and @datatype="" and text()=:term-name]', array( ':node-url' => url('node/' . $node->nid), ':term-name' => $tag2, )); - $this->assertTrue(!empty($term_rdfa_meta), t('Property dc:subject is present for the tag2 field item.')); + $this->assertTrue(!empty($term_rdfa_meta), 'Property dc:subject is present for the tag2 field item.'); } } @@ -227,7 +227,7 @@ class RdfCrudTestCase extends DrupalWebTestCase { function testCRUD() { // Verify loading of a default mapping. $mapping = _rdf_mapping_load('test_entity', 'test_bundle'); - $this->assertTrue(count($mapping), t('Default mapping was found.')); + $this->assertTrue(count($mapping), 'Default mapping was found.'); // Verify saving a mapping. $mapping = array( @@ -244,35 +244,35 @@ class RdfCrudTestCase extends DrupalWebTestCase { ), ), ); - $this->assertTrue(rdf_mapping_save($mapping) === SAVED_NEW, t('Mapping was saved.')); + $this->assertTrue(rdf_mapping_save($mapping) === SAVED_NEW, 'Mapping was saved.'); // Read the raw record from the {rdf_mapping} table. $result = db_query('SELECT * FROM {rdf_mapping} WHERE type = :type AND bundle = :bundle', array(':type' => $mapping['type'], ':bundle' => $mapping['bundle'])); $stored_mapping = $result->fetchAssoc(); $stored_mapping['mapping'] = unserialize($stored_mapping['mapping']); - $this->assertEqual($mapping, $stored_mapping, t('Mapping was stored properly in the {rdf_mapping} table.')); + $this->assertEqual($mapping, $stored_mapping, 'Mapping was stored properly in the {rdf_mapping} table.'); // Verify loading of saved mapping. - $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), t('Saved mapping loaded successfully.')); + $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Saved mapping loaded successfully.'); // Verify updating of mapping. $mapping['mapping']['title'] = array( 'predicates' => array('dc2:bar2'), ); - $this->assertTrue(rdf_mapping_save($mapping) === SAVED_UPDATED, t('Mapping was updated.')); + $this->assertTrue(rdf_mapping_save($mapping) === SAVED_UPDATED, 'Mapping was updated.'); // Read the raw record from the {rdf_mapping} table. $result = db_query('SELECT * FROM {rdf_mapping} WHERE type = :type AND bundle = :bundle', array(':type' => $mapping['type'], ':bundle' => $mapping['bundle'])); $stored_mapping = $result->fetchAssoc(); $stored_mapping['mapping'] = unserialize($stored_mapping['mapping']); - $this->assertEqual($mapping, $stored_mapping, t('Updated mapping was stored properly in the {rdf_mapping} table.')); + $this->assertEqual($mapping, $stored_mapping, 'Updated mapping was stored properly in the {rdf_mapping} table.'); // Verify loading of saved mapping. - $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), t('Saved mapping loaded successfully.')); + $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Saved mapping loaded successfully.'); // Verify deleting of mapping. - $this->assertTrue(rdf_mapping_delete($mapping['type'], $mapping['bundle']), t('Mapping was deleted.')); - $this->assertFalse(_rdf_mapping_load($mapping['type'], $mapping['bundle']), t('Deleted mapping is no longer found in the database.')); + $this->assertTrue(rdf_mapping_delete($mapping['type'], $mapping['bundle']), 'Mapping was deleted.'); + $this->assertFalse(_rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Deleted mapping is no longer found in the database.'); } } @@ -303,8 +303,8 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { // from the node default bundle definition. $blog_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']"); $blog_meta = $this->xpath("//div[(@about='$url') and (@typeof='sioct:Weblog')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']"); - $this->assertTrue(!empty($blog_title), t('Property dc:title is present in meta tag.')); - $this->assertTrue(!empty($blog_meta), t('RDF type is present on post. Properties dc:date and dc:created are present on post date.')); + $this->assertTrue(!empty($blog_title), 'Property dc:title is present in meta tag.'); + $this->assertTrue(!empty($blog_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.'); } /** @@ -321,8 +321,8 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { // Ensure the mapping defined in rdf_module.test is used. $test_bundle_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']"); $test_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'foo:mapping_install1') and contains(@typeof, 'bar:mapping_install2')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']"); - $this->assertTrue(!empty($test_bundle_title), t('Property dc:title is present in meta tag.')); - $this->assertTrue(!empty($test_bundle_meta), t('RDF type is present on post. Properties dc:date and dc:created are present on post date.')); + $this->assertTrue(!empty($test_bundle_title), 'Property dc:title is present in meta tag.'); + $this->assertTrue(!empty($test_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.'); } /** @@ -340,8 +340,8 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { // from the node default bundle definition. $random_bundle_title = $this->xpath("//meta[@property='dc:title' and @content='$node->title']"); $random_bundle_meta = $this->xpath("//div[(@about='$url') and contains(@typeof, 'sioc:Item') and contains(@typeof, 'foaf:Document')]//span[contains(@property, 'dc:date') and contains(@property, 'dc:created') and @datatype='xsd:dateTime' and @content='$isoDate']"); - $this->assertTrue(!empty($random_bundle_title), t('Property dc:title is present in meta tag.')); - $this->assertTrue(!empty($random_bundle_meta), t('RDF type is present on post. Properties dc:date and dc:created are present on post date.')); + $this->assertTrue(!empty($random_bundle_title), 'Property dc:title is present in meta tag.'); + $this->assertTrue(!empty($random_bundle_meta), 'RDF type is present on post. Properties dc:date and dc:created are present on post date.'); } /** @@ -363,19 +363,19 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { $user2_profile_about = $this->xpath('//div[@class="profile" and @typeof="sioc:UserAccount" and @about=:account-uri]', array( ':account-uri' => $account_uri, )); - $this->assertTrue(!empty($user2_profile_about), t('RDFa markup found on user profile page')); + $this->assertTrue(!empty($user2_profile_about), 'RDFa markup found on user profile page'); $user_account_holder = $this->xpath('//meta[contains(@typeof, "foaf:Person") and @about=:person-uri and @resource=:account-uri and contains(@rel, "foaf:account")]', array( ':person-uri' => $person_uri, ':account-uri' => $account_uri, )); - $this->assertTrue(!empty($user_account_holder), t('URI created for account holder and username set on sioc:UserAccount.')); + $this->assertTrue(!empty($user_account_holder), 'URI created for account holder and username set on sioc:UserAccount.'); $user_username = $this->xpath('//meta[@about=:account-uri and contains(@property, "foaf:name") and @content=:username]', array( ':account-uri' => $account_uri, ':username' => $username, )); - $this->assertTrue(!empty($user_username), t('foaf:name set on username.')); + $this->assertTrue(!empty($user_username), 'foaf:name set on username.'); // User 2 creates node. $this->drupalLogin($user2); @@ -384,10 +384,10 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { $this->drupalGet('node/' . $node->nid); // Ensures the default bundle mapping for user is used on the Authored By // information on the node. - $author_about = $this->xpath('//a[@typeof="sioc:UserAccount" and @about=:account-uri and @property="foaf:name" and contains(@xml:lang, "")]', array( + $author_about = $this->xpath('//a[@typeof="sioc:UserAccount" and @about=:account-uri and @property="foaf:name" and @datatype="" and contains(@xml:lang, "")]', array( ':account-uri' => $account_uri, )); - $this->assertTrue(!empty($author_about), t('RDFa markup found on author information on post. xml:lang on username is set to empty string.')); + $this->assertTrue(!empty($author_about), 'RDFa markup found on author information on post. xml:lang on username is set to empty string.'); } /** @@ -405,7 +405,7 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { ':term-url' => $term_url, ':term-name' => $term_name, )); - $this->assertTrue(!empty($term_rdfa_meta), t('RDFa markup found on term page.')); + $this->assertTrue(!empty($term_rdfa_meta), 'RDFa markup found on term page.'); } } @@ -457,15 +457,15 @@ class RdfCommentAttributesTestCase extends CommentHelperCase { // Tests number of comments in teaser view. $this->drupalGet('node'); $comment_count_teaser = $this->xpath('//div[contains(@typeof, "sioc:Item")]//li[contains(@class, "comment-comments")]/a[contains(@property, "sioc:num_replies") and contains(@content, "2") and @datatype="xsd:integer"]'); - $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on teaser view.')); + $this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on teaser view.'); $comment_count_link = $this->xpath('//div[@about=:url]//a[contains(@property, "sioc:num_replies") and @rel=""]', array(':url' => url("node/{$this->node1->nid}"))); - $this->assertTrue(!empty($comment_count_link), t('Empty rel attribute found in comment count link.')); + $this->assertTrue(!empty($comment_count_link), 'Empty rel attribute found in comment count link.'); // Tests number of comments in full node view. $this->drupalGet('node/' . $this->node1->nid); $node_url = url('node/' . $this->node1->nid); $comment_count_teaser = $this->xpath('/html/head/meta[@about=:node-url and @property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url)); - $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on full node view.')); + $this->assertTrue(!empty($comment_count_teaser), 'RDFa markup for the number of comments found on full node view.'); } /** @@ -502,22 +502,22 @@ class RdfCommentAttributesTestCase extends CommentHelperCase { // Tests comment #2 as anonymous user. $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); // Tests the RDFa markup for the homepage (specific to anonymous comments). - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); - $this->assertTrue(!empty($comment_homepage), t('RDFa markup for the homepage of anonymous user found.')); + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @datatype="" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); + $this->assertTrue(!empty($comment_homepage), 'RDFa markup for the homepage of anonymous user found.'); // There should be no about attribute on anonymous comments. $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); - $this->assertTrue(empty($comment_homepage), t('No about attribute is present on anonymous user comment.')); + $this->assertTrue(empty($comment_homepage), 'No about attribute is present on anonymous user comment.'); // Tests comment #2 as logged in user. $this->drupalLogin($this->web_user); $this->drupalGet('node/' . $this->node2->nid); $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); // Tests the RDFa markup for the homepage (specific to anonymous comments). - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); - $this->assertTrue(!empty($comment_homepage), t("RDFa markup for the homepage of anonymous user found.")); + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @datatype="" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); + $this->assertTrue(!empty($comment_homepage), "RDFa markup for the homepage of anonymous user found."); // There should be no about attribute on anonymous comments. $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); - $this->assertTrue(empty($comment_homepage), t("No about attribute is present on anonymous user comment.")); + $this->assertTrue(empty($comment_homepage), "No about attribute is present on anonymous user comment."); } /** @@ -530,9 +530,9 @@ class RdfCommentAttributesTestCase extends CommentHelperCase { // Tests the reply_of relationship of a first level comment. $result = $this->xpath("(id('comments')//div[contains(@class,'comment ')])[position()=1]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); + $this->assertEqual(1, count($result), 'RDFa markup referring to the node is present.'); $result = $this->xpath("(id('comments')//div[contains(@class,'comment ')])[position()=1]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1#comment-1'))); - $this->assertFalse($result, t('No RDFa markup referring to the comment itself is present.')); + $this->assertFalse($result, 'No RDFa markup referring to the comment itself is present.'); // Posts a reply to the first comment. $this->drupalGet('comment/reply/' . $this->node1->nid . '/' . $comments[0]->id); @@ -540,9 +540,9 @@ class RdfCommentAttributesTestCase extends CommentHelperCase { // Tests the reply_of relationship of a second level comment. $result = $this->xpath("(id('comments')//div[contains(@class,'comment ')])[position()=2]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); + $this->assertEqual(1, count($result), 'RDFa markup referring to the node is present.'); $result = $this->xpath("(id('comments')//div[contains(@class,'comment ')])[position()=2]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1', array('fragment' => 'comment-1')))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the parent comment is present.')); + $this->assertEqual(1, count($result), 'RDFa markup referring to the parent comment is present.'); $comments = $this->xpath("(id('comments')//div[contains(@class,'comment ')])[position()=2]"); } @@ -558,17 +558,17 @@ class RdfCommentAttributesTestCase extends CommentHelperCase { */ function _testBasicCommentRdfaMarkup($comment, $account = array()) { $comment_container = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]'); - $this->assertTrue(!empty($comment_container), t("Comment RDF type for comment found.")); + $this->assertTrue(!empty($comment_container), "Comment RDF type for comment found."); $comment_title = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//h3[@property="dc:title"]'); - $this->assertEqual((string)$comment_title[0]->a, $comment->subject, t("RDFa markup for the comment title found.")); + $this->assertEqual((string)$comment_title[0]->a, $comment->subject, "RDFa markup for the comment title found."); $comment_date = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//*[contains(@property, "dc:date") and contains(@property, "dc:created")]'); - $this->assertTrue(!empty($comment_date), t("RDFa markup for the date of the comment found.")); + $this->assertTrue(!empty($comment_date), "RDFa markup for the date of the comment found."); // The author tag can be either a or span - $comment_author = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/*[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name"]'); + $comment_author = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/*[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @datatype=""]'); $name = empty($account["name"]) ? $this->web_user->name : $account["name"] . " (not verified)"; - $this->assertEqual((string)$comment_author[0], $name, t("RDFa markup for the comment author found.")); + $this->assertEqual((string)$comment_author[0], $name, "RDFa markup for the comment author found."); $comment_body = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//div[@class="content"]//div[contains(@class, "comment-body")]//div[@property="content:encoded"]'); - $this->assertEqual((string)$comment_body[0]->p, $comment->comment, t("RDFa markup for the comment body found.")); + $this->assertEqual((string)$comment_body[0]->p, $comment->comment, "RDFa markup for the comment body found."); } } @@ -629,35 +629,35 @@ class RdfTrackerAttributesTestCase extends DrupalWebTestCase { // success of the following tests, but making it explicit will make // debugging easier in case of failure. $tracker_about = $this->xpath('//tr[@about=:url]', array(':url' => $url)); - $this->assertTrue(!empty($tracker_about), t('About attribute found on table row for @user content.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_about), format_string('About attribute found on table row for @user content.', array('@user'=> $user))); // Tests whether the title has the correct property attribute. $tracker_title = $this->xpath('//tr[@about=:url]/td[@property="dc:title" and @datatype=""]', array(':url' => $url)); - $this->assertTrue(!empty($tracker_title), t('Title property attribute found on @user content.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_title), format_string('Title property attribute found on @user content.', array('@user'=> $user))); // Tests whether the relationship between the content and user has been set. $tracker_user = $this->xpath('//tr[@about=:url]//td[contains(@rel, "sioc:has_creator")]//*[contains(@typeof, "sioc:UserAccount") and contains(@property, "foaf:name")]', array(':url' => $url)); - $this->assertTrue(!empty($tracker_user), t('Typeof and name property attributes found on @user.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_user), format_string('Typeof and name property attributes found on @user.', array('@user'=> $user))); // There should be an about attribute on logged in users and no about // attribute for anonymous users. $tracker_user = $this->xpath('//tr[@about=:url]//td[@rel="sioc:has_creator"]/*[@about]', array(':url' => $url)); if ($node->uid == 0) { - $this->assertTrue(empty($tracker_user), t('No about attribute is present on @user.', array('@user'=> $user))); + $this->assertTrue(empty($tracker_user), format_string('No about attribute is present on @user.', array('@user'=> $user))); } elseif ($node->uid > 0) { - $this->assertTrue(!empty($tracker_user), t('About attribute is present on @user.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_user), format_string('About attribute is present on @user.', array('@user'=> $user))); } // Tests whether the property has been set for number of comments. $tracker_replies = $this->xpath('//tr[@about=:url]//td[contains(@property, "sioc:num_replies") and contains(@content, "0") and @datatype="xsd:integer"]', array(':url' => $url)); - $this->assertTrue($tracker_replies, t('Num replies property and content attributes found on @user content.', array('@user'=> $user))); + $this->assertTrue($tracker_replies, format_string('Num replies property and content attributes found on @user content.', array('@user'=> $user))); // Tests that the appropriate RDFa markup to annotate the latest activity // date has been added to the tracker output before comments have been // posted, meaning the latest activity reflects changes to the node itself. $isoDate = date('c', $node->changed); $tracker_activity = $this->xpath('//tr[@about=:url]//td[contains(@property, "dc:modified") and contains(@property, "sioc:last_activity_date") and contains(@datatype, "xsd:dateTime") and @content=:date]', array(':url' => $url, ':date' => $isoDate)); - $this->assertTrue(!empty($tracker_activity), t('Latest activity date and changed properties found when there are no comments on @user content. Latest activity date content is correct.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_activity), format_string('Latest activity date and changed properties found when there are no comments on @user content. Latest activity date content is correct.', array('@user'=> $user))); // Tests that the appropriate RDFa markup to annotate the latest activity // date has been added to the tracker output after a comment is posted. @@ -670,7 +670,7 @@ class RdfTrackerAttributesTestCase extends DrupalWebTestCase { // Tests whether the property has been set for number of comments. $tracker_replies = $this->xpath('//tr[@about=:url]//td[contains(@property, "sioc:num_replies") and contains(@content, "1") and @datatype="xsd:integer"]', array(':url' => $url)); - $this->assertTrue($tracker_replies, t('Num replies property and content attributes found on @user content.', array('@user'=> $user))); + $this->assertTrue($tracker_replies, format_string('Num replies property and content attributes found on @user content.', array('@user'=> $user))); // Need to query database directly to obtain last_activity_date because // it cannot be accessed via node_load(). @@ -680,7 +680,7 @@ class RdfTrackerAttributesTestCase extends DrupalWebTestCase { } $isoDate = date('c', $expected_last_activity_date); $tracker_activity = $this->xpath('//tr[@about=:url]//td[@property="sioc:last_activity_date" and @datatype="xsd:dateTime" and @content=:date]', array(':url' => $url, ':date' => $isoDate)); - $this->assertTrue(!empty($tracker_activity), t('Latest activity date found when there are comments on @user content. Latest activity date content is correct.', array('@user'=> $user))); + $this->assertTrue(!empty($tracker_activity), format_string('Latest activity date found when there are comments on @user content. Latest activity date content is correct.', array('@user'=> $user))); } } @@ -707,9 +707,9 @@ class RdfGetRdfNamespacesTestCase extends DrupalWebTestCase { // Get all RDF namespaces. $ns = rdf_get_namespaces(); - $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', t('A prefix declared once is included.')); - $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', t('The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.')); - $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', t('Two prefixes can be assigned the same namespace.')); - $this->assertTrue(!isset($ns['dc']), t('A prefix with conflicting namespaces is discarded.')); + $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', 'A prefix declared once is included.'); + $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', 'The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.'); + $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', 'Two prefixes can be assigned the same namespace.'); + $this->assertTrue(!isset($ns['dc']), 'A prefix with conflicting namespaces is discarded.'); } } diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index fb18967d9f3fe8e8ec4302a9adbc3135795be60a..77c3d8ec054c8e1e7126b5ae2c75d2376487ec4b 100644 --- a/modules/rdf/tests/rdf_test.info +++ b/modules/rdf/tests/rdf_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/search/search.info b/modules/search/search.info index 780c52f8daf0948ca673fe7334719f267589a386..2cac42c91aa6e77193620bd551349d3de03f3e21 100644 --- a/modules/search/search.info +++ b/modules/search/search.info @@ -8,8 +8,8 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc index b74847303bae31392b553a735aa36f40f99d0d93..9dd00a6de7e35749c479c189761370c68d988dd4 100644 --- a/modules/search/search.pages.inc +++ b/modules/search/search.pages.inc @@ -15,7 +15,6 @@ */ function search_view($module = NULL, $keys = '') { $info = FALSE; - $redirect = FALSE; $keys = trim($keys); // Also try to pull search keywords out of the $_REQUEST variable to // support old GET format of searches for existing links. diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info index 1ea22344c3480eebb16b6a0876f0183b18c30982..6561c4433cdfafee089047738842e99a08f7d7b4 100644 --- a/modules/search/tests/search_embedded_form.info +++ b/modules/search/tests/search_embedded_form.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info index fce90a5d59ffdb4ef809dbd6885f9024aeded5e7..7b28ee94108c5fad69bf98003550a094177d0bff 100644 --- a/modules/search/tests/search_extra_type.info +++ b/modules/search/tests/search_extra_type.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index e27160ce03d77066f3a1a7f58c93da188180c9d8..07fe05acb2fb1c7c8f75b8a83d201789e1bea130 100644 --- a/modules/shortcut/shortcut.info +++ b/modules/shortcut/shortcut.info @@ -6,8 +6,8 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e3cab62a276609d15fe3ca987ecf466e46680ce9..0853c7d9627cef39c449fae164dd64cfc470cece 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -447,7 +447,8 @@ abstract class DrupalTestCase { */ protected function verbose($message) { if ($id = simpletest_verbose($message)) { - $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html'); + $class_safe = str_replace('\\', '_', get_class($this)); + $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); } } @@ -466,7 +467,8 @@ abstract class DrupalTestCase { */ public function run(array $methods = array()) { // Initialize verbose debugging. - simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), get_class($this)); + $class = get_class($this); + simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '_', $class)); // HTTP auth settings (<username>:<password>) for the simpletest browser // when sending requests to the test site. @@ -478,7 +480,6 @@ abstract class DrupalTestCase { } set_error_handler(array($this, 'errorHandler')); - $class = get_class($this); // Iterate through all the methods in this class, unless a specific list of // methods to run was passed. $class_methods = get_class_methods($class); @@ -1217,28 +1218,28 @@ class DrupalWebTestCase extends DrupalTestCase { * $account->pass_raw = $pass_raw; * @endcode * - * @param $user + * @param $account * User object representing the user to log in. * * @see drupalCreateUser() */ - protected function drupalLogin(stdClass $user) { + protected function drupalLogin(stdClass $account) { if ($this->loggedInUser) { $this->drupalLogout(); } $edit = array( - 'name' => $user->name, - 'pass' => $user->pass_raw + 'name' => $account->name, + 'pass' => $account->pass_raw ); $this->drupalPost('user', $edit, t('Log in')); // If a "log out" link appears on the page, it is almost certainly because // the login was successful. - $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login')); + $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $account->name)), t('User login')); if ($pass) { - $this->loggedInUser = $user; + $this->loggedInUser = $account; } } @@ -2646,10 +2647,9 @@ class DrupalWebTestCase extends DrupalTestCase { /** * Follows a link by name. * - * Will click the first link found with this link text by default, or a - * later one if an index is given. Match is case insensitive with - * normalized space. The label is translated label. There is an assert - * for successful click. + * Will click the first link found with this link text by default, or a later + * one if an index is given. Match is case sensitive with normalized space. + * The label is translated label. There is an assert for successful click. * * @param $label * Text between the anchor tags. @@ -3148,6 +3148,42 @@ class DrupalWebTestCase extends DrupalTestCase { return $this->assertNotEqual($actual, $title, $message, $group); } + /** + * Asserts themed output. + * + * @param $callback + * The name of the theme function to invoke; e.g. 'links' for theme_links(). + * @param $variables + * An array of variables to pass to the theme function. + * @param $expected + * The expected themed output string. + * @param $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '', $group = 'Other') { + $output = theme($callback, $variables); + $this->verbose('Variables:' . '<pre>' . check_plain(var_export($variables, TRUE)) . '</pre>' + . '<hr />' . 'Result:' . '<pre>' . check_plain(var_export($output, TRUE)) . '</pre>' + . '<hr />' . 'Expected:' . '<pre>' . check_plain(var_export($expected, TRUE)) . '</pre>' + . '<hr />' . $output + ); + if (!$message) { + $message = '%callback rendered correctly.'; + } + $message = format_string($message, array('%callback' => 'theme_' . $callback . '()')); + return $this->assertIdentical($output, $expected, $message, $group); + } + /** * Asserts that a field exists in the current page by the given XPath. * diff --git a/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php b/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0292956ced7b1da51d39851a8dc4af7ce11b7f82 --- /dev/null +++ b/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\simpletest\Tests; + +class PSR0WebTest extends \DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'PSR0 web test', + 'description' => 'We want to assert that this PSR-0 test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index 687f2368df7f5ae7fca41d272951707b329a53e6..ebebb816363fb73d94d1a70a7d5d04e4b4a5d6ec 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -55,8 +55,8 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index f82575573a0571417add747c19714c057e7ec8db..3103af0e885903daedf32d1c4e6a68650cf32383 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -157,6 +157,7 @@ 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. @@ -289,6 +290,9 @@ 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 @@ -309,6 +313,9 @@ function simpletest_test_get_all() { $groups = &drupal_static(__FUNCTION__); if (!$groups) { + // Register a simple class loader for PSR-0 test classes. + simpletest_classloader_register(); + // Load test information from cache if available, otherwise retrieve the // information from each tests getInfo() method. if ($cache = cache_get('simpletest', 'cache')) { @@ -318,6 +325,34 @@ function simpletest_test_get_all() { // Select all clases in files ending with .test. $classes = db_query("SELECT name FROM {registry} WHERE type = :type AND filename LIKE :name", array(':type' => 'class', ':name' => '%.test'))->fetchCol(); + // Also discover PSR-0 test classes, if the PHP version allows it. + if (version_compare(PHP_VERSION, '5.3') > 0) { + + // 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(); @@ -353,6 +388,78 @@ function simpletest_test_get_all() { return $groups; } +/* + * Register a simple class loader that can find D8-style PSR-0 test classes. + * + * Other PSR-0 class loading can happen in contrib, but those contrib class + * loader modules will not be enabled when testbot runs. So we need to do this + * one in core. + */ +function simpletest_classloader_register() { + + // Prevent duplicate classloader registration. + static $first_run = TRUE; + if (!$first_run) { + return; + } + $first_run = FALSE; + + // Only register PSR-0 class loading if we are on PHP 5.3 or higher. + if (version_compare(PHP_VERSION, '5.3') > 0) { + spl_autoload_register('_simpletest_autoload_psr0'); + } +} + +/** + * Autoload callback to find PSR-0 test classes. + * + * This will only work on classes where the namespace is of the pattern + * "Drupal\$extension\Tests\.." + */ +function _simpletest_autoload_psr0($class) { + + // Static cache for extension paths. + // This cache is lazily filled as soon as it is needed. + static $extensions; + + // Check that the first namespace fragment is "Drupal\" + if (substr($class, 0, 7) === 'Drupal\\') { + // Find the position of the second namespace separator. + $pos = strpos($class, '\\', 7); + // Check that the third namespace fragment is "\Tests\". + if (substr($class, $pos, 7) === '\\Tests\\') { + + // Extract the second namespace fragment, which we expect to be the + // extension name. + $extension = substr($class, 7, $pos - 7); + + // Lazy-load the extension paths, both enabled and disabled. + if (!isset($extensions)) { + $extensions = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed(); + } + + // Check if the second namespace fragment is a known extension name. + if (isset($extensions[$extension])) { + + // Split the class into namespace and classname. + $nspos = strrpos($class, '\\'); + $namespace = substr($class, 0, $nspos); + $classname = substr($class, $nspos + 1); + + // Build the filepath where we expect the class to be defined. + $path = dirname($extensions[$extension]) . '/lib/' . + str_replace('\\', '/', $namespace) . '/' . + str_replace('_', '/', $classname) . '.php'; + + // Include the file, if it does exist. + if (file_exists($path)) { + include $path; + } + } + } + } +} + /** * Implements hook_registry_files_alter(). * diff --git a/modules/simpletest/simpletest.pages.inc b/modules/simpletest/simpletest.pages.inc index d1a7e4aded95017fe33dde58f2e70bd76fa8ee65..3127459e2f72759bb998d94406c2540e5a48a100 100644 --- a/modules/simpletest/simpletest.pages.inc +++ b/modules/simpletest/simpletest.pages.inc @@ -181,6 +181,7 @@ function theme_simpletest_test_table($variables) { * Run selected tests. */ function simpletest_test_form_submit($form, &$form_state) { + simpletest_classloader_register(); // Get list of tests. $tests_list = array(); foreach ($form_state['values'] as $class_name => $value) { @@ -233,6 +234,8 @@ 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/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index c67b004ead7fc5c8745bb80945c8423ea2bcc90c..dde162ec7ffe53a6e5ecda351980c36329ba8177 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -45,9 +45,9 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { global $conf; if (!$this->inCURL()) { $this->drupalGet('node'); - $this->assertTrue($this->drupalGetHeader('Date'), t('An HTTP header was received.')); - $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('Site title matches.')); - $this->assertNoTitle('Foo', t('Site title does not match.')); + $this->assertTrue($this->drupalGetHeader('Date'), 'An HTTP header was received.'); + $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.'); + $this->assertNoTitle('Foo', 'Site title does not match.'); // Make sure that we are locked out of the installer when prefixing // using the user-agent header. This is an important security check. global $base_url; @@ -58,12 +58,12 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { $user = $this->drupalCreateUser(); $this->drupalLogin($user); $headers = $this->drupalGetHeaders(TRUE); - $this->assertEqual(count($headers), 2, t('There was one intermediate request.')); - $this->assertTrue(strpos($headers[0][':status'], '302') !== FALSE, t('Intermediate response code was 302.')); - $this->assertFalse(empty($headers[0]['location']), t('Intermediate request contained a Location header.')); - $this->assertEqual($this->getUrl(), $headers[0]['location'], t('HTTP redirect was followed')); - $this->assertFalse($this->drupalGetHeader('Location'), t('Headers from intermediate request were reset.')); - $this->assertResponse(200, t('Response code from intermediate request was reset.')); + $this->assertEqual(count($headers), 2, 'There was one intermediate request.'); + $this->assertTrue(strpos($headers[0][':status'], '302') !== FALSE, 'Intermediate response code was 302.'); + $this->assertFalse(empty($headers[0]['location']), 'Intermediate request contained a Location header.'); + $this->assertEqual($this->getUrl(), $headers[0]['location'], 'HTTP redirect was followed'); + $this->assertFalse($this->drupalGetHeader('Location'), 'Headers from intermediate request were reset.'); + $this->assertResponse(200, 'Response code from intermediate request was reset.'); // Test the maximum redirection option. $this->drupalLogout(); @@ -74,7 +74,7 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { variable_set('simpletest_maximum_redirects', 1); $this->drupalPost('user?destination=user/logout', $edit, t('Log in')); $headers = $this->drupalGetHeaders(TRUE); - $this->assertEqual(count($headers), 2, t('Simpletest stopped following redirects after the first one.')); + $this->assertEqual(count($headers), 2, 'Simpletest stopped following redirects after the first one.'); } } @@ -88,30 +88,30 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { $HTTP_path = $simpletest_path .'/tests/http.php?q=node'; $https_path = $simpletest_path .'/tests/https.php?q=node'; // Generate a valid simpletest User-Agent to pass validation. - $this->assertTrue(preg_match('/simpletest\d+/', $this->databasePrefix, $matches), t('Database prefix contains simpletest prefix.')); + $this->assertTrue(preg_match('/simpletest\d+/', $this->databasePrefix, $matches), 'Database prefix contains simpletest prefix.'); $test_ua = drupal_generate_test_ua($matches[0]); $this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua); // Test pages only available for testing. $this->drupalGet($HTTP_path); - $this->assertResponse(200, t('Requesting http.php with a legitimate simpletest User-Agent returns OK.')); + $this->assertResponse(200, 'Requesting http.php with a legitimate simpletest User-Agent returns OK.'); $this->drupalGet($https_path); - $this->assertResponse(200, t('Requesting https.php with a legitimate simpletest User-Agent returns OK.')); + $this->assertResponse(200, 'Requesting https.php with a legitimate simpletest User-Agent returns OK.'); // Now slightly modify the HMAC on the header, which should not validate. $this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua . 'X'); $this->drupalGet($HTTP_path); - $this->assertResponse(403, t('Requesting http.php with a bad simpletest User-Agent fails.')); + $this->assertResponse(403, 'Requesting http.php with a bad simpletest User-Agent fails.'); $this->drupalGet($https_path); - $this->assertResponse(403, t('Requesting https.php with a bad simpletest User-Agent fails.')); + $this->assertResponse(403, 'Requesting https.php with a bad simpletest User-Agent fails.'); // Use a real User-Agent and verify that the special files http.php and // https.php can't be accessed. $this->additionalCurlOptions = array(CURLOPT_USERAGENT => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12'); $this->drupalGet($HTTP_path); - $this->assertResponse(403, t('Requesting http.php with a normal User-Agent fails.')); + $this->assertResponse(403, 'Requesting http.php with a normal User-Agent fails.'); $this->drupalGet($https_path); - $this->assertResponse(403, t('Requesting https.php with a normal User-Agent fails.')); + $this->assertResponse(403, 'Requesting https.php with a normal User-Agent fails.'); } } @@ -147,7 +147,7 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { // Regression test for #290316. // Check that test_id is incrementing. - $this->assertTrue($this->test_ids[0] != $this->test_ids[1], t('Test ID is incrementing.')); + $this->assertTrue($this->test_ids[0] != $this->test_ids[1], 'Test ID is incrementing.'); } } @@ -210,7 +210,7 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { $this->assertEqual('6 passes, 5 fails, 2 exceptions, and 1 debug message', $this->childTestResults['summary'], 'Stub test summary is correct'); $this->test_ids[] = $test_id = $this->getTestIdFromResults(); - $this->assertTrue($test_id, t('Found test ID in results.')); + $this->assertTrue($test_id, 'Found test ID in results.'); } /** @@ -249,7 +249,7 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { break; } } - return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function))); + return $this->assertTrue($found, format_string('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function))); } /** @@ -345,18 +345,18 @@ class SimpleTestBrowserTestCase extends DrupalWebTestCase { $this->drupalGet($url); $absolute = url($url, array('absolute' => TRUE)); - $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.')); - $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.')); + $this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.'); + $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.'); $this->drupalPost(NULL, array(), t('Log in')); - $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.')); - $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.')); + $this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.'); + $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.'); $this->clickLink('Create new account'); $url = 'user/register'; $absolute = url($url, array('absolute' => TRUE)); - $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.')); - $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.')); + $this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.'); + $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.'); } /** @@ -411,19 +411,19 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { // Before we send the e-mail, drupalGetMails should return an empty array. $captured_emails = $this->drupalGetMails(); - $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 0, 'The captured e-mails queue is empty.', 'E-mail'); // Send the e-mail. $response = drupal_mail_system('simpletest', 'drupal_mail_test')->mail($message); // Ensure that there is one e-mail in the captured e-mails array. $captured_emails = $this->drupalGetMails(); - $this->assertEqual(count($captured_emails), 1, t('One e-mail was captured.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 1, 'One e-mail was captured.', 'E-mail'); // Assert that the e-mail was sent by iterating over the message properties // and ensuring that they are captured intact. foreach ($message as $field => $value) { - $this->assertMail($field, $value, t('The e-mail was sent and the value for property @field is intact.', array('@field' => $field)), t('E-mail')); + $this->assertMail($field, $value, format_string('The e-mail was sent and the value for property @field is intact.', array('@field' => $field)), 'E-mail'); } // Send additional e-mails so more than one e-mail is captured. @@ -440,21 +440,21 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { // There should now be 6 e-mails captured. $captured_emails = $this->drupalGetMails(); - $this->assertEqual(count($captured_emails), 6, t('All e-mails were captured.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 6, 'All e-mails were captured.', 'E-mail'); // Test different ways of getting filtered e-mails via drupalGetMails(). $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test')); - $this->assertEqual(count($captured_emails), 1, t('Only one e-mail is returned when filtering by id.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 1, 'Only one e-mail is returned when filtering by id.', 'E-mail'); $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject)); - $this->assertEqual(count($captured_emails), 1, t('Only one e-mail is returned when filtering by id and subject.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 1, 'Only one e-mail is returned when filtering by id and subject.', 'E-mail'); $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject, 'from' => 'this_was_not_used@example.com')); - $this->assertEqual(count($captured_emails), 0, t('No e-mails are returned when querying with an unused from address.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 0, 'No e-mails are returned when querying with an unused from address.', 'E-mail'); // Send the last e-mail again, so we can confirm that the drupalGetMails-filter // correctly returns all e-mails with a given property/value. drupal_mail_system('drupal_mail_test', $index)->mail($message); $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4')); - $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail')); + $this->assertEqual(count($captured_emails), 2, 'All e-mails with the same id are returned when filtering by id.', 'E-mail'); } } @@ -476,7 +476,7 @@ class SimpleTestFolderTestCase extends DrupalWebTestCase { function testFolderSetup() { $directory = file_default_scheme() . '://styles'; - $this->assertTrue(file_prepare_directory($directory, FALSE), "Directory created."); + $this->assertTrue(file_prepare_directory($directory, FALSE), 'Directory created.'); } } @@ -655,3 +655,92 @@ class SimpleTestOtherInstallationProfileModuleTestsTestCase extends DrupalWebTes $this->assertNoText('Installation profile module tests helper'); } } + +/** + * Verifies that tests in other installation profiles are not found. + * + * @see SimpleTestInstallationProfileModuleTestsTestCase + */ +class SimpleTestDiscoveryTestCase extends DrupalWebTestCase { + /** + * Use the Testing profile. + * + * The Testing profile contains drupal_system_listing_compatible_test.test, + * which attempts to: + * - run tests using the Minimal profile (which does not contain the + * drupal_system_listing_compatible_test.module) + * - but still install the drupal_system_listing_compatible_test.module + * contained in the Testing profile. + * + * @see DrupalSystemListingCompatibleTestCase + */ + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Discovery of test classes', + 'description' => 'Verifies that tests classes are discovered and can be autoloaded (class_exists).', + 'group' => 'SimpleTest', + ); + } + + function setUp() { + parent::setUp(array('simpletest')); + + $this->admin_user = $this->drupalCreateUser(array('administer unit tests')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test discovery of PSR-0 test classes. + */ + function testDiscoveryFunctions() { + if (version_compare(PHP_VERSION, '5.3') < 0) { + // Don't expect PSR-0 tests to be discovered on older PHP versions. + return; + } + // TODO: What if we have cached values? Do we need to force a cache refresh? + $classes_all = simpletest_test_get_all(); + foreach (array( + 'Drupal\\simpletest\\Tests\\PSR0WebTest', + 'Drupal\\psr_0_test\\Tests\\ExampleTest', + ) as $class) { + $this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class))); + } + } + + /** + * Tests existence of test cases. + */ + function testDiscovery() { + $this->drupalGet('admin/config/development/testing'); + // Tests within enabled modules. + // (without these, this test wouldn't happen in the first place, so this is + // a bit pointless. We still run it for proof-of-concept.) + // This one is defined in system module. + $this->assertText('Drupal error handlers'); + // This one is defined in simpletest module. + $this->assertText('Discovery of test classes'); + // Tests within disabled modules. + if (version_compare(PHP_VERSION, '5.3') < 0) { + // Don't expect PSR-0 tests to be discovered on older PHP versions. + return; + } + // This one is provided by simpletest itself via PSR-0. + $this->assertText('PSR0 web test'); + $this->assertText('PSR0 example test: PSR-0 in disabled modules.'); + $this->assertText('PSR0 example test: PSR-0 in nested subfolders.'); + + // Test each test individually. + foreach (array( + 'Drupal\\psr_0_test\\Tests\\ExampleTest', + 'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest', + ) as $class) { + $this->drupalGet('admin/config/development/testing'); + $edit = array($class => TRUE); + $this->drupalPost(NULL, $edit, t('Run tests')); + $this->assertText('The test run finished', t('Test @class must finish.', array('@class' => $class))); + $this->assertText('1 pass, 0 fails, and 0 exceptions', t('Test @class must pass.', array('@class' => $class))); + } + } +} diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index bd440bfbf96c5ab8380a5c2800006e639775640e..347448e9f17e2d3912af4e97aa292f877a9f8721 100644 --- a/modules/simpletest/tests/actions_loop_test.info +++ b/modules/simpletest/tests/actions_loop_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index 19ff8a18f375b12f2d5604d1c0a266c2dc0277f8..d96c19d5133242f97abb23b861abcd484c137cc7 100644 --- a/modules/simpletest/tests/ajax_forms_test.info +++ b/modules/simpletest/tests/ajax_forms_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index 042426df26c234ccf1a41344f51721ca6713ab34..6853ca3eb9aeeca4e9f2d5feeb87eb507663ff7b 100644 --- a/modules/simpletest/tests/ajax_test.info +++ b/modules/simpletest/tests/ajax_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index c4e00dd7b9bc2f35f37fef05096de6adb267ccac..70bab1009d0e33b1af21a05016346158f0adfebe 100644 --- a/modules/simpletest/tests/batch_test.info +++ b/modules/simpletest/tests/batch_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index e8e403330c49544bb70c0f61bf29d972fdfb2dd7..82e3055d289c0cc465ddf807399b44c43fa966ba 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -2298,6 +2298,12 @@ class FormatDateUnitTest extends DrupalWebTestCase { $edit = array('date_format' => $admin_date_format); $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + // Add a new date format which just differs in the case. + $admin_date_format_uppercase = 'j M Y'; + $edit = array('date_format' => $admin_date_format_uppercase); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertText(t('Custom date format added.')); + // Add new date type. $edit = array( 'date_type' => 'Example Style', @@ -2306,8 +2312,18 @@ class FormatDateUnitTest extends DrupalWebTestCase { ); $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + // Add a second date format with a different case than the first. + $edit = array( + 'machine_name' => 'example_style_uppercase', + 'date_type' => 'Example Style Uppercase', + 'date_format' => $admin_date_format_uppercase, + ); + $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + $this->assertText(t('New date type added successfully.')); + $timestamp = strtotime('2007-03-10T00:00:00+00:00'); $this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', t('Test format_date() using an admin-defined date type.')); + $this->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007', 'Test format_date() using an admin-defined date type with different case.'); $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), t('Test format_date() defaulting to medium when $type not found.')); } @@ -2499,10 +2515,10 @@ class DrupalGetRdfNamespacesTestCase extends DrupalWebTestCase { $xml = new SimpleXMLElement($this->content); $ns = $xml->getDocNamespaces(); - $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', t('A prefix declared once is displayed.')); - $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', t('The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.')); - $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', t('Two prefixes can be assigned the same namespace.')); - $this->assertTrue(!isset($ns['dc']), t('A prefix with conflicting namespaces is discarded.')); + $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', 'A prefix declared once is displayed.'); + $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', 'The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.'); + $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', 'Two prefixes can be assigned the same namespace.'); + $this->assertTrue(!isset($ns['dc']), 'A prefix with conflicting namespaces is discarded.'); } } diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index aec732bc26ed0a4f4f8962981b7fbe50be6057b5..15070c1cb2e2eb3339ae6341fb38119c6887688b 100644 --- a/modules/simpletest/tests/common_test.info +++ b/modules/simpletest/tests/common_test.info @@ -7,8 +7,8 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info index 3834dfa3d403b5f322b0e8f67799202e38dc319f..1a66665ae8f62409c81ba941b2f18f670f3d3da1 100644 --- a/modules/simpletest/tests/common_test_cron_helper.info +++ b/modules/simpletest/tests/common_test_cron_helper.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index a21a08a854d649339019a752f5be41bdcf78e36e..72128b56a822c3e9a308b5f69be5343c7b7d36ee 100644 --- a/modules/simpletest/tests/database_test.info +++ b/modules/simpletest/tests/database_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 6e1d1597991d0c61f622acae20fece2597999323..b58578e993f76edb5db3ce0609d02eac37fcca04 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -48,7 +48,7 @@ class DatabaseTestCase extends DrupalWebTestCase { } foreach ($schema as $name => $data) { - $this->assertTrue(db_table_exists($name), t('Table @name created successfully.', array('@name' => $name))); + $this->assertTrue(db_table_exists($name), format_string('Table @name created successfully.', array('@name' => $name))); } } @@ -191,25 +191,25 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db1 = Database::getConnection('default', 'default'); $db2 = Database::getConnection('slave', 'default'); - $this->assertNotNull($db1, t('default connection is a real connection object.')); - $this->assertNotNull($db2, t('slave connection is a real connection object.')); - $this->assertNotIdentical($db1, $db2, t('Each target refers to a different connection.')); + $this->assertNotNull($db1, 'default connection is a real connection object.'); + $this->assertNotNull($db2, 'slave connection is a real connection object.'); + $this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.'); // Try to open those targets another time, that should return the same objects. $db1b = Database::getConnection('default', 'default'); $db2b = Database::getConnection('slave', 'default'); - $this->assertIdentical($db1, $db1b, t('A second call to getConnection() returns the same object.')); - $this->assertIdentical($db2, $db2b, t('A second call to getConnection() returns the same object.')); + $this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.'); + $this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.'); // Try to open an unknown target. $unknown_target = $this->randomName(); $db3 = Database::getConnection($unknown_target, 'default'); - $this->assertNotNull($db3, t('Opening an unknown target returns a real connection object.')); - $this->assertIdentical($db1, $db3, t('An unknown target opens the default connection.')); + $this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.'); + $this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.'); // Try to open that unknown target another time, that should return the same object. $db3b = Database::getConnection($unknown_target, 'default'); - $this->assertIdentical($db3, $db3b, t('A second call to getConnection() returns the same object.')); + $this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.'); } /** @@ -227,7 +227,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db1 = Database::getConnection('default', 'default'); $db2 = Database::getConnection('slave', 'default'); - $this->assertIdentical($db1, $db2, t('Both targets refer to the same connection.')); + $this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.'); } /** @@ -242,7 +242,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db2 = Database::getConnection('default', 'default'); // Opening a connection after closing it should yield an object different than the original. - $this->assertNotIdentical($db1, $db2, t('Opening the default connection after it is closed returns a new object.')); + $this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.'); } /** @@ -257,8 +257,8 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // In the MySQL driver, the port can be different, so check individual // options. - $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], t('The default connection info driver matches the current connection options driver.')); - $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], t('The default connection info database matches the current connection options database.')); + $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.'); + $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.'); // Set up identical slave and confirm connection options are identical. Database::addConnectionInfo('default', 'slave', $connection_info['default']); @@ -267,7 +267,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // Get a fresh copy of the default connection options. $connectionOptions = $db->getConnectionOptions(); - $this->assertIdentical($connectionOptions, $connectionOptions2, t('The default and slave connection options are identical.')); + $this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and slave connection options are identical.'); // Set up a new connection with different connection info. $test = $connection_info['default']; @@ -277,7 +277,46 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // Get a fresh copy of the default connection options. $connectionOptions = $db->getConnectionOptions(); - $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], t('The test connection info database does not match the current connection options database.')); + $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); + } +} + +/** + * Test cloning Select queries. + */ +class DatabaseSelectCloneTest extends DatabaseTestCase { + + public static function getInfo() { + return array( + 'name' => 'Select tests, cloning', + 'description' => 'Test cloning Select queries.', + 'group' => 'Database', + ); + } + + /** + * Test that subqueries as value within conditions are cloned properly. + */ + function testSelectConditionSubQueryCloning() { + $subquery = db_select('test', 't'); + $subquery->addField('t', 'id', 'id'); + $subquery->condition('age', 28, '<'); + + $query = db_select('test', 't'); + $query->addField('t', 'name', 'name'); + $query->condition('id', $subquery, 'IN'); + + $clone = clone $query; + // Cloned query should not be altered by the following modification + // happening on original query. + $subquery->condition('age', 25, '>'); + + $clone_result = $clone->countQuery()->execute()->fetchField(); + $query_result = $query->countQuery()->execute()->fetchField(); + + // Make sure the cloned query has not been modified + $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows'); + $this->assertEqual(2, $query_result, 'The query returns the expected number of rows'); } } @@ -302,14 +341,14 @@ class DatabaseFetchTestCase extends DatabaseTestCase { function testQueryFetchDefault() { $records = array(); $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25)); - $this->assertTrue($result instanceof DatabaseStatementInterface, t('Result set is a Drupal statement object.')); + $this->assertTrue($result instanceof DatabaseStatementInterface, 'Result set is a Drupal statement object.'); foreach ($result as $record) { $records[] = $record; - $this->assertTrue(is_object($record), t('Record is an object.')); - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + $this->assertTrue(is_object($record), 'Record is an object.'); + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -320,11 +359,11 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_OBJ)); foreach ($result as $record) { $records[] = $record; - $this->assertTrue(is_object($record), t('Record is an object.')); - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + $this->assertTrue(is_object($record), 'Record is an object.'); + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -335,12 +374,12 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -353,12 +392,12 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'FakeRecord')); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue($record instanceof FakeRecord, t('Record is an object of class FakeRecord.'))) { - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) { + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } } @@ -387,8 +426,8 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_NUM)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); } } @@ -403,13 +442,13 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_BOTH)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.')); - $this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); + $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -419,12 +458,12 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $records = array(); $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); $column = $result->fetchCol(); - $this->assertIdentical(count($column), 3, t('fetchCol() returns the right number of records.')); + $this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.'); $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); $i = 0; foreach ($result as $record) { - $this->assertIdentical($record->name, $column[$i++], t('Column matches direct accesss.')); + $this->assertIdentical($record->name, $column[$i++], 'Column matches direct accesss.'); } } } @@ -456,9 +495,9 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField(); - $this->assertIdentical($saved_age, '29', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.'); } /** @@ -485,13 +524,13 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 3, $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -520,13 +559,13 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -542,11 +581,11 @@ class DatabaseInsertTestCase extends DatabaseTestCase { ->values(array('Moe', '32')) ->execute(); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -560,7 +599,7 @@ class DatabaseInsertTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertIdentical($id, '5', t('Auto-increment ID returned successfully.')); + $this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.'); } /** @@ -586,7 +625,7 @@ class DatabaseInsertTestCase extends DatabaseTestCase { ->execute(); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); } } @@ -608,12 +647,12 @@ class DatabaseInsertLOBTestCase extends DatabaseTestCase { */ function testInsertOneBlob() { $data = "This is\000a test."; - $this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.')); + $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); $id = db_insert('test_one_blob') ->fields(array('blob1' => $data)) ->execute(); $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === $data, t('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); + $this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); } /** @@ -627,7 +666,7 @@ class DatabaseInsertLOBTestCase extends DatabaseTestCase { )) ->execute(); $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', t('Can insert multiple blobs per row.')); + $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.'); } } @@ -654,7 +693,7 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { $schema = drupal_get_schema('test'); $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); - $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.')); + $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); } /** @@ -666,13 +705,13 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { try { $result = db_insert('test')->execute(); // This is only executed if no exception has been thrown. - $this->fail(t('Expected exception NoFieldsException has not been thrown.')); + $this->fail('Expected exception NoFieldsException has not been thrown.'); } catch (NoFieldsException $e) { - $this->pass(t('Expected exception NoFieldsException has been thrown.')); + $this->pass('Expected exception NoFieldsException has been thrown.'); } $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before, $num_records_after, t('Do nothing as no fields are specified.')); + $this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.'); } /** @@ -687,7 +726,7 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { $schema = drupal_get_schema('test'); $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); - $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.')); + $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); } } @@ -712,10 +751,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('name' => 'Tiffany')) ->condition('id', 1) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField(); - $this->assertIdentical($saved_name, 'Tiffany', t('Updated name successfully.')); + $this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.'); } /** @@ -727,10 +766,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('age' => NULL)) ->condition('name', 'Kermit') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField(); - $this->assertNull($saved_age, t('Updated name successfully.')); + $this->assertNull($saved_age, 'Updated name successfully.'); } /** @@ -741,10 +780,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('job', 'Singer') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -755,10 +794,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('age', 26, '>') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -769,10 +808,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->where('age > :age', array(':age' => 26)) ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -784,10 +823,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->where('age > :age', array(':age' => 26)) ->condition('name', 'Ringo'); $num_updated = $update->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -807,7 +846,21 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { $num_rows = db_update('test') ->expression('age', 'age * age') ->execute(); - $this->assertIdentical($num_rows, 3, t('Number of affected rows are returned.')); + $this->assertIdentical($num_rows, 3, 'Number of affected rows are returned.'); + } + + /** + * Confirm that we can update the primary key of a record successfully. + */ + function testPrimaryKeyUpdate() { + $num_updated = db_update('test') + ->fields(array('id' => 42, 'name' => 'John')) + ->condition('id', 1) + ->execute(); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); + + $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField(); + $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.'); } } @@ -835,10 +888,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->condition('name', 'Paul') ); $num_updated = $update->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -849,10 +902,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', array('John', 'Paul'), 'IN') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -865,10 +918,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', array('John', 'Paul', 'George'), 'NoT IN') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -879,10 +932,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('age', array(25, 26), 'BETWEEN') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -893,10 +946,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', '%ge%', 'LIKE') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -910,15 +963,15 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->expression('age', 'age + :age', array(':age' => 4)) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); $person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch(); - $this->assertEqual($person->name, 'Ringo', t('Name set correctly.')); - $this->assertEqual($person->age, $before_age + 4, t('Age set correctly.')); - $this->assertEqual($person->job, 'Musician', t('Job set correctly.')); + $this->assertEqual($person->name, 'Ringo', 'Name set correctly.'); + $this->assertEqual($person->age, $before_age + 4, 'Age set correctly.'); + $this->assertEqual($person->job, 'Musician', 'Job set correctly.'); $GLOBALS['larry_test'] = 0; } @@ -931,10 +984,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->condition('name', 'Ringo') ->expression('age', 'age + :age', array(':age' => 4)) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField(); - $this->assertEqual($before_age + 4, $after_age, t('Age updated correctly')); + $this->assertEqual($before_age + 4, $after_age, 'Age updated correctly'); } } @@ -956,7 +1009,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { */ function testUpdateOneBlob() { $data = "This is\000a test."; - $this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.')); + $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); $id = db_insert('test_one_blob') ->fields(array('blob1' => $data)) ->execute(); @@ -968,7 +1021,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { ->execute(); $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === $data, t('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); + $this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); } /** @@ -988,7 +1041,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { ->execute(); $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', t('Can update multiple blobs per row.')); + $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.'); } } @@ -1028,10 +1081,10 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { ->condition('pid', $subquery, 'IN'); $num_deleted = $delete->execute(); - $this->assertEqual($num_deleted, 1, t("Deleted 1 record.")); + $this->assertEqual($num_deleted, 1, "Deleted 1 record."); $num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.')); + $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); } /** @@ -1043,10 +1096,10 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { $num_deleted = db_delete('test') ->condition('id', 1) ->execute(); - $this->assertIdentical($num_deleted, 1, t('Deleted 1 record.')); + $this->assertIdentical($num_deleted, 1, 'Deleted 1 record.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.')); + $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); } /** @@ -1058,7 +1111,7 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { db_truncate('test')->execute(); $num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField(); - $this->assertEqual(0, $num_records_after, t('Truncate really deletes everything.')); + $this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.'); } } @@ -1089,15 +1142,15 @@ class DatabaseMergeTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertEqual($result, MergeQuery::STATUS_INSERT, t('Insert status returned.')); + $this->assertEqual($result, MergeQuery::STATUS_INSERT, 'Insert status returned.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.')); + $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 31, t('Age set correctly.')); - $this->assertEqual($person->job, 'Presenter', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 31, 'Age set correctly.'); + $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); } /** @@ -1114,15 +1167,15 @@ class DatabaseMergeTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertEqual($result, MergeQuery::STATUS_UPDATE, t('Update status returned.')); + $this->assertEqual($result, MergeQuery::STATUS_UPDATE, 'Update status returned.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 31, t('Age set correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 31, 'Age set correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1138,12 +1191,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1164,12 +1217,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Joe', t('Name set correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Joe', 'Name set correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1193,12 +1246,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, $age_before + 4, t('Age updated correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1212,12 +1265,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.')); + $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); - $this->assertEqual($person->name, '', t('Name set correctly.')); - $this->assertEqual($person->age, 0, t('Age set correctly.')); - $this->assertEqual($person->job, 'Presenter', t('Job set correctly.')); + $this->assertEqual($person->name, '', 'Name set correctly.'); + $this->assertEqual($person->age, 0, 'Age set correctly.'); + $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); } /** @@ -1231,12 +1284,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.')); + $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); db_merge('test_people') ->key(array('job' => 'Speaker')) @@ -1244,12 +1297,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.')); + $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); } /** @@ -1266,10 +1319,10 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); } catch (InvalidMergeQueryException $e) { - $this->pass(t('InvalidMergeQueryException thrown for invalid query.')); + $this->pass('InvalidMergeQueryException thrown for invalid query.'); return; } - $this->fail(t('No InvalidMergeQueryException thrown')); + $this->fail('No InvalidMergeQueryException thrown'); } } @@ -1300,7 +1353,7 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1320,8 +1373,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $query = (string)$query; $expected = "/* Testing query comments */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); - $this->assertEqual($query, $expected, t('The flattened query contains the comment string.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); + $this->assertEqual($query, $expected, 'The flattened query contains the comment string.'); } /** @@ -1341,8 +1394,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $query = (string)$query; $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); - $this->assertEqual($query, $expected, t('The flattened query contains the sanitised comment string.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); + $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.'); } /** @@ -1356,13 +1409,13 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($name_field, 'name', t('Name field alias is correct.')); - $this->assertEqual($age_field, 'age', t('Age field alias is correct.')); + $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); + $this->assertEqual($age_field, 'age', 'Age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27, t('Fetched age is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.'); } /** @@ -1376,13 +1429,13 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($name_field, 'name', t('Name field alias is correct.')); - $this->assertEqual($age_field, 'double_age', t('Age field alias is correct.')); + $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); + $this->assertEqual($age_field, 'double_age', 'Age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*2, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.'); } /** @@ -1397,14 +1450,14 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($age_double_field, 'expression', t('Double age field alias is correct.')); - $this->assertEqual($age_triple_field, 'expression_2', t('Triple age field alias is correct.')); + $this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.'); + $this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_double_field, 27*2, t('Fetched double age expression is correct.')); - $this->assertEqual($record->$age_triple_field, 27*3, t('Fetched triple age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.'); + $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.'); } /** @@ -1417,17 +1470,17 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->execute()->fetchObject(); // Check that all fields we asked for are present. - $this->assertNotNull($record->id, t('ID field is present.')); - $this->assertNotNull($record->name, t('Name field is present.')); - $this->assertNotNull($record->age, t('Age field is present.')); - $this->assertNotNull($record->job, t('Job field is present.')); + $this->assertNotNull($record->id, 'ID field is present.'); + $this->assertNotNull($record->name, 'Name field is present.'); + $this->assertNotNull($record->age, 'Age field is present.'); + $this->assertNotNull($record->job, 'Job field is present.'); // Ensure that we got the right record. // Check that all fields we asked for are present. - $this->assertEqual($record->id, 2, t('ID field has the correct value.')); - $this->assertEqual($record->name, 'George', t('Name field has the correct value.')); - $this->assertEqual($record->age, 27, t('Age field has the correct value.')); - $this->assertEqual($record->job, 'Singer', t('Job field has the correct value.')); + $this->assertEqual($record->id, 2, 'ID field has the correct value.'); + $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); + $this->assertEqual($record->age, 27, 'Age field has the correct value.'); + $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); } /** @@ -1440,17 +1493,17 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->execute()->fetchObject(); // Check that all fields we asked for are present. - $this->assertNotNull($record->id, t('ID field is present.')); - $this->assertNotNull($record->name, t('Name field is present.')); - $this->assertNotNull($record->age, t('Age field is present.')); - $this->assertNotNull($record->job, t('Job field is present.')); + $this->assertNotNull($record->id, 'ID field is present.'); + $this->assertNotNull($record->name, 'Name field is present.'); + $this->assertNotNull($record->age, 'Age field is present.'); + $this->assertNotNull($record->job, 'Job field is present.'); // Ensure that we got the right record. // Check that all fields we asked for are present. - $this->assertEqual($record->id, 2, t('ID field has the correct value.')); - $this->assertEqual($record->name, 'George', t('Name field has the correct value.')); - $this->assertEqual($record->age, 27, t('Age field has the correct value.')); - $this->assertEqual($record->job, 'Singer', t('Job field has the correct value.')); + $this->assertEqual($record->id, 2, 'ID field has the correct value.'); + $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); + $this->assertEqual($record->age, 27, 'Age field has the correct value.'); + $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); } /** @@ -1464,8 +1517,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->isNull('age') ->execute()->fetchCol(); - $this->assertEqual(count($names), 1, t('Correct number of records found with NULL age.')); - $this->assertEqual($names[0], 'Fozzie', t('Correct record returned for NULL age.')); + $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.'); + $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.'); } /** @@ -1480,9 +1533,9 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderBy('name') ->execute()->fetchCol(); - $this->assertEqual(count($names), 2, t('Correct number of records found withNOT NULL age.')); - $this->assertEqual($names[0], 'Gonzo', t('Correct record returned for NOT NULL age.')); - $this->assertEqual($names[1], 'Kermit', t('Correct record returned for NOT NULL age.')); + $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.'); + $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.'); + $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.'); } /** @@ -1503,10 +1556,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $names = $query_1->execute()->fetchCol(); // Ensure we only get 2 records. - $this->assertEqual(count($names), 2, t('UNION correctly discarded duplicates.')); + $this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.'); - $this->assertEqual($names[0], 'George', t('First query returned correct name.')); - $this->assertEqual($names[1], 'Ringo', t('Second query returned correct name.')); + $this->assertEqual($names[0], 'George', 'First query returned correct name.'); + $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.'); } /** @@ -1526,11 +1579,11 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $names = $query_1->execute()->fetchCol(); // Ensure we get all 3 records. - $this->assertEqual(count($names), 3, t('UNION ALL correctly preserved duplicates.')); + $this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.'); - $this->assertEqual($names[0], 'George', t('First query returned correct first name.')); - $this->assertEqual($names[1], 'Ringo', t('Second query returned correct second name.')); - $this->assertEqual($names[2], 'Ringo', t('Third query returned correct name.')); + $this->assertEqual($names[0], 'George', 'First query returned correct first name.'); + $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.'); + $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.'); } /** @@ -1565,7 +1618,7 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderBy('id') ->execute() ->fetchCol(); - $this->assertEqual($ordered_ids, $expected_ids, t('A query without random ordering returns IDs in the correct order.')); + $this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.'); // Now perform the same query, but instead choose a random ordering. We // expect this to contain a differently ordered version of the original @@ -1576,10 +1629,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderRandom() ->execute() ->fetchCol(); - $this->assertNotEqual($randomized_ids, $ordered_ids, t('A query with random ordering returns an unordered set of IDs.')); + $this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.'); $sorted_ids = $randomized_ids; sort($sorted_ids); - $this->assertEqual($sorted_ids, $ordered_ids, t('After sorting the random list, the result matches the original query.')); + $this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.'); // Now perform the exact same query again, and make sure the order is // different. @@ -1589,10 +1642,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderRandom() ->execute() ->fetchCol(); - $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, t('Performing the query with random ordering a second time returns IDs in a different order.')); + $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.'); $sorted_ids_second_set = $randomized_ids_second_set; sort($sorted_ids_second_set); - $this->assertEqual($sorted_ids_second_set, $sorted_ids, t('After sorting the second random list, the result matches the sorted version of the first random list.')); + $this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.'); } /** @@ -1649,7 +1702,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // WHERE tt.task = 'code' $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); } } @@ -1676,7 +1729,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // INNER JOIN test t ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); } /** @@ -1699,7 +1752,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // FROM test tt2 // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1) $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 5, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 5, 'Returned the correct number of rows.'); } /** @@ -1723,7 +1776,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 2, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 2, 'Returned the correct number of rows.'); } /** @@ -1753,7 +1806,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->name, 'George', t('Fetched name is correct using EXISTS query.')); + $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.'); } /** @@ -1783,7 +1836,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // Ensure that we got the right number of records. $people = $query->execute()->fetchCol(); - $this->assertEqual(count($people), 3, t('NOT EXISTS query returned the correct results.')); + $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.'); } } @@ -1814,11 +1867,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $last_age = 0; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->age >= $last_age, t('Results returned in correct order.')); + $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.'); $last_age = $record->age; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1845,11 +1898,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $num_records++; foreach ($record as $kk => $col) { if ($expected[$k][$kk] != $results[$k][$kk]) { - $this->assertTrue(FALSE, t('Results returned in correct order.')); + $this->assertTrue(FALSE, 'Results returned in correct order.'); } } } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1866,11 +1919,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $last_age = 100000000; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->age <= $last_age, t('Results returned in correct order.')); + $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.'); $last_age = $record->age; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } } @@ -1904,12 +1957,12 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $last_priority = 0; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$priority_field >= $last_priority, t('Results returned in correct order.')); - $this->assertNotEqual($record->$name_field, 'Ringo', t('Taskless person not selected.')); + $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.'); + $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.'); $last_priority = $record->$priority_field; } - $this->assertEqual($num_records, 7, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 7, 'Returned the correct number of rows.'); } /** @@ -1930,11 +1983,11 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { foreach ($result as $record) { $num_records++; - $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, t('Results returned in correct order.')); + $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.'); $last_priority = $record->$name_field; } - $this->assertEqual($num_records, 8, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 8, 'Returned the correct number of rows.'); } /** @@ -1953,7 +2006,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $records = array(); foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.')); + $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); $last_count = $record->$count_field; $records[$record->$task_field] = $record->$count_field; } @@ -1967,10 +2020,10 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { ); foreach ($correct_results as $task => $count) { - $this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task))); + $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); } - $this->assertEqual($num_records, 6, t('Returned the correct number of total rows.')); + $this->assertEqual($num_records, 6, 'Returned the correct number of total rows.'); } /** @@ -1990,8 +2043,8 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $records = array(); foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$count_field >= 2, t('Record has the minimum count.')); - $this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.')); + $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.'); + $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); $last_count = $record->$count_field; $records[$record->$task_field] = $record->$count_field; } @@ -2001,10 +2054,10 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { ); foreach ($correct_results as $task => $count) { - $this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task))); + $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); } - $this->assertEqual($num_records, 1, t('Returned the correct number of total rows.')); + $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.'); } /** @@ -2022,7 +2075,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 2, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); } /** @@ -2039,7 +2092,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 6, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 6, 'Returned the correct number of rows.'); } /** @@ -2053,13 +2106,13 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); // Now make sure we didn't break the original query! We should still have // all of the fields we asked for. $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.')); - $this->assertEqual($record->$age_field, 27, t('Correct data retrieved.')); + $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); + $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.'); } function testHavingCountQuery() { @@ -2070,7 +2123,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->addField('test', 'age'); $query->addExpression('age + 1'); $count = count($query->execute()->fetchCol()); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); } /** @@ -2085,20 +2138,20 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { // Check that the 'all_fields' statement is handled properly. $tables = $query->getTables(); - $this->assertEqual($tables['test']['all_fields'], 1, t('Query correctly sets \'all_fields\' statement.')); + $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.'); $tables = $count->getTables(); - $this->assertFalse(isset($tables['test']['all_fields']), t('Count query correctly unsets \'all_fields\' statement.')); + $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.'); // Check that the ordering clause is handled properly. $orderby = $query->getOrderBy(); - $this->assertEqual($orderby['name'], 'ASC', t('Query correctly sets ordering clause.')); + $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.'); $orderby = $count->getOrderBy(); - $this->assertFalse(isset($orderby['name']), t('Count query correctly unsets ordering caluse.')); + $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.'); // Make sure that the count query works. $count = $count->execute()->fetchField(); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); } @@ -2113,11 +2166,11 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { // records in the {test} table). $query = db_select('test'); $query->fields('test', array('fail')); - $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), t('Count Query removed fields')); + $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields'); $query = db_select('test'); $query->addExpression('fail'); - $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), t('Count Query removed expressions')); + $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions'); } /** @@ -2130,7 +2183,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 6, t('Counted the correct number of records.')); + $this->assertEqual($count, 6, 'Counted the correct number of records.'); } /** @@ -2143,7 +2196,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 3, t('Counted the correct number of records.')); + $this->assertEqual($count, 3, 'Counted the correct number of records.'); // Use a column alias as, without one, the query can succeed for the wrong // reason. @@ -2155,7 +2208,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 3, t('Counted the correct number of records.')); + $this->assertEqual($count, 3, 'Counted the correct number of records.'); } /** @@ -2172,7 +2225,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->condition(db_or()->condition('age', 26)->condition('age', 27)); $job = $query->execute()->fetchField(); - $this->assertEqual($job, 'Songwriter', t('Correct data retrieved.')); + $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.'); } /** @@ -2185,8 +2238,8 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->addField($alias, 'job', 'otherjob'); $query->where("$alias.name <> test.name"); $crowded_job = $query->execute()->fetch(); - $this->assertEqual($crowded_job->job, $crowded_job->otherjob, t('Correctly joined same table twice.')); - $this->assertNotEqual($crowded_job->name, $crowded_job->othername, t('Correctly joined same table twice.')); + $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.'); + $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.'); } } @@ -2251,7 +2304,7 @@ class DatabaseSelectComplexTestCase2 extends DatabaseTestCase { // Verify that the string only has one copy of condition placeholder 0. $pos = strpos($str, 'db_condition_placeholder_0', 0); $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1); - $this->assertFalse($pos2, "Condition placeholder is not repeated"); + $this->assertFalse($pos2, 'Condition placeholder is not repeated.'); } } @@ -2295,7 +2348,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $correct_number = $count - ($limit * $page); } - $this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number))); + $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); } } @@ -2329,7 +2382,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $correct_number = $count - ($limit * $page); } - $this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number))); + $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); } } @@ -2351,7 +2404,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $ages = $outer_query ->execute() ->fetchCol(); - $this->assertEqual($ages, array(25, 26, 27, 28), t('Inner pager query returned the correct ages.')); + $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.'); } /** @@ -2371,7 +2424,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $ages = $query ->execute() ->fetchCol(); - $this->assertEqual($ages, array('George', 'Ringo'), t('Pager query with having expression returned the correct ages.')); + $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.'); } /** @@ -2387,7 +2440,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'Paul', t('Pager query #1 with a specified element ID returned the correct results.')); + $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.'); // Setting an element smaller than the previous one // should not overwrite the pager $maxElement with a smaller value. @@ -2398,7 +2451,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'George', t('Pager query #2 with a specified element ID returned the correct results.')); + $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.'); $name = db_select('test', 't')->extend('PagerDefault') ->fields('t', array('name')) @@ -2406,7 +2459,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'John', t('Pager query #3 with a generated element ID returned the correct results.')); + $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.'); unset($_GET['page']); } @@ -2446,8 +2499,8 @@ class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase { $first = array_shift($data->tasks); $last = array_pop($data->tasks); - $this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order.')); - $this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order.')); + $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.'); + $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.'); } } @@ -2472,8 +2525,8 @@ class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase { $first = array_shift($data->tasks); $last = array_pop($data->tasks); - $this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); - $this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); + $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); + $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); } } @@ -2513,8 +2566,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); - $this->assertTrue($query->hasTag('test'), t('hasTag() returned true.')); - $this->assertFalse($query->hasTag('other'), t('hasTag() returned false.')); + $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.'); + $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.'); } /** @@ -2528,8 +2581,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); $query->addTag('other'); - $this->assertTrue($query->hasAllTags('test', 'other'), t('hasAllTags() returned true.')); - $this->assertFalse($query->hasAllTags('test', 'stuff'), t('hasAllTags() returned false.')); + $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.'); + $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.'); } /** @@ -2542,8 +2595,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); - $this->assertTrue($query->hasAnyTag('test', 'other'), t('hasAnyTag() returned true.')); - $this->assertFalse($query->hasAnyTag('other', 'stuff'), t('hasAnyTag() returned false.')); + $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.'); + $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.'); } /** @@ -2564,10 +2617,10 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addMetaData('test', $data); $return = $query->getMetaData('test'); - $this->assertEqual($data, $return, t('Corect metadata returned.')); + $this->assertEqual($data, $return, 'Corect metadata returned.'); $return = $query->getMetaData('nothere'); - $this->assertNull($return, t('Non-existent key returned NULL.')); + $this->assertNull($return, 'Non-existent key returned NULL.'); } } @@ -2602,7 +2655,7 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 2, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); } /** @@ -2619,14 +2672,14 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $records = $result->fetchAll(); - $this->assertEqual(count($records), 2, t('Returned the correct number of rows.')); + $this->assertEqual(count($records), 2, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->name, 'George', t('Correct data retrieved.')); - $this->assertEqual($records[0]->$tid_field, 4, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$task_field, 'sing', t('Correct data retrieved.')); - $this->assertEqual($records[1]->name, 'George', t('Correct data retrieved.')); - $this->assertEqual($records[1]->$tid_field, 5, t('Correct data retrieved.')); - $this->assertEqual($records[1]->$task_field, 'sleep', t('Correct data retrieved.')); + $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.'); + $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.'); + $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.'); + $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.'); + $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.'); } /** @@ -2647,11 +2700,11 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $records = $result->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->$name_field, 'John', t('Correct data retrieved.')); - $this->assertEqual($records[0]->$tid_field, 2, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$pid_field, 1, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$task_field, 'sleep', t('Correct data retrieved.')); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.'); + $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.'); } /** @@ -2665,8 +2718,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $query->addTag('database_test_alter_change_fields'); $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.')); - $this->assertFalse(isset($record->$age_field), t('Age field not found, as intended.')); + $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); + $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.'); } /** @@ -2683,8 +2736,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*3, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); } /** @@ -2699,7 +2752,7 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $num_records = count($query->execute()->fetchAll()); - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -2723,8 +2776,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $name_field = $query->addField('pq', 'name'); $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*3, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); } } @@ -2758,31 +2811,31 @@ class DatabaseRegressionTestCase extends DatabaseTestCase { ))->execute(); $from_database = db_query('SELECT name FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); - $this->assertIdentical($name, $from_database, t("The database handles UTF-8 characters cleanly.")); + $this->assertIdentical($name, $from_database, "The database handles UTF-8 characters cleanly."); } /** * Test the db_table_exists() function. */ function testDBTableExists() { - $this->assertIdentical(TRUE, db_table_exists('node'), t('Returns true for existent table.')); - $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), t('Returns false for nonexistent table.')); + $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.'); + $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.'); } /** * Test the db_field_exists() function. */ function testDBFieldExists() { - $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), t('Returns true for existent column.')); - $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), t('Returns false for nonexistent column.')); + $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.'); + $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.'); } /** * Test the db_index_exists() function. */ function testDBIndexExists() { - $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), t('Returns true for existent index.')); - $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), t('Returns false for nonexistent index.')); + $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.'); + $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.'); } } @@ -2813,10 +2866,10 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries = Database::getLog('testing', 'default'); - $this->assertEqual(count($queries), 3, t('Correct number of queries recorded.')); + $this->assertEqual(count($queries), 3, 'Correct number of queries recorded.'); foreach ($queries as $query) { - $this->assertEqual($query['caller']['function'], __FUNCTION__, t('Correct function in query log.')); + $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.'); } } @@ -2835,8 +2888,8 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); $queries2 = Database::getLog('testing2'); - $this->assertEqual(count($queries1), 2, t('Correct number of queries recorded for log 1.')); - $this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for log 2.')); + $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.'); + $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.'); } /** @@ -2856,9 +2909,9 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); - $this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.')); - $this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.')); - $this->assertEqual($queries1[1]['target'], 'slave', t('Second query used slave target.')); + $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); + $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); + $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.'); } /** @@ -2882,9 +2935,9 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); - $this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.')); - $this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.')); - $this->assertEqual($queries1[1]['target'], 'default', t('Second query used default target as fallback.')); + $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); + $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); + $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.'); } /** @@ -2910,8 +2963,8 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); $queries2 = Database::getLog('testing1', 'test2'); - $this->assertEqual(count($queries1), 1, t('Correct number of queries recorded for first connection.')); - $this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for second connection.')); + $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.'); + $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.'); } } @@ -2938,7 +2991,7 @@ class DatabaseSerializeQueryTestCase extends DatabaseTestCase { // assertion. $query = unserialize(serialize($query)); $results = $query->execute()->fetchCol(); - $this->assertEqual($results[0], 28, t('Query properly executed after unserialization.')); + $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.'); } } @@ -2964,12 +3017,12 @@ class DatabaseRangeQueryTestCase extends DrupalWebTestCase { function testRangeQuery() { // Test if return correct number of rows. $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll(); - $this->assertEqual(count($range_rows), 3, t('Range query work and return correct number of rows.')); + $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.'); // Test if return target data. $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll(); $raw_rows = array_slice($raw_rows, 2, 3); - $this->assertEqual($range_rows, $raw_rows, t('Range query work and return target data.')); + $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.'); } } @@ -3003,19 +3056,19 @@ class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase { $this->drupalGet('database_test/db_query_temporary'); $data = json_decode($this->drupalGetContent()); if ($data) { - $this->assertEqual($this->countTableRows("system"), $data->row_count, t('The temporary table contains the correct amount of rows.')); - $this->assertFalse(db_table_exists($data->table_name), t('The temporary table is, indeed, temporary.')); + $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.'); + $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.'); } else { - $this->fail(t("The creation of the temporary table failed.")); + $this->fail("The creation of the temporary table failed."); } // Now try to run two db_query_temporary() in the same request. $table_name_system = db_query_temporary('SELECT status FROM {system}', array()); $table_name_users = db_query_temporary('SELECT uid FROM {users}', array()); - $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), t('A temporary table was created successfully in this request.')); - $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), t('A second temporary table was created successfully in this request.')); + $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), 'A temporary table was created successfully in this request.'); + $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), 'A second temporary table was created successfully in this request.'); } } @@ -3050,7 +3103,7 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ':a4' => ' a ', ':a5' => 'test.', )); - $this->assertIdentical($result->fetchField(), 'This is a test.', t('Basic CONCAT works.')); + $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.'); } /** @@ -3063,7 +3116,7 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ':a3' => '.', ':age' => 25, )); - $this->assertIdentical($result->fetchField(), 'The age of John is 25.', t('Field CONCAT works.')); + $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.'); } /** @@ -3082,14 +3135,14 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '2', t('Found 2 records.')); + $this->assertIdentical($num_matches, '2', 'Found 2 records.'); // Match only "Ring_" using a LIKE expression with no wildcards. $num_matches = db_select('test', 't') ->condition('name', db_like('Ring_'), 'LIKE') ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '1', t('Found 1 record.')); + $this->assertIdentical($num_matches, '1', 'Found 1 record.'); } /** @@ -3113,14 +3166,14 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '2', t('Found 2 records.')); + $this->assertIdentical($num_matches, '2', 'Found 2 records.'); // Match only the former using a LIKE expression with no wildcards. $num_matches = db_select('test', 't') ->condition('name', db_like('abc%\_'), 'LIKE') ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '1', t('Found 1 record.')); + $this->assertIdentical($num_matches, '1', 'Found 1 record.'); } } @@ -3151,9 +3204,9 @@ class DatabaseCaseSensitivityTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField(); - $this->assertIdentical($saved_age, '2', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.'); } } @@ -3196,7 +3249,7 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { 'job' => 'Singer', )) ->execute(); - $this->fail(t('Insert succeedded when it should not have.')); + $this->fail('Insert succeedded when it should not have.'); } catch (Exception $e) { // Check if the first record was inserted. @@ -3208,14 +3261,14 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { // Database engines that don't support transactions can leave partial // inserts in place when an error occurs. This is the case for MySQL // when running on a MyISAM table. - $this->pass(t("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions")); + $this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions"); } else { - $this->fail(t('The whole transaction is rolled back when a duplicate key insert occurs.')); + $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.'); } } else { - $this->pass(t('The whole transaction is rolled back when a duplicate key insert occurs.')); + $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.'); } // Ensure the other values were not inserted. @@ -3224,7 +3277,7 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { ->condition('age', array(17, 75), 'IN') ->execute()->fetchObject(); - $this->assertFalse($record, t('The rest of the insert aborted as expected.')); + $this->assertFalse($record, 'The rest of the insert aborted as expected.'); } } @@ -3252,7 +3305,7 @@ class DatabaseQueryTestCase extends DatabaseTestCase { function testArraySubstitution() { $names = db_query('SELECT name FROM {test} WHERE age IN (:ages) ORDER BY age', array(':ages' => array(25, 26, 27)))->fetchAll(); - $this->assertEqual(count($names), 3, t('Correct number of names returned')); + $this->assertEqual(count($names), 3, 'Correct number of names returned'); } } @@ -3320,19 +3373,19 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertTrue($connection->inTransaction(), t('In transaction before calling nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.'); // We're already in a transaction, but we call ->transactionInnerLayer // to nest another transaction inside the current one. $this->transactionInnerLayer($suffix, $rollback, $ddl_statement); - $this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.'); if ($rollback) { // Roll back the transaction, if requested. // This rollback should propagate to the last savepoint. $txn->rollback(); - $this->assertTrue(($connection->transactionDepth() == $depth), t('Transaction has rolled back to the last savepoint after calling rollback().')); + $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); } } @@ -3358,7 +3411,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $txn = db_transaction(); $depth2 = $connection->transactionDepth(); - $this->assertTrue($depth < $depth2, t('Transaction depth is has increased with new transaction.')); + $this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.'); // Insert a single row into the testing table. db_insert('test') @@ -3368,7 +3421,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); if ($ddl_statement) { $table = array( @@ -3383,14 +3436,14 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { ); db_create_table('database_test_1', $table); - $this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); } if ($rollback) { // Roll back the transaction, if requested. // This rollback should propagate to the last savepoint. $txn->rollback(); - $this->assertTrue(($connection->transactionDepth() == $depth), t('Transaction has rolled back to the last savepoint after calling rollback().')); + $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); } } @@ -3411,9 +3464,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Neither of the rows we inserted in the two transaction layers // should be present in the tables post-rollback. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); - $this->assertNotIdentical($saved_age, '24', t('Cannot retrieve DavidB row after commit.')); + $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); - $this->assertNotIdentical($saved_age, '19', t('Cannot retrieve DanielB row after commit.')); + $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3437,9 +3490,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Because our current database claims to not support transactions, // the inserted rows should be present despite the attempt to roll back. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); - $this->assertIdentical($saved_age, '24', t('DavidB not rolled back, since transactions are not supported.')); + $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); - $this->assertIdentical($saved_age, '19', t('DanielB not rolled back, since transactions are not supported.')); + $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3459,9 +3512,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Because we committed, both of the inserted rows should be present. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField(); - $this->assertIdentical($saved_age, '24', t('Can retrieve DavidA row after commit.')); + $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField(); - $this->assertIdentical($saved_age, '19', t('Can retrieve DanielA row after commit.')); + $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3553,7 +3606,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // $this->fail(t('Rolling back a transaction containing DDL should fail.')); } catch (DatabaseTransactionNoActiveException $e) { - $this->pass(t('Rolling back a transaction containing DDL should fail.')); + $this->pass('Rolling back a transaction containing DDL should fail.'); } $this->assertRowPresent('row'); } @@ -3606,7 +3659,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { */ function assertRowPresent($name, $message = NULL) { if (!isset($message)) { - $message = t('Row %name is present.', array('%name' => $name)); + $message = format_string('Row %name is present.', array('%name' => $name)); } $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); return $this->assertTrue($present, $message); @@ -3622,7 +3675,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { */ function assertRowAbsent($name, $message = NULL) { if (!isset($message)) { - $message = t('Row %name is absent.', array('%name' => $name)); + $message = format_string('Row %name is absent.', array('%name' => $name)); } $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); return $this->assertFalse($present, $message); @@ -3646,10 +3699,10 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $this->insertRow('inner'); // Pop the inner transaction. unset($transaction2); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the inner transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction'); // Pop the outer transaction. unset($transaction); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the outer transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction'); $this->assertRowPresent('outer'); $this->assertRowPresent('inner'); @@ -3662,10 +3715,10 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Pop the outer transaction, nothing should happen. unset($transaction); $this->insertRow('inner-after-outer-commit'); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Pop the inner transaction, the whole transaction should commit. unset($transaction2); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowPresent('inner'); $this->assertRowPresent('inner-after-outer-commit'); @@ -3679,11 +3732,11 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Now rollback the inner transaction. $transaction2->rollback(); unset($transaction2); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Pop the outer transaction, it should commit. $this->insertRow('outer-after-inner-rollback'); unset($transaction); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowAbsent('inner'); $this->assertRowPresent('outer-after-inner-rollback'); @@ -3696,11 +3749,11 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $this->insertRow('inner'); // Pop the outer transaction, nothing should happen. unset($transaction); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Now rollback the inner transaction, it should rollback. $transaction2->rollback(); unset($transaction2); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowAbsent('inner'); @@ -3718,23 +3771,23 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { try { $transaction->rollback(); unset($transaction); - $this->fail(t('Rolling back the outer transaction while the inner transaction is active resulted in an exception.')); + $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); } catch (DatabaseTransactionOutOfOrderException $e) { - $this->pass(t('Rolling back the outer transaction while the inner transaction is active resulted in an exception.')); + $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); } - $this->assertFalse($database->inTransaction(), t('No more in a transaction after rolling back the outer transaction')); + $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction'); // Try to commit one inner transaction. unset($transaction3); - $this->pass(t('Trying to commit an inner transaction resulted in an exception.')); + $this->pass('Trying to commit an inner transaction resulted in an exception.'); // Try to rollback one inner transaction. try { $transaction->rollback(); unset($transaction2); - $this->fail(t('Trying to commit an inner transaction resulted in an exception.')); + $this->fail('Trying to commit an inner transaction resulted in an exception.'); } catch (DatabaseTransactionNoActiveException $e) { - $this->pass(t('Trying to commit an inner transaction resulted in an exception.')); + $this->pass('Trying to commit an inner transaction resulted in an exception.'); } $this->assertRowAbsent('outer'); $this->assertRowAbsent('inner'); @@ -3764,9 +3817,9 @@ class DatabaseNextIdCase extends DrupalWebTestCase { // We can test for exact increase in here because we know there is no // other process operating on these tables -- normally we could only // expect $second > $first. - $this->assertEqual($first + 1, $second, t('The second call from a sequence provides a number increased by one.')); + $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.'); $result = db_next_id(1000); - $this->assertEqual($result, 1001, t('Sequence provides a larger number than the existing ID.')); + $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.'); } } @@ -3788,8 +3841,8 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { function testEmpty() { $result = new DatabaseStatementEmpty(); - $this->assertTrue($result instanceof DatabaseStatementInterface, t('Class implements expected interface')); - $this->assertNull($result->fetchObject(), t('Null result returned.')); + $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface'); + $this->assertNull($result->fetchObject(), 'Null result returned.'); } /** @@ -3799,11 +3852,11 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { $result = new DatabaseStatementEmpty(); foreach ($result as $record) { - $this->fail(t('Iterating empty result set should not iterate.')); + $this->fail('Iterating empty result set should not iterate.'); return; } - $this->pass(t('Iterating empty result set skipped iteration.')); + $this->pass('Iterating empty result set skipped iteration.'); } /** @@ -3812,6 +3865,225 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { function testEmptyFetchAll() { $result = new DatabaseStatementEmpty(); - $this->assertEqual($result->fetchAll(), array(), t('Empty array returned from empty result set.')); + $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.'); + } +} + +/** + * Tests management of database connections. + */ +class ConnectionUnitTest extends DrupalUnitTestCase { + + protected $key; + protected $target; + + protected $monitor; + protected $originalCount; + + public static function getInfo() { + return array( + 'name' => 'Connection unit tests', + 'description' => 'Tests management of database connections.', + 'group' => 'Database', + ); + } + + function setUp() { + parent::setUp(); + + $this->key = 'default'; + $this->originalTarget = 'default'; + $this->target = 'DatabaseConnectionUnitTest'; + + // Determine whether the database driver is MySQL. If it is not, the test + // methods will not be executed. + // @todo Make this test driver-agnostic, or find a proper way to skip it. + // @see http://drupal.org/node/1273478 + $connection_info = Database::getConnectionInfo('default'); + $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql'; + if ($this->skipTest) { + // Insert an assertion to prevent Simpletest from interpreting the test + // as failure. + $this->pass('This test is only compatible with MySQL.'); + } + + // Create an additional connection to monitor the connections being opened + // and closed in this test. + // @see TestBase::changeDatabasePrefix() + $connection_info = Database::getConnectionInfo('default'); + Database::addConnectionInfo('default', 'monitor', $connection_info['default']); + global $databases; + $databases['default']['monitor'] = $connection_info['default']; + $this->monitor = Database::getConnection('monitor'); + } + + /** + * Adds a new database connection info to Database. + */ + protected function addConnection() { + // Add a new target to the connection, by cloning the current connection. + $connection_info = Database::getConnectionInfo($this->key); + Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]); + + // Verify that the new target exists. + $info = Database::getConnectionInfo($this->key); + // Note: Custom assertion message to not expose database credentials. + $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.'); + } + + /** + * Returns the connection ID of the current test connection. + * + * @return integer + */ + protected function getConnectionID() { + return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField(); } + + /** + * Asserts that a connection ID exists. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id))); + } + + /** + * Asserts that a connection ID does not exist. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertNoConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id))); + } + + /** + * Tests Database::closeConnection() without query. + * + * @todo getConnectionID() executes a query. + */ + function testOpenClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query. + */ + function testOpenQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES'); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query and custom prefetch method. + */ + function testOpenQueryPrefetchClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol(); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a select query. + */ + function testOpenSelectQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Create a table. + $name = 'foo'; + Database::getConnection($this->target, $this->key)->schema()->createTable($name, array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + ), + ), + )); + + // Execute a query. + Database::getConnection($this->target, $this->key)->select('foo', 'f') + ->fields('f', array('name')) + ->execute() + ->fetchAll(); + + // Drop the table. + Database::getConnection($this->target, $this->key)->schema()->dropTable($name); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + } diff --git a/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index ee0ce89022db56138519a5ee27150c190dc1cc08..7e8e2c67b8bd1cf77a8d744a6fe003097bedf89c 100644 --- a/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 225e5165e1fbe81d79fb1c21b75123e8f46bebe6..df7a7199f753afa4a19bde844b07ad8d6834b231 100644 --- a/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/entity_cache_test.info b/modules/simpletest/tests/entity_cache_test.info index f9249fc53351558d2441d4598cc00027269f7067..4949544657d5ad57b98fb40a224fafab09f1cdce 100644 --- a/modules/simpletest/tests/entity_cache_test.info +++ b/modules/simpletest/tests/entity_cache_test.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/entity_cache_test_dependency.info b/modules/simpletest/tests/entity_cache_test_dependency.info index b03e24438fed693a6ce7947275c006a709e017c7..3a5eaa87ce5963a3d4a189ae44384dcc691689f0 100644 --- a/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/modules/simpletest/tests/entity_cache_test_dependency.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/entity_crud_hook_test.info b/modules/simpletest/tests/entity_crud_hook_test.info index a27e9ebeffba8fa75b433dfbf7f1c404c81a4536..058e2087495de43fd6e269eddccf68fa96a37733 100644 --- a/modules/simpletest/tests/entity_crud_hook_test.info +++ b/modules/simpletest/tests/entity_crud_hook_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/entity_query_access_test.info b/modules/simpletest/tests/entity_query_access_test.info index 396f07e01075974f602b126fbef7451564e0ae0a..d122fe6febc8f2cb5bb5ce2e40e5bfaf4d5148bc 100644 --- a/modules/simpletest/tests/entity_query_access_test.info +++ b/modules/simpletest/tests/entity_query_access_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/error_test.info b/modules/simpletest/tests/error_test.info index e009ed4b8b4b5b049354a4d916c40e4c590c2b2b..bf8b1d01f45afa2ca41d6d77c880312cbf030e14 100644 --- a/modules/simpletest/tests/error_test.info +++ b/modules/simpletest/tests/error_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 3df31ba5fe0b448db5a0c2640b4ab69f6b86177e..ebaa0c03483fa3ff27568b2a9027f70c00b8f0fd 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -2578,6 +2578,15 @@ class FileNameMungingTest extends FileTestCase { $this->assertNotEqual($munged_name, $this->name, t('The new filename (%munged) has been modified from the original (%original)', array('%munged' => $munged_name, '%original' => $this->name))); } + /** + * Tests munging with a null byte in the filename. + */ + function testMungeNullByte() { + $prefix = $this->randomName(); + $filename = $prefix . '.' . $this->bad_extension . "\0.txt"; + $this->assertEqual(file_munge_filename($filename, ''), $prefix . '.' . $this->bad_extension . '_.txt', 'A filename with a null byte is correctly munged to remove the null byte.'); + } + /** * If the allow_insecure_uploads variable evaluates to true, the file should * come out untouched, no matter how evil the filename. diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info index 047e5a85701ad8b89d2dd0ec29e0251e5d5e594b..5dcecb02eb371d1a8263600ee8e79cc13ca91c8a 100644 --- a/modules/simpletest/tests/file_test.info +++ b/modules/simpletest/tests/file_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info index dd95f92b14a9bbbe39f5832318a95210b7180109..c8e98eb85e6aab472c7204d5eb6b257d317a0a65 100644 --- a/modules/simpletest/tests/filter_test.info +++ b/modules/simpletest/tests/filter_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index 7eac198ad2c942e71929b9a4d044b81984cc525d..7c44b1bed46f74aec4dd7ad1f1642d1186c8a183 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -657,6 +657,17 @@ class FormValidationTestCase extends DrupalWebTestCase { $this->assertText(t('!name field is required.', array('!name' => 'Title'))); $this->assertText('Test element is invalid'); } + + /** + * Tests error border of multiple fields with same name in a page. + */ + function testMultiFormSameNameErrorClass() { + $this->drupalGet('form-test/double-form'); + $edit = array(); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertFieldByXpath('//input[@id="edit-name" and contains(@class, "error")]', NULL, 'Error input form element class found for first element.'); + $this->assertNoFieldByXpath('//input[@id="edit-name--2" and contains(@class, "error")]', NULL, 'No error input form element class found for second element.'); + } } /** diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index d86866370c815b55962194b595514be6f6f5d718..8b38d2e1ad5a48f00954e19f3dd1f8dcaa68b965 100644 --- a/modules/simpletest/tests/form_test.info +++ b/modules/simpletest/tests/form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index 50d5158156159a3ceab2c0f5b0cbec05c1cbff5a..7aa41c5cb3622bdb49d03979abf9137c6c05aba5 100644 --- a/modules/simpletest/tests/image_test.info +++ b/modules/simpletest/tests/image_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index 404cd8de97002cfa0b6c94caf6699f3b78ec9483..55a2c6817b4d56f5a7dd9c9710b1704eb7959fb6 100644 --- a/modules/simpletest/tests/menu_test.info +++ b/modules/simpletest/tests/menu_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/module_test.info b/modules/simpletest/tests/module_test.info index 40eacedee768ba55332837f350e1a86156fac27c..19059d6f1ae6f64717c0f7d642f15c0ae87a9caa 100644 --- a/modules/simpletest/tests/module_test.info +++ b/modules/simpletest/tests/module_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/path_test.info b/modules/simpletest/tests/path_test.info index 29b5f766857fb8150e5f36d72c40dd4f2828ef45..0b416c90f486243c88e04bf111e201b14b0fdd52 100644 --- a/modules/simpletest/tests/path_test.info +++ b/modules/simpletest/tests/path_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3098c925fd0c892e98c952dc8c91f5c9413d8463 --- /dev/null +++ b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\psr_0_test\Tests; + +class ExampleTest extends \DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'PSR0 example test: PSR-0 in disabled modules.', + 'description' => 'We want to assert that this test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..324ed439a2b0aaf25167c68806860d9572ec53e8 --- /dev/null +++ b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\psr_0_test\Tests\Nested; + +class NestedExampleTest extends \DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'PSR0 example test: PSR-0 in nested subfolders.', + 'description' => 'We want to assert that this PSR-0 test case is being discovered.', + 'group' => 'SimpleTest', + ); + } + + function testArithmetics() { + $this->assert(1 + 1 == 2, '1 + 1 == 2'); + } +} diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.info b/modules/simpletest/tests/psr_0_test/psr_0_test.info new file mode 100644 index 0000000000000000000000000000000000000000..5e0878143cf6c564aa948461a19ec2691867ff2d --- /dev/null +++ b/modules/simpletest/tests/psr_0_test/psr_0_test.info @@ -0,0 +1,11 @@ +name = PSR-0 Test cases +description = Test classes to be discovered by simpletest. +core = 7.x + +hidden = TRUE +package = Testing + +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" +project = "drupal" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.module b/modules/simpletest/tests/psr_0_test/psr_0_test.module new file mode 100644 index 0000000000000000000000000000000000000000..b3d9bbc7f3711e882119cd6b3af051245d859d04 --- /dev/null +++ b/modules/simpletest/tests/psr_0_test/psr_0_test.module @@ -0,0 +1 @@ +<?php diff --git a/modules/simpletest/tests/requirements1_test.info b/modules/simpletest/tests/requirements1_test.info index d3ad0336e91a26e3776ebda744735418697538aa..224c89395587785b10705982c6ffa8c5d0d77c53 100644 --- a/modules/simpletest/tests/requirements1_test.info +++ b/modules/simpletest/tests/requirements1_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/requirements2_test.info b/modules/simpletest/tests/requirements2_test.info index 322b37efd7d9d01407ee48cadefdc61aa5d34ae2..478ee758cc867f4d074b58e6222372028ae2a2a6 100644 --- a/modules/simpletest/tests/requirements2_test.info +++ b/modules/simpletest/tests/requirements2_test.info @@ -7,8 +7,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info index b4b5ea9a8b84bd1326325c55362f15b59c09a40e..e5ad50ea5607a9e6f0bd3b0f0bb6dc7376785c27 100644 --- a/modules/simpletest/tests/session_test.info +++ b/modules/simpletest/tests/session_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info index f556fd8d2f89bae73dfabeeb2b81d972de86be3a..431d20a54db2a6d0c13bd92ac721b5f84b1a3f60 100644 --- a/modules/simpletest/tests/system_dependencies_test.info +++ b/modules/simpletest/tests/system_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index a1701b0f6df3d2f060a40e0cac080e7147fd5b73..18bd8a62d76094bbd1552b29e18f6cda1afae56d 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_incompatible_core_version_test.info b/modules/simpletest/tests/system_incompatible_core_version_test.info index 8133d3b51f3242960d9e01c155bca6c48d97f430..9f445c58f5c4e8ee1f42d7378310af3678587cf5 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,8 +5,8 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index e174c76dddce60080b95f576569264ed9a579104..19e7ad02a8308aeb45ffd18a817b1a8402b8688b 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,8 +7,8 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info index 558a4cb11c1e95bc69f183453b840a609d15e8c9..2b3c0d9d4dec8f0c9ca8d9898a4cee61f219c403 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,8 +5,8 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/system_test.info b/modules/simpletest/tests/system_test.info index 884a65aee04ab9ce7663f8febcc9aee35eed630d..cb59ab63ae9ca692440002119d999b07f82543c7 100644 --- a/modules/simpletest/tests/system_test.info +++ b/modules/simpletest/tests/system_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index 6cf082379d7cfcf4339784a5b6692ad099b45fe0..41b3dc5e6da283985a92b2167f8849613e44bfad 100644 --- a/modules/simpletest/tests/taxonomy_test.info +++ b/modules/simpletest/tests/taxonomy_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index 4ac92f8f983924cbbb36cbe4483f5cda1a2a542c..9c1507e134b53cbe20e03e03beb5afd7f726d63f 100644 --- a/modules/simpletest/tests/theme_test.info +++ b/modules/simpletest/tests/theme_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index 79dd3daefaa16bffe0d88588a37acc53e7c02722..fb3c015e529b58e983157922a0c2fb5cd4f2a2ee 100644 --- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index cb63c50d518b5a44ccc5ea25379d588236312268..245797534b31320026d402d028218fee264dd382 100644 --- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info index 654495aea0a94c7ff26950d8fa0ff5ecaff82b54..fd9ef75b38e4ed959990dda964cc6c4e9bf0758d 100644 --- a/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,8 +17,8 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info index 62506580675ffbb41582345f4143acdb9bb67b67..a76708873dc3a60bbe6de6a4ae0e53d5ec890c15 100644 --- a/modules/simpletest/tests/update_script_test.info +++ b/modules/simpletest/tests/update_script_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index 640724d7f21c62b0d5f8cf94dc6a21e2c67fb18f..e3c860e9a3f53bab28345a422688ae2eb970416c 100644 --- a/modules/simpletest/tests/update_test_1.info +++ b/modules/simpletest/tests/update_test_1.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index 640724d7f21c62b0d5f8cf94dc6a21e2c67fb18f..e3c860e9a3f53bab28345a422688ae2eb970416c 100644 --- a/modules/simpletest/tests/update_test_2.info +++ b/modules/simpletest/tests/update_test_2.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index 640724d7f21c62b0d5f8cf94dc6a21e2c67fb18f..e3c860e9a3f53bab28345a422688ae2eb970416c 100644 --- a/modules/simpletest/tests/update_test_3.info +++ b/modules/simpletest/tests/update_test_3.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/upgrade/drupal-6.duplicate-permission.database.php b/modules/simpletest/tests/upgrade/drupal-6.duplicate-permission.database.php new file mode 100644 index 0000000000000000000000000000000000000000..5da5e84442bf729c92c673bcce48b89ca1bac056 --- /dev/null +++ b/modules/simpletest/tests/upgrade/drupal-6.duplicate-permission.database.php @@ -0,0 +1,8 @@ +<?php + +// Simulate duplicated permission condition. +db_update('permission')->fields(array( + 'perm' => 'access content, access content', +)) +->condition('pid', 1) +->execute(); diff --git a/modules/simpletest/tests/upgrade/upgrade.test b/modules/simpletest/tests/upgrade/upgrade.test index 9df8ec77934cfe1ec7920e4ca65643e3dd57d1e2..cc849aa794eb00d4b42915938a4532add65e337e 100644 --- a/modules/simpletest/tests/upgrade/upgrade.test +++ b/modules/simpletest/tests/upgrade/upgrade.test @@ -566,6 +566,20 @@ class BasicMinimalUpdatePath extends UpdatePathTestCase { // Confirm that no {menu_links} entry exists for user/autocomplete. $result = db_query('SELECT COUNT(*) FROM {menu_links} WHERE link_path = :user_autocomplete', array(':user_autocomplete' => 'user/autocomplete'))->fetchField(); $this->assertFalse($result, t('No {menu_links} entry exists for user/autocomplete')); + + // Confirm that a date format that just differs in the case can be added. + $admin_date_format = 'j M y'; + $edit = array('date_format' => $admin_date_format); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + + // Add a new date format which just differs in the case. + $admin_date_format_uppercase = 'j M Y'; + $edit = array('date_format' => $admin_date_format_uppercase); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertText(t('Custom date format added.')); + + // Verify that the unique key on {date_formats}.format still exists. + $this->assertTrue(db_index_exists('date_formats', 'formats'), 'Unique key on {date_formats} exists'); } } diff --git a/modules/simpletest/tests/upgrade/upgrade.user.test b/modules/simpletest/tests/upgrade/upgrade.user.test index c33ba11795797a76193a91e1cbd756361f1db62c..d33234e4312fabc709b8c8a1912f9ffbce98d65b 100644 --- a/modules/simpletest/tests/upgrade/upgrade.user.test +++ b/modules/simpletest/tests/upgrade/upgrade.user.test @@ -61,3 +61,32 @@ class UserUpgradePathNoPasswordTokenTestCase extends UpgradePathTestCase { $this->assertEqual(variable_get('user_mail_register_no_approval_required_body'), '[user:name], [site:name], [site:url], [site:url-brief], [user:mail], [date:medium], [site:login-url], [user:edit-url], [user:one-time-login-url].', 'Existing email templates have been modified (password token not involved).'); } } + +/** + * Upgrade test for user.module (duplicated permission). + */ +class UserUpgradePathDuplicatedPermissionTestCase extends UpgradePathTestCase { + public static function getInfo() { + return array( + 'name' => 'User upgrade path (duplicated permission)', + 'description' => 'User upgrade path tests (duplicated permission).', + 'group' => 'Upgrade path', + ); + } + + public function setUp() { + // Path to the database dump files. + $this->databaseDumpFiles = array( + drupal_get_path('module', 'simpletest') . '/tests/upgrade/drupal-6.bare.database.php', + drupal_get_path('module', 'simpletest') . '/tests/upgrade/drupal-6.duplicate-permission.database.php', + ); + parent::setUp(); + } + + /** + * Test a successful upgrade. + */ + public function testUserUpgrade() { + $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); + } +} diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index c34b15e4a98a8b4fe5081c735ca76b5e001f7006..fafae63657fe3dd1661986a5b1e330ae31e0815a 100644 --- a/modules/simpletest/tests/url_alter_test.info +++ b/modules/simpletest/tests/url_alter_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index 37b45dc50e485ad6f811a344ad643e63e8b6cae4..772857d5ad6cf34c8fd6b3de218c4c1477bef068 100644 --- a/modules/simpletest/tests/xmlrpc_test.info +++ b/modules/simpletest/tests/xmlrpc_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 695bc64aaf1d4a21528a4c3d173f0a962c4c6a33..890681ba40d483e9ce3c60a09b832c875e22ccb0 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -6,8 +6,8 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index b88a5f6c7e414d0e80d3ae49aee2230d0990f66a..f8eaba730f513b99311ee5544bccea0bcaa8dec6 100644 --- a/modules/syslog/syslog.info +++ b/modules/syslog/syslog.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = syslog.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/system/language.api.php b/modules/system/language.api.php index 671479309be0a8e90643e2b337f5cbacf03132e6..d868b6fef30a68b293e4192765701213a1bc0684 100644 --- a/modules/system/language.api.php +++ b/modules/system/language.api.php @@ -62,16 +62,22 @@ function hook_language_switch_links_alter(array &$links, $type, $path) { } /** - * Allow modules to define their own language types. + * Define language types. * * @return - * An array of language type definitions. Each language type has an identifier - * key. The language type definition is an associative array that may contain - * the following key-value pairs: - * - "name": The human-readable language type identifier. - * - "description": A description of the language type. - * - "fixed": An array of language provider identifiers. Defining this key - * makes the language type non-configurable. + * An associative array of language type definitions. The keys are the + * identifiers, which are also used as names for global variables representing + * the types in the bootstrap phase. The values are associative arrays that + * may contain the following elements: + * - name: The human-readable language type identifier. + * - description: A description of the language type. + * - fixed: A fixed array of language negotiation provider identifiers to use + * to initialize this language. Defining this key makes the language type + * non-configurable, so it will always use the specified providers in the + * given priority order. Omit to make the language type configurable. + * + * @see hook_language_types_info_alter() + * @ingroup language_negotiation */ function hook_language_types_info() { return array( @@ -90,6 +96,9 @@ function hook_language_types_info() { * * @param $language_types * Array of language type definitions. + * + * @see hook_language_types_info() + * @ingroup language_negotiation */ function hook_language_types_info_alter(array &$language_types) { if (isset($language_types['custom_language_type'])) { @@ -98,31 +107,35 @@ function hook_language_types_info_alter(array &$language_types) { } /** - * Allow modules to define their own language providers. + * Define language negotiation providers. * * @return - * An array of language provider definitions. Each language provider has an - * identifier key. The language provider definition is an associative array - * that may contain the following key-value pairs: - * - "types": An array of allowed language types. If a language provider does - * not specify which language types it should be used with, it will be - * available for all the configurable language types. - * - "callbacks": An array of functions that will be called to perform various - * tasks. Possible key-value pairs are: - * - "language": Required. The callback that will determine the language - * value. - * - "switcher": The callback that will determine the language switch links - * associated to the current language provider. - * - "url_rewrite": The callback that will provide URL rewriting. - * - "file": A file that will be included before the callback is invoked; this - * allows callback functions to be in separate files. - * - "weight": The default weight the language provider has. - * - "name": A human-readable identifier. - * - "description": A description of the language provider. - * - "config": An internal path pointing to the language provider - * configuration page. - * - "cache": The value Drupal's page cache should be set to for the current - * language provider to be invoked. + * An associative array of language negotiation provider definitions. The keys + * are provider identifiers, and the values are associative arrays definining + * each provider, with the following elements: + * - types: An array of allowed language types. If a language negotiation + * provider does not specify which language types it should be used with, it + * will be available for all the configurable language types. + * - callbacks: An associative array of functions that will be called to + * perform various tasks. Possible elements are: + * - negotiation: (required) Name of the callback function that determines + * the language value. + * - language_switch: (optional) Name of the callback function that + * determines links for a language switcher block associated with this + * provider. See language_switcher_url() for an example. + * - url_rewrite: (optional) Name of the callback function that provides URL + * rewriting, if needed by this provider. + * - file: The file where callback functions are defined (this file will be + * included before the callbacks are invoked). + * - weight: The default weight of the provider. + * - name: The translated human-readable name for the provider. + * - description: A translated longer description of the provider. + * - config: An internal path pointing to the provider's configuration page. + * - cache: The value Drupal's page cache should be set to for the current + * provider to be invoked. + * + * @see hook_language_negotiation_info_alter() + * @ingroup language_negotiation */ function hook_language_negotiation_info() { return array( @@ -135,18 +148,21 @@ function hook_language_negotiation_info() { 'file' => drupal_get_path('module', 'custom') . '/custom.module', 'weight' => -4, 'types' => array('custom_language_type'), - 'name' => t('Custom language provider'), - 'description' => t('This is a custom language provider.'), + 'name' => t('Custom language negotiation provider'), + 'description' => t('This is a custom language negotiation provider.'), 'cache' => 0, ), ); } /** - * Perform alterations on language providers. + * Perform alterations on language negoiation providers. * * @param $language_providers - * Array of language provider definitions. + * Array of language negotiation provider definitions. + * + * @see hook_language_negotiation_info() + * @ingroup language_negotiation */ function hook_language_negotiation_info_alter(array &$language_providers) { if (isset($language_providers['custom_language_provider'])) { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 061898c8557d3ec6003b044595c694621266fe79..05543be6a5fb09bf40bd75537e9ea99ab7726b42 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1594,6 +1594,7 @@ function system_site_information_settings_validate($form, &$form_state) { * @ingroup forms */ function system_cron_settings() { + global $base_url; $form['description'] = array( '#markup' => '<p>' . t('Cron takes care of running periodic tasks like checking for updates and indexing content for search.') . '</p>', ); @@ -1606,6 +1607,11 @@ function system_cron_settings() { $form['status'] = array( '#markup' => $status, ); + + $form['cron_url'] = array( + '#markup' => '<p>' . t('To run cron from outside the site, go to <a href="!cron">!cron</a>', array('!cron' => url($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))))) . '</p>', + ); + $form['cron'] = array( '#type' => 'fieldset', ); diff --git a/modules/system/system.api.php b/modules/system/system.api.php index adef26141fbc2ee6405082c68088848968fd3c09..2fefdce7ff1c4273a0eb9098936f1526b091e18e 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -154,7 +154,10 @@ function hook_hook_info_alter(&$hooks) { * the name of the bundle object. * - bundles: An array describing all bundles for this object type. Keys are * bundles machine names, as found in the objects' 'bundle' property - * (defined in the 'entity keys' entry above). Elements: + * (defined in the 'entity keys' entry above). This entry can be omitted if + * this entity type exposes a single bundle (all entities have the same + * collection of fields). The name of this single bundle will be the same as + * the entity type. Elements: * - label: The human-readable name of the bundle. * - uri callback: Same as the 'uri callback' key documented above for the * entity type, but for the bundle only. When determining the URI of an @@ -1201,6 +1204,10 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) { * same weight are ordered alphabetically. * - "menu_name": Optional. Set this to a custom menu if you don't want your * item to be placed in Navigation. + * - "expanded": Optional. If set to TRUE, and if a menu link is provided for + * this menu item (as a result of other properties), then the menu link is + * always expanded, equivalent to its 'always expanded' checkbox being set + * in the UI. * - "context": (optional) Defines the context a tab may appear in. By * default, all tabs are only displayed as local tasks when being rendered * in a page context. All tabs that should be accessible as contextual links @@ -1412,7 +1419,7 @@ function hook_menu_link_delete($link) { * - #link: An associative array containing: * - title: The localized title of the link. * - href: The system path to link to. - * - localized_options: An array of options to pass to url(). + * - localized_options: An array of options to pass to l(). * - #active: Whether the link should be marked as 'active'. * * @param $data @@ -1929,8 +1936,9 @@ function hook_image_toolkits() { * The drupal_mail() id of the message. Look at module source code or * drupal_mail() for possible id values. * - 'to': - * The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * The address or addresses the message will be sent to. The formatting of + * this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - 'from': * The address the message will be marked as being from, which is * either a custom address or the site-wide default email address. @@ -2100,7 +2108,9 @@ function hook_permission() { * specify how a particular render array is to be rendered as HTML (this is * usually the case if the theme function is assigned to the render array's * #theme property), or they return the HTML that should be returned by an - * invocation of theme(). + * invocation of theme(). See + * @link http://drupal.org/node/933976 Using the theme layer Drupal 7.x @endlink + * for more information on how to implement theme hooks. * * The following parameters are all optional. * @@ -2196,6 +2206,8 @@ function hook_permission() { * 'module', 'theme_engine', or 'theme'. * - theme path: (automatically derived) The directory path of the theme or * module, so that it doesn't need to be looked up. + * + * @see hook_theme_registry_alter() */ function hook_theme($existing, $type, $theme, $path) { return array( @@ -2290,7 +2302,8 @@ function hook_theme_registry_alter(&$theme_registry) { * @return * The machine-readable name of the theme that should be used for the current * page request. The value returned from this function will only have an - * effect if it corresponds to a currently-active theme on the site. + * effect if it corresponds to a currently-active theme on the site. Do not + * return a value if you do not wish to set a custom theme. */ function hook_custom_theme() { // Allow the user to request a particular theme via a query parameter. @@ -2476,8 +2489,9 @@ function hook_watchdog(array $log_entry) { * An array to be filled in. Elements in this array include: * - id: An ID to identify the mail sent. Look at module source code * or drupal_mail() for possible id values. - * - to: The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * - to: The address or addresses the message will be sent to. The formatting + * of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - subject: Subject of the e-mail to be sent. This must not contain any * newline characters, or the mail may not be sent properly. drupal_mail() * sets this to an empty string when the hook is invoked. @@ -3092,44 +3106,48 @@ function hook_schema() { 'description' => 'The primary identifier for a node.', 'type' => 'serial', 'unsigned' => TRUE, - 'not null' => TRUE), + 'not null' => TRUE, + ), 'vid' => array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0), + 'default' => 0, + ), 'type' => array( 'description' => 'The {node_type} of this node.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, - 'default' => ''), + 'default' => '', + ), 'title' => array( 'description' => 'The title of this node, always treated as non-markup plain text.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, - 'default' => ''), + 'default' => '', ), + ), 'indexes' => array( 'node_changed' => array('changed'), 'node_created' => array('created'), - ), + ), 'unique keys' => array( 'nid_vid' => array('nid', 'vid'), - 'vid' => array('vid') - ), + 'vid' => array('vid'), + ), 'foreign keys' => array( 'node_revision' => array( 'table' => 'node_revision', 'columns' => array('vid' => 'vid'), - ), + ), 'node_author' => array( 'table' => 'users', - 'columns' => array('uid' => 'uid') - ), - ), + 'columns' => array('uid' => 'uid'), + ), + ), 'primary key' => array('nid'), ); return $schema; @@ -3236,8 +3254,7 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { * a hook_update_N() is added to the module, this function needs to be updated * to reflect the current version of the database schema. * - * See the Schema API documentation at - * @link http://drupal.org/node/146843 http://drupal.org/node/146843 @endlink + * See the @link http://drupal.org/node/146843 Schema API documentation @endlink * for details on hook_schema and how database tables are defined. * * Note that since this function is called from a full bootstrap, all functions @@ -3622,6 +3639,9 @@ function hook_registry_files_alter(&$files, $modules) { * variable_del() before your last task has completed and control is handed * back to the installer. * + * @param array $install_state + * An array of information about the current installation state. + * * @return * A keyed array of tasks the profile will perform during the final stage of * the installation. Each key represents the name of a function (usually a @@ -3679,7 +3699,7 @@ function hook_registry_files_alter(&$files, $modules) { * @see install_state_defaults() * @see batch_set() */ -function hook_install_tasks() { +function hook_install_tasks(&$install_state) { // Here, we define a variable to allow tasks to indicate that a particular, // processor-intensive batch process needs to be triggered later on in the // installation. diff --git a/modules/system/system.info b/modules/system/system.info index 533650297ca27f3eb1365b90436019b64f923f7d..30ba186b57d1b99c552aaabd04c58c8327c526d4 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -12,8 +12,8 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/system/system.install b/modules/system/system.install index 7b667678ec0dcc547c1a923345f5ec3a5db220e3..1b037b82bac6c6491b2a8d8d7586fd5b73bbfa07 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -744,6 +744,7 @@ function system_schema() { 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, + 'binary' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', @@ -1889,7 +1890,7 @@ function system_update_7007() { $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid ASC"); $query = db_insert('role_permission')->fields(array('rid', 'permission')); foreach ($result as $role) { - foreach (explode(', ', $role->perm) as $perm) { + foreach (array_unique(explode(', ', $role->perm)) as $perm) { $query->values(array( 'rid' => $role->rid, 'permission' => $perm, @@ -2758,12 +2759,14 @@ function system_update_7061(&$sandbox) { // Retrieve a list of node revisions that have uploaded files attached. // DISTINCT queries are expensive, especially when paged, so we store the // data in its own table for the duration of the update. - $table = array( - 'description' => t('Stores temporary data for system_update_7061.'), - 'fields' => array('vid' => array('type' => 'int')), - 'primary key' => array('vid'), - ); - db_create_table('system_update_7061', $table); + if (!db_table_exists('system_update_7061')) { + $table = array( + 'description' => t('Stores temporary data for system_update_7061.'), + 'fields' => array('vid' => array('type' => 'int')), + 'primary key' => array('vid'), + ); + db_create_table('system_update_7061', $table); + } $query = db_select('upload', 'u'); $query->distinct(); $query->addField('u','vid'); @@ -3032,6 +3035,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_drop_unique_key('file_managed', 'uri'); db_change_field('file_managed', 'uri', 'uri', array( 'description' => 'The URI to access the file (either local or remote).', 'type' => 'varchar', @@ -3040,6 +3044,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_add_unique_key('file_managed', 'uri', array('uri')); } /** @@ -3086,6 +3091,21 @@ function system_update_7077() { )); } + +/** + * Add binary to {date_formats}.format. + */ +function system_update_7078() { + db_drop_unique_key('date_formats', 'formats'); + db_change_field('date_formats', 'format', 'format', array( + 'description' => 'The date format string.', + 'type' => 'varchar', + 'length' => 100, + 'not null' => TRUE, + 'binary' => TRUE, + ), array('unique keys' => array('formats' => array('format', 'type')))); +} + /** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. diff --git a/modules/system/system.module b/modules/system/system.module index d47ab8a8100e67b309594e3d8e65f811da3f940d..2bbcd7fcf50be2ae51ac093e2873b0af4c947d42 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1907,17 +1907,18 @@ function system_init() { // Ignore slave database servers for this request. // - // In Drupal's distributed database structure, new data is written to the master - // and then propagated to the slave servers. This means there is a lag - // between when data is written to the master and when it is available on the slave. - // At these times, we will want to avoid using a slave server temporarily. - // For example, if a user posts a new node then we want to disable the slave - // server for that user temporarily to allow the slave server to catch up. - // That way, that user will see their changes immediately while for other - // users we still get the benefits of having a slave server, just with slightly - // stale data. Code that wants to disable the slave server should use the - // db_set_ignore_slave() function to set $_SESSION['ignore_slave_server'] to - // the timestamp after which the slave can be re-enabled. + // In Drupal's distributed database structure, new data is written to the + // master and then propagated to the slave servers. This means there is a + // lag between when data is written to the master and when it is available on + // the slave. At these times, we will want to avoid using a slave server + // temporarily. For example, if a user posts a new node then we want to + // disable the slave server for that user temporarily to allow the slave + // server to catch up. That way, that user will see their changes immediately + // while for other users we still get the benefits of having a slave server, + // just with slightly stale data. Code that wants to disable the slave + // server should use the db_ignore_slave() function to set + // $_SESSION['ignore_slave_server'] to the timestamp after which the slave + // can be re-enabled. if (isset($_SESSION['ignore_slave_server'])) { if ($_SESSION['ignore_slave_server'] >= REQUEST_TIME) { Database::ignoreTarget('default', 'slave'); diff --git a/modules/system/system.test b/modules/system/system.test index abd21aae6e234b4b7f88340a79bb1ac524501743..990b16f041ddd40b4fa681a63c6e094e6ea892b6 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -2681,3 +2681,33 @@ class SystemIndexPhpTest extends DrupalWebTestCase { } } +/** + * Test token replacement in strings. + */ +class TokenScanTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Token scanning', + 'description' => 'Scan token-like patterns in a dummy text to check token scanning.', + 'group' => 'System', + ); + } + + /** + * Scans dummy text, then tests the output. + */ + function testTokenScan() { + // Define text with valid and not valid, fake and existing token-like + // strings. + $text = 'First a [valid:simple], but dummy token, and a dummy [valid:token with: spaces].'; + $text .= 'Then a [not valid:token].'; + $text .= 'Last an existing token: [node:author:name].'; + $token_wannabes = token_scan($text); + + $this->assertTrue(isset($token_wannabes['valid']['simple']), 'A simple valid token has been matched.'); + $this->assertTrue(isset($token_wannabes['valid']['token with: spaces']), 'A valid token with space characters in the token name has been matched.'); + $this->assertFalse(isset($token_wannabes['not valid']), 'An invalid token with spaces in the token type has not been matched.'); + $this->assertTrue(isset($token_wannabes['node']), 'An existing valid token has been matched.'); + } +} diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index 0df1ad955c8ed6b511f49480ba89be090d9ac629..a14d788b149ddb47066a6303edfe9c0e0dbe3ce2 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -73,8 +73,12 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { return array(); } + /** + * Returns a list of post install actions. + */ public function postInstallTasks() { return array( + l(t('Install another module'), 'admin/modules/install'), l(t('Enable newly added modules'), 'admin/modules'), l(t('Administration pages'), 'admin'), ); diff --git a/modules/taxonomy/taxonomy-term.tpl.php b/modules/taxonomy/taxonomy-term.tpl.php index d410d1ef3bef3812dd1daf2cfe34a6aeca999119..a225c3a577128450699dc436798ffdcf259126e9 100644 --- a/modules/taxonomy/taxonomy-term.tpl.php +++ b/modules/taxonomy/taxonomy-term.tpl.php @@ -5,7 +5,8 @@ * Default theme implementation to display a term. * * Available variables: - * - $name: the (sanitized) name of the term. + * - $name: (deprecated) The unsanitized name of the term. Use $term_name + * instead. * - $content: An array of items for the content of the term (fields and * description). Use render($content) to print them all, or print a subset * such as render($content['field_example']). Use diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index ec6e2e6c3fab8869c3fde5a828f67a8da6598e0d..12ecebd6fa45a7decbf26a4854dd1d240a91cb07 100644 --- a/modules/taxonomy/taxonomy.info +++ b/modules/taxonomy/taxonomy.info @@ -8,8 +8,8 @@ files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc index 299c7bb6ee1f158184b593b720e58e2b633cd7c6..975ff1203a6be5e12789f6903eed912f12ae0703 100644 --- a/modules/taxonomy/taxonomy.pages.inc +++ b/modules/taxonomy/taxonomy.pages.inc @@ -118,7 +118,7 @@ function taxonomy_term_feed($term) { * @see taxonomy_menu() * @see taxonomy_field_widget_info() */ -function taxonomy_autocomplete($field_name, $tags_typed = '') { +function taxonomy_autocomplete($field_name = '', $tags_typed = '') { // If the request has a '/' in the search text, then the menu system will have // split it into multiple arguments, recover the intended $tags_typed. $args = func_get_args(); @@ -138,7 +138,7 @@ function taxonomy_autocomplete($field_name, $tags_typed = '') { $tags_typed = drupal_explode_tags($tags_typed); $tag_last = drupal_strtolower(array_pop($tags_typed)); - $matches = array(); + $term_matches = array(); if ($tag_last != '') { // Part of the criteria for the query come from the field's own settings. @@ -167,7 +167,6 @@ function taxonomy_autocomplete($field_name, $tags_typed = '') { $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; - $term_matches = array(); foreach ($tags_return as $tid => $name) { $n = $name; // Term names containing commas or quotes must be wrapped in quotes. diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index 123bdce40fed3b8f74a2d4afddd224f503429cdb..270ed5802901b48dc65ce54c93632aa0dcc62954 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -722,6 +722,13 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { $this->assertFalse(field_info_field($field_name), format_string('Field %field_name does not exist.', array('%field_name' => $field_name))); $this->drupalGet('taxonomy/autocomplete/' . $field_name . '/' . $tag); $this->assertRaw($message, 'Autocomplete returns correct error message when the taxonomy field does not exist.'); + + // Test the autocomplete path without passing a field_name and terms. + // This should not trigger a PHP notice. + $field_name = ''; + $message = t("Taxonomy field @field_name not found.", array('@field_name' => $field_name)); + $this->drupalGet('taxonomy/autocomplete'); + $this->assertRaw($message, 'Autocomplete returns correct error message when no taxonomy field is given.'); } /** diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info index 70d98c361861c0135a981accf7483af7aebcbaab..030658eaaeaa83cd52d3dbc6097a3657a3a4c60a 100644 --- a/modules/toolbar/toolbar.info +++ b/modules/toolbar/toolbar.info @@ -4,8 +4,8 @@ core = 7.x package = Core version = VERSION -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info index 0158a6593af64e314303cc4f977555702c5c6805..04692f64a151d9261d1f794b91dc2e93f9237914 100644 --- a/modules/tracker/tracker.info +++ b/modules/tracker/tracker.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info index eed01c8b1d7513bae30a66bc01412dd948fae2c8..759c05deaccbbbb9d949f00efe079965af378617 100644 --- a/modules/translation/tests/translation_test.info +++ b/modules/translation/tests/translation_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index b15e758d6121944a149ee7af125d1ef65ea0af48..232f329d9aea314674af29b7c8aa0ebf6ab067b6 100644 --- a/modules/translation/translation.info +++ b/modules/translation/translation.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index 278d6cdf0b5a5471ccd0b6fe804bc19a2d595d19..b467bff36d384963213bec7bd4639946be273818 100644 --- a/modules/trigger/tests/trigger_test.info +++ b/modules/trigger/tests/trigger_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index fba5029b05c45ce27bf34f164cef56339392e3ca..7df9ebabba92a4bfc2e64f966e489fa9a9af60c1 100644 --- a/modules/trigger/trigger.info +++ b/modules/trigger/trigger.info @@ -6,8 +6,8 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index b0e8d6a12aedd0c6eb80eaf714e6003d1ac4b07f..38228127c85ae429b199108c3aedf0719b06c8a5 100644 --- a/modules/update/tests/aaa_update_test.info +++ b/modules/update/tests/aaa_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index e58911a9b9d5159cbd97e345d3d03040da86b507..012aff6f17d5a812f07e434970c856f108e33fca 100644 --- a/modules/update/tests/bbb_update_test.info +++ b/modules/update/tests/bbb_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index c852adfaf1820ebf0ff1a97b93c90aa099a2de40..f5bdac7bbe33ee3c81fbfebe3cfd72b1af3f15bd 100644 --- a/modules/update/tests/ccc_update_test.info +++ b/modules/update/tests/ccc_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 201ca82b45b0cbf0b5d09b667810c1e1fc9f3098..6ddcd4e708271abdddafbd2bba663995ff26c9f8 100644 --- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,8 +3,8 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index e38839070fa60e84ddd26cd90670f5303a5112a4..8a793166d613d22b92ec56209aae6f94ced21677 100644 --- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,8 +4,8 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index fea86aeb9a9df2389d173e700c09340712f06983..acef7b71d64157debdc0d855dd28a9bb857a79e3 100644 --- a/modules/update/tests/update_test.info +++ b/modules/update/tests/update_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/update.info b/modules/update/update.info index 64183b38b57159d4c225a4e184ffa234f3ab6387..145a5121be787dbe53940f3cdfc0f30e72208368 100644 --- a/modules/update/update.info +++ b/modules/update/update.info @@ -6,8 +6,8 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/update/update.module b/modules/update/update.module index 85c0968d5d732099d8308d56affc1d542821b386..d5728be3e29d7249ea68107fea41f727012945f4 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -139,10 +139,10 @@ function update_init() { if (!empty($verbose)) { if (isset($status[$type]['severity'])) { if ($status[$type]['severity'] == REQUIREMENT_ERROR) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) { - drupal_set_message($status[$type]['description'], 'warning'); + drupal_set_message($status[$type]['description'], 'warning', FALSE); } } } @@ -152,7 +152,7 @@ function update_init() { if (isset($status[$type]) && isset($status[$type]['reason']) && $status[$type]['reason'] === UPDATE_NOT_SECURE) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } } } diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info index 871e1fb9f453fb14e378e7cdbb217e7c10debfef..cb6fe9fe1f795304c68db153104dff7062a99f1d 100644 --- a/modules/user/tests/user_form_test.info +++ b/modules/user/tests/user_form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 1cc2c4a2455263e7c22c95cde8b8627be064412f..932c205934db00fe6de31f5bcb31ce57ebc3943f 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -5,6 +5,21 @@ * Admin page callback file for the user module. */ +/** + * Page callback: Generates the appropriate user administration form. + * + * This function generates the user registration, multiple user cancellation, + * or filtered user list admin form, depending on the argument and the POST + * form values. + * + * @param string $callback_arg + * (optional) Indicates which form to build. Defaults to '', which will + * trigger the user filter form. If the POST value 'op' is present, this + * function uses that value as the callback argument. + * + * @return string + * A renderable form array for the respective request. + */ function user_admin($callback_arg = '') { $op = isset($_POST['op']) ? $_POST['op'] : $callback_arg; diff --git a/modules/user/user.api.php b/modules/user/user.api.php index 64c863fe4c63c653234cd0da651082d01813fcea..3afc88ab02836b8b61bea029f8b12766909f33dd 100644 --- a/modules/user/user.api.php +++ b/modules/user/user.api.php @@ -214,9 +214,11 @@ function hook_user_categories() { * to have their data serialized on save. * * @param $edit - * The array of form values submitted by the user. + * The array of form values submitted by the user. Assign values to this + * array to save changes in the database. * @param $account - * The user object on which the operation is performed. + * The user object on which the operation is performed. Values assigned in + * this object will not be saved in the database. * @param $category * The active category of user information being edited. * diff --git a/modules/user/user.info b/modules/user/user.info index 1430b68f989fc3571e4ed89350a4727c5fb80e88..b895ab7e3ff70331243a2cccaea2a3b54d16350b 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/modules/user/user.module b/modules/user/user.module index 622fe4d256ee38dc1ea7ed8d5e58b4f8d339ebf7..c1c7ec21832b91b529c8fa217a62c104c606ea60 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1517,15 +1517,33 @@ function theme_user_list($variables) { return theme('item_list', array('items' => $items, 'title' => $title)); } +/** + * Determines if the current user is anonymous. + * + * @return bool + * TRUE if the user is anonymous, FALSE if the user is authenticated. + */ function user_is_anonymous() { // Menu administrators can see items for anonymous when administering. return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']); } +/** + * Determines if the current user is logged in. + * + * @return bool + * TRUE if the user is logged in, FALSE if the user is anonymous. + */ function user_is_logged_in() { return (bool) $GLOBALS['user']->uid; } +/** + * Determines if the current user has access to the user registration page. + * + * @return bool + * TRUE if the user is not already logged in and can register for an account. + */ function user_register_access() { return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); } @@ -3353,7 +3371,7 @@ function user_filters() { $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; - if ($permissions = $function('permission')) { + if ($permissions = $function()) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index c4b68b9f6147daec45473e98682ecf12dc669cd7..29fe6cf4d120739c80e5ae2032c85149c5486e1a 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -128,12 +128,12 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { // First stage is a confirmation form, then login if ($action == 'login') { - watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp)); // Set the new user. $user = $account; // user_login_finalize() also updates the login timestamp of the // user, which invalidates further use of the one-time login link. user_login_finalize(); + watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp)); drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); // Let the user's password be changed without the current password check. $token = drupal_hash_base64(drupal_random_bytes(55)); diff --git a/patches/redirect-global-905914-130.patch b/patches/redirect-global-905914-130.patch new file mode 100644 index 0000000000000000000000000000000000000000..2a33a9fc8c3fa92bc37f1fb406094180582d6620 --- /dev/null +++ b/patches/redirect-global-905914-130.patch @@ -0,0 +1,285 @@ +diff --git a/redirect.admin.inc b/redirect.admin.inc +index 176f105..a6610d5 100644 +--- a/redirect.admin.inc ++++ b/redirect.admin.inc +@@ -521,6 +521,7 @@ function redirect_edit_form_validate($form, &$form_state) { + + if (empty($form_state['values']['override'])) { + if ($existing = redirect_load_by_source($redirect->source, $redirect->language)) { ++ $existing = $existing[1]; + if ($redirect->rid != $existing->rid && $redirect->language == $existing->language) { + // The "from" path should not conflict with another redirect + $form_state['storage']['override_messages']['redirect-conflict'] = t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $redirect->source, '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid))); +@@ -636,32 +637,46 @@ function redirect_settings_form($form, &$form_state) { + $form['globals'] = array( + '#type' => 'fieldset', + '#title' => t('Always enabled redirections'), +- '#description' => t('(formerly Global Redirect features)'), +- '#access' => FALSE, + ); + $form['globals']['redirect_global_home'] = array( + '#type' => 'checkbox', +- '#title' => t('Redirect from paths like index.php and /node to the root directory.'), ++ '#title' => t('Redirect alternative front page URLs to the root directory.'), + '#default_value' => variable_get('redirect_global_home', 1), +- '#access' => FALSE, ++ '#description' => t('Includes all aliases for the front page, %root, as well as %node if a default front page is not set.', array('%root' => '/index.php', '%node' => '/node')), ++ ); ++ $form['globals']['redirect_global_index'] = array( ++ '#type' => 'checkbox', ++ '#title' => t('Remove index.php from all non-front page paths.'), ++ '#default_value' => variable_get('redirect_global_index', 0), ++ '#description' => t('Will remove index.php from paths such as %prepend and %append.', array('%prepend' => '/index.php?q=node/1', '%append' => '/page-alias/index.php')), + ); + $form['globals']['redirect_global_clean'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect from non-clean URLs to clean URLs.'), + '#default_value' => variable_get('redirect_global_clean', 1), + '#disabled' => !variable_get('clean_url', 0), +- '#access' => FALSE, + ); + $form['globals']['redirect_global_canonical'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect from non-canonical URLs to the canonical URLs.'), + '#default_value' => variable_get('redirect_global_canonical', 1), + ); ++ $form['globals']['redirect_global_canonical_front'] = array( ++ '#type' => 'checkbox', ++ '#title' => t('Redirect the front page to its canonical URL.'), ++ '#default_value' => variable_get('redirect_global_canonical_front', 0), ++ '#description' => t('Add a path to a request for the site root. For example, a request for %rootpath will redirect to %canonicalpath if the default front page is set to %defaultfrontpage.', array('%rootpath' => 'http://example.com/', '%canonicalpath' => 'http://example.com/node/1', '%defaultfrontpage' => 'node/1')), ++ ); + $form['globals']['redirect_global_deslash'] = array( + '#type' => 'checkbox', + '#title' => t('Remove trailing slashes from paths.'), +- '#default_value' => variable_get('redirect_global_deslash', 0), +- '#access' => FALSE, ++ '#default_value' => variable_get('redirect_global_deslash', 1), ++ ); ++ $form['globals']['redirect_global_add_slash'] = array( ++ '#type' => 'checkbox', ++ '#title' => t('Add trailing slashes to paths.'), ++ '#description' => t('For use with the Trailing Slash module instead of editing the .htaccess file.'), ++ '#default_value' => variable_get('redirect_global_add_slash', 0), + ); + $form['globals']['redirect_global_admin_paths'] = array( + '#type' => 'checkbox', +@@ -669,7 +684,11 @@ function redirect_settings_form($form, &$form_state) { + '#default_value' => variable_get('redirect_global_admin_paths', 0), + ); + +- $form['#submit'][] = 'redirect_settings_form_submit'; ++ $form['submit'] = array( ++ '#validate' => array('redirect_settings_form_validate'), ++ '#submit' => array('redirect_settings_form_submit'), ++ ); ++ + return system_settings_form($form); + } + +@@ -682,6 +701,17 @@ function redirect_settings_form_submit($form, &$form_state) { + redirect_page_cache_clear(); + } + ++/** ++ * Form validator; makes sure conflicting settings aren't chosen. ++ * ++ * @see redirect_settings_form() ++ */ ++function redirect_settings_form_validate($form, &$form_state) { ++ if ($form_state['values']['redirect_global_deslash'] && $form_state['values']['redirect_global_add_slash']) { ++ form_set_error('info', t('You can not have both %deslash and %add_slash enabled.', array('%deslash' => rtrim($form['globals']['redirect_global_deslash']['#title'], '.'), '%add_slash' => rtrim($form['globals']['redirect_global_add_slash']['#title'], '.')))); ++ } ++} ++ + function redirect_404_list($form = NULL) { + $destination = drupal_get_destination(); + +diff --git a/redirect.module b/redirect.module +index 8c47b75..65d5599 100644 +--- a/redirect.module ++++ b/redirect.module +@@ -244,21 +244,46 @@ function redirect_url_inbound_alter(&$path, $original_path, $path_language) { + } + } + +- // Redirect to canonical URLs. +- if ($path && variable_get('redirect_canonical', 1)) { +- $alias = drupal_get_path_alias($path, $path_language); +- if ($alias != $path && $alias != $original_path) { +- //return redirect_redirect(array('redirect' => $alias, 'type' => 'global')); +- } ++ // Check for empty path. ++ if (empty($path)) { ++ return; ++ } ++ // Do not redirect if $original_path does not match the requested url. ++ if ($original_path != $_GET['q']) { ++ return; ++ } ++ // Do not redirect if disallowed. ++ if (!redirect_can_redirect()) { ++ return; ++ } ++ ++ // Determine if front page. drupal_is_front_page() is not accurate here ++ // because drupal_path_initialize() has not executed yet. ++ $is_front_page = ($path == variable_get('site_frontpage', 'node') ? TRUE : FALSE); + +- // Redirect from default entity paths to the proper entity path. +- //if ($path_entity = redirect_load_entity_from_path($path)) { +- // if ($uri = entity_uri($path_entity['entity_type'], $path_entity['entity'])) { +- // if ($path != $uri['path']) { +- // return redirect_redirect(array('redirect' => $uri['path'], 'redirect_options' => $uri['options'], 'type' => 'global')); +- // } +- // } +- //} ++ // Redirect the front page to the root level. ++ if ($is_front_page ++ && variable_get('redirect_global_home', 0) ++ && !variable_get('redirect_global_canonical_front', 0) ++ && base_path() != request_uri()) { ++ return redirect_redirect((object)array('redirect' => '', 'type' => 'global')); ++ } ++ ++ // Redirect to the canonical URL. ++ $alias = drupal_get_path_alias($path, $path_language); ++ if ((!$is_front_page && variable_get('redirect_global_canonical', 1) ++ || $is_front_page && variable_get('redirect_global_canonical_front', 0)) ++ && $alias != $path ++ && $alias != $original_path) { ++ return redirect_redirect((object)array('redirect' => $alias, 'type' => 'global')); ++ } ++ ++ // Redirect from default entity paths to the proper entity path. ++ if ($path_entity = redirect_load_entity_from_path($path)) { ++ $uri = entity_uri($path_entity['entity_type'], $path_entity['entity']); ++ if ($path != $uri['path']) { ++ return redirect_redirect((object)array('redirect' => $uri['path'], 'redirect_options' => $uri['options'], 'type' => 'global')); ++ } + } + } + +@@ -323,29 +348,55 @@ function redirect_init() { + + // Fetch the current redirect. + if ($redirect = redirect_get_current_redirect()) { +- redirect_redirect($redirect); ++ redirect_redirect((object) reset($redirect)); + } + +- $redirect_global = FALSE; +- $request_uri = $original_uri = ltrim(request_uri(), '/'); ++ // Get the request URI without the $base_path prefix. ++ if (isset($_REQUEST['q'])) { ++ $path = $_REQUEST['q']; ++ } ++ else { ++ // This is a request using a clean URL. Extract the path from request_uri(). ++ $request_path = strtok(request_uri(), '?'); ++ $base_path_len = drupal_strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); ++ // Unescape and strip $base_path prefix, leaving q without a leading slash. ++ $path = drupal_substr(urldecode($request_path), $base_path_len + 1); ++ } ++ $request_uri = $original_uri = ltrim($path, '/'); + + // Redirect from non-clean URLs to clean URLs. +- if (variable_get('redirect_global_clean', 1) && variable_get('clean_url', 0) && strpos($request_uri, '?q=') !== FALSE) { +- //$redirect_global = TRUE; +- //$request_uri = str_replace('?q=', '', $request_uri); ++ if (variable_get('redirect_global_clean', 1) ++ && variable_get('clean_url', 0) ++ && strpos(request_uri(), '?q=') !== FALSE) { ++ redirect_redirect((object)array('redirect' => $request_uri, 'type' => 'global')); + } + +- if (strpos($request_uri, 'index.php') !== FALSE) { +- //$redirect_global = TRUE; +- //$request_uri = str_replace('index.php', '', $request_uri); ++ // Strip index.php ++ if (strpos(request_uri(), 'index.php') !== FALSE) { ++ if (!drupal_is_front_page() && variable_get('redirect_global_index', 0)) { ++ $request_uri = str_replace('index.php', '', $request_uri); ++ redirect_redirect((object)array('redirect' => $request_uri, 'type' => 'global')); ++ } ++ elseif (drupal_is_front_page() && variable_get('redirect_global_home', 0)) { ++ redirect_redirect((object)array('redirect' => '', 'type' => 'global')); ++ } + } + +- //$request_uri = ltrim($request_uri, '/'); +- //$parsed = parse_url($request_uri); +- +- if ($redirect_global && $request_uri != $original_uri) { +- redirect_redirect(array(/*'redirect' => $request_uri,*/ 'type' => 'global')); ++ // Deslash (Remove trailing slashes from paths). ++ $langcode = isset($options['language']->language) ? $options['language']->language : ''; ++ $alias = drupal_get_path_alias(current_path(), $langcode); ++ if (variable_get('redirect_global_deslash', 0) ++ && substr($request_uri, -1) == '/' ++ && $request_uri !== $alias) { ++ redirect_redirect((object)array('redirect' => rtrim($request_uri, '/'), 'type' => 'global')); ++ } ++ // Add slash (Add trailing slashes to paths). ++ elseif (variable_get('redirect_global_add_slash', 0) ++ && substr($request_uri, -1) !== '/' ++ && !drupal_is_front_page()) { ++ redirect_redirect((object)array('redirect' => $request_uri . '/', 'type' => 'global')); + } ++ + } + + /** +@@ -596,7 +647,7 @@ function redirect_load_by_source($source, $language = LANGUAGE_NONE, array $quer + $context = array('language' => $language, 'query' => $query); + drupal_alter('redirect_load_by_source', $redirects, $source, $context); + +- return !empty($redirects) ? reset($redirects) : FALSE; ++ return !empty($redirects) ? $redirects : FALSE; + } + } + +@@ -711,7 +762,7 @@ function redirect_validate($redirect, $form, &$form_state) { + } + } + +-function redirect_object_prepare($redirect, $defaults = array()) { ++function redirect_object_prepare(stdClass $redirect, $defaults = array()) { + $defaults += array( + 'rid' => NULL, + 'type' => 'redirect', +@@ -979,7 +1030,7 @@ function redirect_purge_inactive_redirects(array $types = array('redirect'), $in + * + * @ingroup redirect_api + */ +-function redirect_redirect($redirect = NULL) { ++function redirect_redirect(stdClass $redirect = NULL) { + // First check if we're in an infinite loop. + $session_id = session_id(); + if (flood_is_allowed('redirection', 5, 15, $session_id ? $session_id : NULL)) { +@@ -1028,6 +1079,12 @@ function redirect_redirect($redirect = NULL) { + */ + function redirect_goto($redirect) { + $redirect->redirect_options['absolute'] = TRUE; ++ // Prevent a path like 'index.php?q=node/1' from redirecting to '?q=path-alias' ++ // if canonical redirection is disabled. This will make url() treat 'node/1' ++ // as if it is already an alias and prevent a drupal_get_path_alias() lookup. ++ if (!variable_get('redirect_global_canonical', 0)) { ++ $redirect->redirect_options['alias'] = TRUE; ++ } + $url = url($redirect->redirect, $redirect->redirect_options); + drupal_add_http_header('Location', $url); + drupal_add_http_header('Status', redirect_status_code_options($redirect->status_code)); +@@ -1310,8 +1367,12 @@ function redirect_variables() { + 'redirect_page_cache' => 0, + 'redirect_purge_inactive' => 0, + 'redirect_global_home' => 1, ++ 'redirect_global_index' => 0, + 'redirect_global_clean' => 1, + 'redirect_global_canonical' => 1, ++ 'redirect_global_canonical_front' => 0, ++ 'redirect_global_deslash' => 1, ++ 'redirect_global_add_slash' => 0, + 'redirect_global_admin_paths' => 0, + ); + } diff --git a/patches/text-plain-1152216-24.patch b/patches/text-plain-1152216-24.patch new file mode 100644 index 0000000000000000000000000000000000000000..d48b6bdaf42e54fed1f352a10fb7b2530c77dcfc --- /dev/null +++ b/patches/text-plain-1152216-24.patch @@ -0,0 +1,29 @@ +diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module +index d73814f..9ba0051 100644 +--- a/modules/field/modules/text/text.module ++++ b/modules/field/modules/text/text.module +@@ -284,7 +284,9 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la + + case 'text_plain': + foreach ($items as $delta => $item) { +- $element[$delta] = array('#markup' => strip_tags($item['value'])); ++ // The text value has no text format assigned to it, so the user input ++ // should equal the output, including newlines. ++ $element[$delta] = array('#markup' => nl2br(check_plain($item['value']))); + } + break; + } +@@ -316,7 +318,12 @@ function _text_sanitize($instance, $langcode, $item, $column) { + if (isset($item["safe_$column"])) { + return $item["safe_$column"]; + } +- return $instance['settings']['text_processing'] ? check_markup($item[$column], $item['format'], $langcode) : check_plain($item[$column]); ++ if ($instance['settings']['text_processing']) { ++ return check_markup($item[$column], $item['format'], $langcode); ++ } ++ // Escape all HTML and retain newlines. ++ // @see text_field_formatter_view() ++ return nl2br(check_plain($item[$column])); + } + + /** diff --git a/patches/upload_replace_error-1115484-13.patch b/patches/upload_replace_error-1115484-13.patch new file mode 100644 index 0000000000000000000000000000000000000000..32a0ac764ebb384f13e75f40f339deb61114bb68 --- /dev/null +++ b/patches/upload_replace_error-1115484-13.patch @@ -0,0 +1,109 @@ +diff --git a/upload_replace.module b/upload_replace.module +index 0489f2d..7d1d644 100644 +--- a/upload_replace.module ++++ b/upload_replace.module +@@ -15,60 +15,53 @@ + /** + * Implementation of hook_file_update() + */ +-function upload_replace_file_update(&$new_file) { ++function upload_replace_file_update($new_file) { + if (!$new_file->fid) { + //Nothing to do if no fileid + return; + } +- + $desired_destination = preg_replace('/_[0-9]+\.(.*)$/', '.$1', $new_file->uri); + $db_path = db_select('file_managed', 'f') +- ->fields('f', array('uri', )) ++ ->fields('f', array('uri')) + ->condition('fid', $new_file->fid) + ->execute() +- ->fetchField(); +- if ($db_path != $new_file->uri) { ++ ->fetchAssoc(); ++ if ($db_path['uri'] != $new_file->uri) { + //this happens when a reversion is being reverted +- $next_good_filepath = file_destination($desired_destination, FILE_EXISTS_RENAME); ++ $next_good_uri = file_destination($desired_destination, FILE_EXISTS_RENAME); + db_update('file_managed') +- ->fields(array('uri' => $next_good_filepath)) ++ ->fields(array('uri' => $next_good_uri)) + ->condition('fid', $new_file->fid) + ->execute(); + $new_file->uri = $desired_destination; + } + else { + //If the filename has be modified by adding a _0 value, or +- //on certain situations the filepath will not match the filepath in the db, such as ++ //on certain situations the uri will not match the uri in the db, such as + //when reverting a revision. When reverting a revision change the filename as well +- if (!strpos($new_file->uri, $new_file->uri)) { +- //the filename is not in the filepath, so drupal must have added a "_0" before the extension ++ if (!strpos($new_file->uri, $new_file->filename)) { ++ //the filename is not in the uri, so drupal must have added a "_0" before the extension + //find the file that is blocking this file from keeping the correct path + $result = db_select('file_managed', 'f') + ->fields('f') + ->condition('uri', $desired_destination) + ->execute(); + //@todo only one result is handled, should allow for multiple results +- $is_blocked = false; +- + foreach ($result as $file) { + $is_blocked = TRUE; + $blocking_file = $file; +- $tmp_destination = file_directory_temp() ."/$blocking_file->filename"; ++ $tmp_destination = file_directory_temp()."/test_-".$blocking_file->fid."_-".$blocking_file->filename.""; + } + +- $old_destination = $db_path; +- +- if ($old_destination == $desired_destination){ +- return; +- } ++ $old_destination = $db_path['uri']; + //Swap the files + if ($is_blocked) { +- //move the blocking file to a temporary location ++ //move the blocking file to a temparary location + if (!file_unmanaged_move($desired_destination, $tmp_destination)) { + drupal_set_message(t('The file %old could not be moved to %new', array('%old' => $desired_destination, '%new' => $tmp_destination)), 'error'); + return; + } +- //DRUPAL 7 no longer changes the source filepath during move ++ //DRUPAL 7 no longer changes the source uri during move + //move blocking file was successful, update the DB + db_update('file_managed') + ->fields(array('uri' => $tmp_destination)) +@@ -107,10 +100,10 @@ function upload_replace_file_update(&$new_file) { + //Have to clear the cache because the revision data is cached somewhere + /* + * Find the nids where this file is used +- $query = "SELECT DISTINCT nid FROM {files} WHERE fid=%d"; ++ $query = "SELECT DISTINCT id FROM {file_usage} WHERE fid=%d"; + $result = db_query($query, $new_file->fid); + while($data = db_fetch_object($result)) { +- cache_clear_all("content:$data->nid"); ++ cache_clear_all("node:$data->id"); + } + */ + //This is inefficent, but how can we determine what nodes use this file? +@@ -118,11 +111,12 @@ function upload_replace_file_update(&$new_file) { + } + + /** +- * HOOK_file_delete, update the filepath in the file object before deleting as we may have altered it above ++ * HOOK_file_delete, update the uri in the file object before deleting as we may have altered it above + * @param object $new_file + */ + /* +-function upload_replace_file_delete(&$file) { +- $file->filepath = db_result(db_query("SELECT filepath FROM {files} WHERE fid = %d", $file->fid)); ++function upload_replace_file_delete($file) { ++ $file->uri = db_result(db_query("SELECT uri FROM {file_managed} WHERE fid = %d", $file->fid)); + } +-*/ +\ No newline at end of file ++*/ ++ diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info index 657c2c124a59874f1d980e2f476af99f651f36af..270e2751248e657f23a6bfc271ea7d12d1fb4e26 100644 --- a/profiles/minimal/minimal.info +++ b/profiles/minimal/minimal.info @@ -5,8 +5,8 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info index 2dc7d3ef367bbfe400912f68ddd67718b8912537..750dc77a24eaf7f13bc514bf193798f2198eb936 100644 --- a/profiles/standard/standard.info +++ b/profiles/standard/standard.info @@ -24,8 +24,8 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 55bd3ffda9675ced1787cccd38a76342a672c544..653964c16a1cec68dd315e53a011ab0f87e1f609 100644 --- a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 81fb29a187b65c29513c8cf73434155f6319a6d3..5faae3ba33d44dd144438212dbd3df8f0b172a51 100644 --- a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,8 +8,8 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/profiles/testing/testing.info b/profiles/testing/testing.info index 76b7e8a1a5c8ac0aadc98a64c1a1f89e48d824d1..83321f0f082e829a61b2212c06c4203a4cfae1f9 100644 --- a/profiles/testing/testing.info +++ b/profiles/testing/testing.info @@ -4,8 +4,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/sites/all/modules/robotstxt/robots.txt b/robots.txt old mode 100755 new mode 100644 similarity index 87% rename from sites/all/modules/robotstxt/robots.txt rename to robots.txt index 91968f6bacb1da8bd1e7b887da6f28f1a4bde860..7b87acf25a1d3b790bf319f098ff59f6234e0189 --- a/sites/all/modules/robotstxt/robots.txt +++ b/robots.txt @@ -1,4 +1,3 @@ -# $Id: robots.txt,v 1.8.2.2 2011/01/05 23:24:10 hass Exp $ # # robots.txt # @@ -31,6 +30,7 @@ Disallow: /CHANGELOG.txt Disallow: /cron.php Disallow: /INSTALL.mysql.txt Disallow: /INSTALL.pgsql.txt +Disallow: /INSTALL.sqlite.txt Disallow: /install.php Disallow: /INSTALL.txt Disallow: /LICENSE.txt @@ -41,6 +41,7 @@ Disallow: /xmlrpc.php # Paths (clean URLs) Disallow: /admin/ Disallow: /comment/reply/ +Disallow: /filter/tips/ Disallow: /node/add/ Disallow: /search/ Disallow: /user/register/ @@ -50,9 +51,15 @@ 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/ +# UNL +Disallow: /book/export/html/ +Disallow: /*/book/export/html/ +Disallow: /?q=book/export/html/ +Disallow: /?q=*/book/export/html/ diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 334501527f445c7df63aec129096808f7caa8993..189d7f2e809b83bde896cb388837c459b0a91056 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -362,6 +362,8 @@ 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(); @@ -395,7 +397,7 @@ function simpletest_script_command($test_id, $test_class) { if ($args['color']) { $command .= ' --color'; } - $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class"; + $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class); return $command; } diff --git a/sites/all/modules/context/API.txt b/sites/all/modules/context/API.txt index 8b4785284536df3877a08f1858e4b62d2e2b1985..9c7142c93e660b3c310c88c536a22530292c0431 100644 --- a/sites/all/modules/context/API.txt +++ b/sites/all/modules/context/API.txt @@ -61,9 +61,34 @@ to add a new condition or reaction for your module, follow these steps: 3. Write your condition or reaction plugin class. It's best to look at one of the included plugins as a starting point. -4. Add in a Drupal integration point for your plugin. A node page condition - plugin, for example, may be invoked from `hook_nodeapi()`. +4. Create a Drupal integration point for your plugin. A node page condition + plugin, for example, may be invoked from `hook_node_view()`. Typically a + Drupal integration point for a condition uses a Drupal hook to trigger + tests that determine whether context conditions are met for one or more + plug-ins. For example, this is how the context module itself uses + hook_init(): + + function context_init() { + if ($plugin = context_get_plugin('condition', 'path')) { + $plugin->execute(); + } + if ($plugin = context_get_plugin('condition', 'language')) { + global $language; + $plugin->execute($language->language); + } + if ($plugin = context_get_plugin('condition', 'user')) { + global $user; + $plugin->execute($user); + } + } + This function first instantiates the Context module's path condition + plugin (filename context_condition_path.inc in the plugins directory), + and then uses that plugin's execute() method. The execute() method + determine whether any path conditions are met and, if so, it activates + the contexts that match those conditions. After setting contexts based + path conditions, the context_init() function then does the same thing + with the Context module's language and user condition plugins. Replacing or extending existing plugins --------------------------------------- diff --git a/sites/all/modules/context/LICENSE.txt b/sites/all/modules/context/LICENSE.txt index 2c095c8d3f42488e8168f9710a4ffbfc4125a159..d159169d1050894d3ea3b98e1c965c4058208fe1 100644 --- a/sites/all/modules/context/LICENSE.txt +++ b/sites/all/modules/context/LICENSE.txt @@ -1,274 +1,339 @@ -GNU GENERAL PUBLIC LICENSE - - Version 2, June 1991 - -Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, -Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute -verbatim copies of this license document, but changing it is not allowed. - - Preamble - -The licenses for most software are designed to take away your freedom to -share and change it. By contrast, the GNU General Public License is -intended to guarantee your freedom to share and change free software--to -make sure the software is free for all its users. This General Public License -applies to most of the Free Software Foundation's software and to any other -program whose authors commit to using it. (Some other Free Software -Foundation software is covered by the GNU Library General Public License -instead.) You can apply it to your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our -General Public Licenses are designed to make sure that you have the -freedom to distribute copies of free software (and charge for this service if -you wish), that you receive source code or can get it if you want it, that you -can change the software or use pieces of it in new free programs; and that -you know you can do these things. - -To protect your rights, we need to make restrictions that forbid anyone to -deny you these rights or to ask you to surrender the rights. These restrictions -translate to certain responsibilities for you if you distribute copies of the -software, or if you modify it. - -For example, if you distribute copies of such a program, whether gratis or for -a fee, you must give the recipients all the rights that you have. You must make -sure that they, too, receive or can get the source code. And you must show -them these terms so they know their rights. - -We protect your rights with two steps: (1) copyright the software, and (2) -offer you this license which gives you legal permission to copy, distribute -and/or modify the software. - -Also, for each author's protection and ours, we want to make certain that -everyone understands that there is no warranty for this free software. If the -software is modified by someone else and passed on, we want its recipients -to know that what they have is not the original, so that any problems -introduced by others will not reflect on the original authors' reputations. - -Finally, any free program is threatened constantly by software patents. We -wish to avoid the danger that redistributors of a free program will individually -obtain patent licenses, in effect making the program proprietary. To prevent -this, we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. - -The precise terms and conditions for copying, distribution and modification -follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND - MODIFICATION - -0. This License applies to any program or other work which contains a notice -placed by the copyright holder saying it may be distributed under the terms -of this General Public License. The "Program", below, refers to any such -program or work, and a "work based on the Program" means either the -Program or any derivative work under copyright law: that is to say, a work -containing the Program or a portion of it, either verbatim or with -modifications and/or translated into another language. (Hereinafter, translation -is included without limitation in the term "modification".) Each licensee is -addressed as "you". - -Activities other than copying, distribution and modification are not covered -by this License; they are outside its scope. The act of running the Program is -not restricted, and the output from the Program is covered only if its contents -constitute a work based on the Program (independent of having been made -by running the Program). Whether that is true depends on what the Program -does. - -1. You may copy and distribute verbatim copies of the Program's source -code as you receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice and -disclaimer of warranty; keep intact all the notices that refer to this License -and to the absence of any warranty; and give any other recipients of the -Program a copy of this License along with the Program. - -You may charge a fee for the physical act of transferring a copy, and you -may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Program or any portion of it, -thus forming a work based on the Program, and copy and distribute such -modifications or work under the terms of Section 1 above, provided that you -also meet all of these conditions: - -a) You must cause the modified files to carry prominent notices stating that -you changed the files and the date of any change. - -b) You must cause any work that you distribute or publish, that in whole or in -part contains or is derived from the Program or any part thereof, to be -licensed as a whole at no charge to all third parties under the terms of this -License. - -c) If the modified program normally reads commands interactively when run, -you must cause it, when started running for such interactive use in the most -ordinary way, to print or display an announcement including an appropriate -copyright notice and a notice that there is no warranty (or else, saying that -you provide a warranty) and that users may redistribute the program under -these conditions, and telling the user how to view a copy of this License. -(Exception: if the Program itself is interactive but does not normally print such -an announcement, your work based on the Program is not required to print -an announcement.) - -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Program, and can be -reasonably considered independent and separate works in themselves, then -this License, and its terms, do not apply to those sections when you distribute -them as separate works. But when you distribute the same sections as part -of a whole which is a work based on the Program, the distribution of the -whole must be on the terms of this License, whose permissions for other -licensees extend to the entire whole, and thus to each and every part -regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to -work written entirely by you; rather, the intent is to exercise the right to -control the distribution of derivative or collective works based on the -Program. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of a -storage or distribution medium does not bring the other work under the scope -of this License. - -3. You may copy and distribute the Program (or a work based on it, under -Section 2) in object code or executable form under the terms of Sections 1 -and 2 above provided that you also do one of the following: - -a) Accompany it with the complete corresponding machine-readable source -code, which must be distributed under the terms of Sections 1 and 2 above -on a medium customarily used for software interchange; or, - -b) Accompany it with a written offer, valid for at least three years, to give -any third party, for a charge no more than your cost of physically performing -source distribution, a complete machine-readable copy of the corresponding -source code, to be distributed under the terms of Sections 1 and 2 above on -a medium customarily used for software interchange; or, - -c) Accompany it with the information you received as to the offer to distribute -corresponding source code. (This alternative is allowed only for -noncommercial distribution and only if you received the program in object -code or executable form with such an offer, in accord with Subsection b -above.) +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source code -means all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation and -installation of the executable. However, as a special exception, the source -code distributed need not include anything that is normally distributed (in -either source or binary form) with the major components (compiler, kernel, -and so on) of the operating system on which the executable runs, unless that -component itself accompanies the executable. - -If distribution of executable or object code is made by offering access to -copy from a designated place, then offering equivalent access to copy the -source code from the same place counts as distribution of the source code, -even though third parties are not compelled to copy the source along with the -object code. - -4. You may not copy, modify, sublicense, or distribute the Program except as -expressly provided under this License. Any attempt otherwise to copy, -modify, sublicense or distribute the Program is void, and will automatically -terminate your rights under this License. However, parties who have received -copies, or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - -5. You are not required to accept this License, since you have not signed it. -However, nothing else grants you permission to modify or distribute the -Program or its derivative works. These actions are prohibited by law if you -do not accept this License. Therefore, by modifying or distributing the -Program (or any work based on the Program), you indicate your acceptance -of this License to do so, and all its terms and conditions for copying, -distributing or modifying the Program or works based on it. - -6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the original -licensor to copy, distribute or modify the Program subject to these terms and -conditions. You may not impose any further restrictions on the recipients' -exercise of the rights granted herein. You are not responsible for enforcing -compliance by third parties to this License. - -7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), conditions -are imposed on you (whether by court order, agreement or otherwise) that -contradict the conditions of this License, they do not excuse you from the -conditions of this License. If you cannot distribute so as to satisfy -simultaneously your obligations under this License and any other pertinent -obligations, then as a consequence you may not distribute the Program at all. -For example, if a patent license would not permit royalty-free redistribution -of the Program by all those who receive copies directly or indirectly through -you, then the only way you could satisfy both it and this License would be to +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply and -the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or -other property right claims or to contest validity of any such claims; this -section has the sole purpose of protecting the integrity of the free software -distribution system, which is implemented by public license practices. Many -people have made generous contributions to the wide range of software -distributed through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing to -distribute software through any other system and a licensee cannot impose -that choice. - -This section is intended to make thoroughly clear what is believed to be a -consequence of the rest of this License. - -8. If the distribution and/or use of the Program is restricted in certain -countries either by patents or by copyrighted interfaces, the original copyright -holder who places the Program under this License may add an explicit -geographical distribution limitation excluding those countries, so that -distribution is permitted only in or among countries not thus excluded. In such -case, this License incorporates the limitation as if written in the body of this -License. - -9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will be -similar in spirit to the present version, but may differ in detail to address new -problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies -a version number of this License which applies to it and "any later version", -you have the option of following the terms and conditions either of that -version or of any later version published by the Free Software Foundation. If -the Program does not specify a version number of this License, you may -choose any version ever published by the Free Software Foundation. - -10. If you wish to incorporate parts of the Program into other free programs -whose distribution conditions are different, write to the author to ask for -permission. For software which is copyrighted by the Free Software -Foundation, write to the Free Software Foundation; we sometimes make -exceptions for this. Our decision will be guided by the two goals of -preserving the free status of all derivatives of our free software and of -promoting the sharing and reuse of software generally. - - NO WARRANTY - -11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT -PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE -STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL -NECESSARY SERVICING, REPAIR OR CORRECTION. - -12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR -AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR -ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE -LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, -SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE -PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA -OR DATA BEING RENDERED INACCURATE OR LOSSES -SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE -PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN -IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/context/README.txt b/sites/all/modules/context/README.txt index 435e14360d2561d241e82851b3cf95708ec25d27..460c947d1c2e7e391dba953705845a8d274b9aa2 100644 --- a/sites/all/modules/context/README.txt +++ b/sites/all/modules/context/README.txt @@ -62,19 +62,6 @@ For a more in-depth overview of the UI components, see the Context UI `README.txt`. -Upgrading from Context 2.x for Drupal 6.x ------------------------------------------ -- Download latest Context 3.x and latest CTools release and place in modules - directory. Make sure to *remove* the existing Context 2.x directory before - unpacking Context 3.x. There are stale files in the 2.x branch that need to - be removed. -- Run `update.php` or `drush updatedb`. -- If your site contains contexts defined in code they will be overridden. - Re-export them to code again. If you are using any custom conditions or - reactions, you may need to upgrade or reconfigure them by hand. See `API.txt` - for instructions on adding and extending plugins in Context. - - Hooks ----- See `context.api.php` for the hooks made available by context and `API.txt` for diff --git a/sites/all/modules/context/context.api.php b/sites/all/modules/context/context.api.php index 03f0563763592f233fe9196dbd2a7ff4d41ea80a..5755edf3faf6e1218fd3539ab4f4d1893eff8406 100644 --- a/sites/all/modules/context/context.api.php +++ b/sites/all/modules/context/context.api.php @@ -53,6 +53,30 @@ function hook_context_registry() { ); } +/** + * Execute Context page conditions + * + * Allows modules to hook into Context's hook_page_build to execute their + * conditions at an appropriate time before the firing of reactions. + */ +function hook_context_page_condition() { + if ($plugin = context_get_plugin('condition', 'bar')) { + $plugin->execute(); + } +} + +/** + * Execute Context page reactions + * + * Allows modules to hook into Context's hook_page_build to execute their + * reactions at an appropriate time after the firing of conditions. + */ +function hook_context_page_reaction() { + if ($plugin = context_get_plugin('reaction', 'baz')) { + $plugin->execute(); + } +} + /** * Alter the registry. * diff --git a/sites/all/modules/context/context.core.inc b/sites/all/modules/context/context.core.inc index e6605a73552c9b824cbbdaae45db4d0c8a2d1e4d..2d44c75ac0dd0f756920330d34c4aeac43f07143 100644 --- a/sites/all/modules/context/context.core.inc +++ b/sites/all/modules/context/context.core.inc @@ -6,8 +6,8 @@ function context_help($path, $arg) { switch ($path) { case 'admin/help#context': - $output = file_get_contents(drupal_get_path('module', 'context') .'/README.txt'); - return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>'; + $output = file_get_contents(drupal_get_path('module', 'context') . '/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>' . check_plain($output) . '</pre>'; } } @@ -72,7 +72,13 @@ function context_theme_registry_alter(&$theme_registry) { if ($position !== FALSE) { unset($theme_registry['page']['preprocess functions'][$position]); } - $position = array_search('template_preprocess_page', $theme_registry['page']['preprocess functions']); + // Prevent conflict with i18n_menu. + if (module_exists('i18n_menu')) { + $position = array_search('i18n_menu_preprocess_page', $theme_registry['page']['preprocess functions']); + } + else { + $position = array_search('template_preprocess_page', $theme_registry['page']['preprocess functions']); + } $position = $position ? $position + 1 : 2; array_splice($theme_registry['page']['preprocess functions'], $position, 0, 'context_preprocess_page'); } @@ -85,7 +91,7 @@ function context_ctools_render_alter($info, $page, $data) { extract($data); if ($page && in_array($task['name'], array('node_view', 'node_edit'), TRUE)) { foreach ($contexts as $ctools_context) { - if ($ctools_context->type === 'node' && !empty($ctools_context->data)) { + if (in_array('node', $ctools_context->type) && !empty($ctools_context->data)) { context_node_condition($ctools_context->data, $task['name'] === 'node_view' ? 'view' : 'form'); break; } @@ -121,15 +127,32 @@ function context_node_view($node, $view_mode) { * Implementation of hook_form_alter(). */ function context_form_alter(&$form, $form_state, $form_id) { + // If the form is an admin for, flag it so that we can force a rebuild if needed. + if (path_is_admin($_GET['q'])) { + $form['#submit'][] = 'context_admin_form_submit'; + } + // Trigger the condition in an after_build function to avoid being skipped + // when there are validation errors. + $form['#after_build'][] = 'context_form_alter_node_after_build'; +} + +/** + * Form #after_build callback for context_form_alter(). + */ +function context_form_alter_node_after_build($form, &$form_state) { // Prevent this from firing on admin pages... damn form driven apis... if (!empty($form['#node_edit_form']) && arg(0) != 'admin') { context_node_condition($form['#node'], 'form'); } - // Clear out block info cache when an admin area form is submitted. - if (arg(0) === 'admin' && !empty($form_state['input']) && isset($form_state['method']) && $form_state['method'] === 'post') { - if ($plugin = context_get_plugin('reaction', 'block')) { - $plugin->rebuild_needed(TRUE); - } + return $form; +} + +/** + * Clear out block info cache when an admin area form is submitted. + */ +function context_admin_form_submit(&$form, $form_state) { + if ($plugin = context_get_plugin('reaction', 'block')) { + $plugin->rebuild_needed(TRUE); } } @@ -177,7 +200,7 @@ function context_form_user_profile_form_alter(&$form, $form_state) { /** * Implementation of hook_form_alter() for user_register_form. */ -function context_form_user_register_form(&$form, $form_state) { +function context_form_user_register_form_alter(&$form, $form_state) { if ($plugin = context_get_plugin('condition', 'user_page')) { $plugin->execute($form['#user'], 'register'); } @@ -272,7 +295,7 @@ function context_links($reset = FALSE) { if (!empty($active_types)) { // Iterate over active contexts foreach ($active_types as $type) { - $add_url = 'node/add/'. str_replace('_', '-', $type); + $add_url = 'node/add/' . str_replace('_', '-', $type); $item = menu_get_item($add_url); if ($item && $item['access'] && strpos($_GET['q'], $add_url) !== 0) { $links[$type] = array('title' => t('Add @type', array('@type' => node_type_get_name($type))), 'href' => $add_url); diff --git a/sites/all/modules/context/context.info b/sites/all/modules/context/context.info index 05240e65a8a04a0c6097b8fe4fe109483b212366..bc4faf8021ef94b1d2c43c24d33a6c74ddffc317 100644 --- a/sites/all/modules/context/context.info +++ b/sites/all/modules/context/context.info @@ -9,9 +9,9 @@ files[] = tests/context.test files[] = tests/context.conditions.test files[] = tests/context.reactions.test -; Information added by drupal.org packaging script on 2011-02-28 -version = "7.x-3.0-beta1" +; Information added by drupal.org packaging script on 2012-12-19 +version = "7.x-3.0-beta6" core = "7.x" project = "context" -datestamp = "1298925068" +datestamp = "1355879811" diff --git a/sites/all/modules/context/context.install b/sites/all/modules/context/context.install index d9ded9c38353ad594867e528ee3d46ac244e6495..721973319ea7cedaf787586b096bc1f72f4632d7 100644 --- a/sites/all/modules/context/context.install +++ b/sites/all/modules/context/context.install @@ -80,214 +80,13 @@ function context_schema() { return $schema; } -/** - * Update script for context that installs the context schema and migrates - * any existing context data from deprecated context_ui tables. - */ -function context_update_6001() { - $ret = array(); - - if (!db_table_exists('context')) { - drupal_install_schema('context'); - } - - if (db_table_exists('context_ui')) { - // Clear the schema cache and rebuild - drupal_get_schema(NULL, TRUE); - - // Migrate existing contexts to context table - $result = db_query("SELECT * FROM {context_ui}"); - while ($context = db_fetch_object($result)) { - // Load setters - $setter_result = db_query("SELECT * FROM {context_ui_setter} WHERE cid = %d", $context->cid); - while ($row = db_fetch_object($setter_result)) { - $context->{$row->type}[$row->id] = $row->id; - } - // Load getters - $getter_result = db_query("SELECT * FROM {context_ui_getter} WHERE cid = %d", $context->cid); - while ($row = db_fetch_object($getter_result)) { - $context->{$row->type} = unserialize($row->data); - } - // Load blocks - $block_result = db_query("SELECT module, delta, region, weight FROM {context_ui_block} WHERE cid = %d", $context->cid); - while ($block = db_fetch_object($block_result)) { - if (!isset($context->block)) { - $context->block = array(); - } - $block->bid = $block->module ."_". $block->delta; - $context->block[$block->bid] = $block; - } - // Clear out identifier - unset($context->cid); - context_save_context($context); - } - } - - module_enable(array('context_contrib')); - - return $ret; -} - -/** - * Update script for API change in path condition. - */ -function context_update_6002() { - define('CONTEXT_STORAGE_DEFAULT', 0); - define('CONTEXT_STORAGE_OVERRIDDEN', 1); - define('CONTEXT_STORAGE_NORMAL', 2); - - // Iterate through all DB-stored contexts and incorporate path - // wildcards into their path conditions. Any exported/default - // contexts will need to be updated by hand. - $contexts = context_enabled_contexts(); - foreach ($contexts as $context) { - if (($context->type == CONTEXT_STORAGE_NORMAL || $context->type == CONTEXT_STORAGE_OVERRIDDEN) && (!empty($context->path) && is_array($context->path))) { - $changed = FALSE; - foreach ($context->path as $k => $v) { - if ($v != '<front>' && strpos($v, '*') === FALSE) { - $changed = TRUE; - $context->path[$k] = "{$v}*"; - } - } - if ($changed) { - context_save_context($context); - } - } - } - return array(); -} - -/** - * Remove deprecated tables from context_ui. - */ -function context_update_6003() { - $ret = array(); - $tables = array('context_ui', 'context_ui_setter', 'context_ui_getter', 'context_ui_block'); - foreach ($tables as $table) { - if (db_table_exists($table)) { - db_drop_table($ret, $table); - } - } - return $ret; -} - -/** - * Update 6301: Update schema. - */ -function context_update_6301() { - // Install CTools. - drupal_install_modules(array('ctools')); - - $schema = array( - 'fields' => array( - 'name' => array( - 'description' => 'The primary identifier for a context.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'description' => array( - 'description' => 'Description for this context.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'tag' => array( - 'description' => 'Tag for this context.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'conditions' => array( - 'description' => 'Serialized storage of all context condition settings.', - 'type' => 'text', - 'serialize' => TRUE, - ), - 'reactions' => array( - 'description' => 'Serialized storage of all context reaction settings.', - 'type' => 'text', - 'serialize' => TRUE, - ), - ), - 'primary key' => array('name'), - ); - $ret = array(); - if (db_table_exists('context')) { - $result = db_query("SELECT * FROM {context}"); - - // Migrate old contexts into new table. - $contexts = array(); - while ($context = db_fetch_object($result)) { - $data = unserialize($context->data); - unset($context->data); - foreach ($data as $k => $v) { - $context->{$k} = $v; - } - $contexts["{$context->namespace}-{$context->attribute}-{$context->value}"] = $context; - } - - // Drop the existing context table and create one using the new schema. - db_drop_table($ret, 'context'); - db_create_table($ret, 'context', $schema); - - // Migrate objects. - context_migrate_api_3($ret, $contexts); - } - return $ret; -} - -/** - * Update 6302: Update old context exportables. This update script may be - * re-run at any time to update context 2 objects that have been exported. - */ -function context_update_6302() { - $contexts = array(); - // Invoke context 2 default hooks so that the contexts can be migrated. - foreach (module_invoke_all('context_default_contexts') as $context) { - $context = (object) $context; - if (!isset($context->api_version)) { - $contexts["{$context->namespace}-{$context->attribute}-{$context->value}"] = $context; - } - } - // Migrate objects. - $ret = array(); - context_migrate_api_3($ret, $contexts); - return $ret; -} - -/** - * Update 6303: Add field for context condition mode. - */ -function context_update_6303() { - $ret = array(); - $spec = array( - 'description' => 'Condition mode for this context.', - 'type' => 'int', - 'default' => 0, - ); - db_add_field($ret, 'context', 'condition_mode', $spec); - return $ret; -} - -/** - * Update 6304: Rename variable 'context_ui_show_empty_regions'. - */ -function context_update_6304() { - if (!db_result(db_query("SELECT name FROM {variable} WHERE name = 'context_reaction_block_all_regions'"))) { - db_query("UPDATE {variable} SET name = 'context_reaction_block_all_regions' WHERE name = 'context_ui_show_empty_regions'"); - return array(array('success' => TRUE, 'query' => 'Variable renamed successfully.')); - } - return array(); -} - /** * Update 7000: Handle adjustments to split of theme reaction to support D7 preprocess split between _page and _html */ function context_update_7000() { + drupal_load('module', 'ctools'); + drupal_load('module', 'context'); $updated = array(); $contexts = context_load(NULL, TRUE); foreach ($contexts as $c) { @@ -310,83 +109,3 @@ function context_update_7000() { } return $ret; } - -/** - * Helper function to update context 2 objects to context 3. - */ -function context_migrate_api_3(&$ret, $contexts) { - foreach ($contexts as $context) { - if (!db_result(db_query("SELECT name FROM {context} WHERE name = '%s'", "{$context->namespace}-{$context->attribute}-{$context->value}"))) { - $new = array( - 'name' => "{$context->namespace}-{$context->attribute}-{$context->value}", - 'description' => isset($context->description) ? $context->description : '', - 'tag' => '', - 'conditions' => array(), - 'reactions' => array(), - ); - // Migration condition/reaction settings. - // Some have been renamed. Map them. - $conditions = array( - 'node' => 'node', - 'user' => 'user', - 'book' => 'book', - 'sitewide' => 'sitewide', - 'path' => 'path', - 'menu_trail' => 'menu', - 'views' => 'views', - 'nodequeue' => 'nodequeue' - ); - foreach ($conditions as $old_key => $new_key) { - if (isset($context->{$old_key})) { - $values = $context->{$old_key}; - $new['conditions'][$new_key] = array( - 'values' => is_array($values) ? $values : array($values), - 'options' => array() - ); - } - } - $reactions = array( - 'menu' => 'menu', - 'theme_section' => 'theme', - 'css_injector' => 'css_injector', - 'block' => 'block', - ); - foreach ($reactions as $old_key => $new_key) { - if (isset($context->{$old_key})) { - // Special treatment for blocks. - if ($old_key === 'block') { - foreach ($context->block as $block) { - $block = (array)$block; - $new['reactions']['block']['blocks'][$block['module'] .'-'. $block['delta']] = $block; - } - } - else { - $new['reactions'][$new_key] = $context->{$old_key}; - } - } - } - $new['conditions'] = serialize($new['conditions']); - $new['reactions'] = serialize($new['reactions']); - - // Update_sql does not escape strings properly. - db_query("INSERT INTO {context} (name,description,tag,conditions,reactions) VALUES ('%s', '%s', '%s', '%s', '%s')", $new['name'], $new['description'], $new['tag'], $new['conditions'], $new['reactions']); - - // Notify the user of any keys that were not migrated. - $known_keys = array_merge(array_keys($conditions), array_keys($reactions), array('cid', 'system', 'namespace', 'attribute', 'value', 'description')); - $unmigrated = array_diff(array_keys((array) $context), $known_keys); - if (!empty($unmigrated)) { - $unmigrated = implode(', ', $unmigrated); - $ret[] = array( - 'success' => TRUE, - 'query' => "Updated context: {$new['name']}. The following properties could not be migrated: {$unmigrated}." - ); - } - else { - $ret[] = array( - 'success' => TRUE, - 'query' => "Updated context: {$new['name']}." - ); - } - } - } -} diff --git a/sites/all/modules/context/context.module b/sites/all/modules/context/context.module index ea1ef3097c5c5a9345b9a429802ff6866cfe085b..a3beee2e828f643065ddab538ac68906d82a21f1 100644 --- a/sites/all/modules/context/context.module +++ b/sites/all/modules/context/context.module @@ -37,7 +37,7 @@ function context_context($op = CONTEXT_GET, $namespace = NULL, $attribute = NULL return $context; } // return entire space if set - else if (isset($context[(string) $namespace])) { + elseif (isset($context[(string) $namespace])) { // return val of key from space if (is_array($context[(string) $namespace]) && isset($context[(string) $namespace][(string) $attribute])) { return $context[(string) $namespace][(string) $attribute]; @@ -178,6 +178,25 @@ function context_init() { } } +/** + * Implementation of hook_preprocess_menu_link(). + * + * This allows menus that are not primary/secondary menus to get + * the "active" class assigned to them. This assumes they are using + * theme('menu_link') for the menu rendering to html. + */ +function context_preprocess_menu_link(&$variables) { + if($contexts = context_active_contexts()){ + foreach($contexts as $context){ + if((isset($context->reactions['menu']))){ + if ($variables['element']['#href'] == $context->reactions['menu']) { + $variables['element']['#localized_options']['attributes']['class'][] = "active"; + } + } + } + } +} + /** * Load & crud functions ============================================== */ @@ -254,7 +273,7 @@ function context_save($context) { */ function context_delete($context) { if (isset($context->name) && ($context->export_type & EXPORT_IN_DATABASE)) { - db_query("DELETE FROM {context} WHERE name = '%s'", $context->name); + db_query("DELETE FROM {context} WHERE name = :name", array(':name' => $context->name)); context_invalidate_cache(); return TRUE; } @@ -390,6 +409,7 @@ function context_condition_map($reset = FALSE) { $condition_map = $cache; } else { + $condition_map = array(); foreach (array_keys(context_conditions()) as $condition) { if ($plugin = context_get_plugin('condition', $condition)) { foreach (context_enabled_contexts() as $context) { @@ -513,8 +533,31 @@ function _context_registry($key = NULL, $reset = FALSE) { * not dropped */ function context_block_view_alter(&$data, $block) { - $context_ui_editor_present = &drupal_static('context_ui_editor_present', FALSE); - if ($context_ui_editor_present && empty($data['content'])) { - $data['content']['#markup'] = "<div class='context-block-empty'>". t('This block appears empty when displayed on this page.') . "</div>"; + if (context_isset('context_ui', 'context_ui_editor_present') && empty($data['content'])) { + $data['content']['#markup'] = "<div class='context-block-empty-content'>" . t('This block appears empty when displayed on this page.') . "</div>"; + $data['context_block_hidden'] = TRUE; + } +} + +/** + * implement hook_page_alter() + * + * used for region context + */ +function context_page_alter(&$page) { + if ($plugin = context_get_plugin('reaction', 'region')) { + $plugin->execute($page); + } +} + +/** + * hook_block_view_alter - if the context editor block is on this page, + * ensure that all blocks have some content so that empty blocks are + * not dropped + */ +function context_preprocess_block(&$vars) { + if (isset($vars['block']->context_block_hidden)) { + $vars['classes_array'][] = 'context-block-hidden'; + $vars['classes_array'][] = 'context-block-empty'; } } diff --git a/sites/all/modules/context/context.plugins.inc b/sites/all/modules/context/context.plugins.inc index d5f18f737b58bba6154c51a075fa70f5dfdd2f0c..6f865598c4fc90a6a183cd2b64916c26ae9b4bc5 100644 --- a/sites/all/modules/context/context.plugins.inc +++ b/sites/all/modules/context/context.plugins.inc @@ -8,7 +8,7 @@ function _context_context_registry() { $registry['conditions'] = array( 'context' => array( 'title' => t('Context'), - 'description' => t('Set this context on the basis of other active contexts. Put each context on a separate line. You can use the <code>*</code> character as a wildcard and <code>~</code> to exclude one or more contexts.'), + 'description' => t('Set this context on the basis of other active contexts. Put each context on a separate line. You can use the <code>*</code> character (asterisk) as a wildcard and the <code>~</code> character (tilde) to prevent this context from activating if the listed context is active. Other contexts which use context conditions can not be used to exclude this context from activating.'), 'plugin' => 'context_condition_context', ), 'node' => array( @@ -23,7 +23,7 @@ function _context_context_registry() { ), 'path' => array( 'title' => t('Path'), - 'description' => t('Set this context when any of the paths above match the page path. Put each path on a separate line. You can use the "*" character as a wildcard and <code>~</code> to exclude one or more paths. Use <front> for the site front page.'), + 'description' => t('Set this context when any of the paths above match the page path. Put each path on a separate line. You can use the <code>*</code> character (asterisk) as a wildcard and the <code>~</code> character (tilde) to exclude one or more paths. Use <front> for the site front page.'), 'plugin' => 'context_condition_path', ), 'user' => array( @@ -88,6 +88,11 @@ function _context_context_registry() { 'description' => t('Control block visibility using context.'), 'plugin' => 'context_reaction_block', ), + 'region' => array( + 'title' => t('Regions'), + 'description' => t('Control Region visiblity using context.'), + 'plugin' => 'context_reaction_region', + ), 'breadcrumb' => array( 'title' => t('Breadcrumb'), 'description' => t('Set the breadcrumb trail to the selected menu item.'), @@ -137,14 +142,14 @@ function _context_context_plugins() { */ $plugins['context_condition'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition.inc', 'class' => 'context_condition', ), ); $plugins['context_condition_context'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_context.inc', 'class' => 'context_condition_context', 'parent' => 'context_condition_path', @@ -152,7 +157,7 @@ function _context_context_plugins() { ); $plugins['context_condition_node'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_node.inc', 'class' => 'context_condition_node', 'parent' => 'context_condition', @@ -160,7 +165,7 @@ function _context_context_plugins() { ); $plugins['context_condition_sitewide'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_sitewide.inc', 'class' => 'context_condition_sitewide', 'parent' => 'context_condition', @@ -168,7 +173,7 @@ function _context_context_plugins() { ); $plugins['context_condition_path'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_path.inc', 'class' => 'context_condition_path', 'parent' => 'context_condition', @@ -176,7 +181,7 @@ function _context_context_plugins() { ); $plugins['context_condition_user'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_user.inc', 'class' => 'context_condition_user', 'parent' => 'context_condition', @@ -184,7 +189,7 @@ function _context_context_plugins() { ); $plugins['context_condition_user_page'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_user_page.inc', 'class' => 'context_condition_user_page', 'parent' => 'context_condition', @@ -192,7 +197,7 @@ function _context_context_plugins() { ); $plugins['context_condition_menu'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_menu.inc', 'class' => 'context_condition_menu', 'parent' => 'context_condition', @@ -201,7 +206,7 @@ function _context_context_plugins() { if (module_exists('taxonomy')) { $plugins['context_condition_node_taxonomy'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_node_taxonomy.inc', 'class' => 'context_condition_node_taxonomy', 'parent' => 'context_condition_node', @@ -209,7 +214,7 @@ function _context_context_plugins() { ); $plugins['context_condition_taxonomy_term'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_taxonomy_term.inc', 'class' => 'context_condition_taxonomy_term', 'parent' => 'context_condition', @@ -219,7 +224,7 @@ function _context_context_plugins() { if (module_exists('locale')) { $plugins['context_condition_language'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_language.inc', 'class' => 'context_condition_language', 'parent' => 'context_condition', @@ -229,7 +234,7 @@ function _context_context_plugins() { if (module_exists('book')) { $plugins['context_condition_book'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_book.inc', 'class' => 'context_condition_book', 'parent' => 'context_condition', @@ -237,7 +242,7 @@ function _context_context_plugins() { ); $plugins['context_condition_bookroot'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_bookroot.inc', 'class' => 'context_condition_bookroot', 'parent' => 'context_condition_node', @@ -247,7 +252,7 @@ function _context_context_plugins() { if (module_exists('views')) { $plugins['context_condition_views'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_condition_views.inc', 'class' => 'context_condition_views', 'parent' => 'context_condition', @@ -260,22 +265,30 @@ function _context_context_plugins() { */ $plugins['context_reaction'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction.inc', 'class' => 'context_reaction', ), ); $plugins['context_reaction_block'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_block.inc', 'class' => 'context_reaction_block', 'parent' => 'context_reaction', ), ); + $plugins['context_reaction_region'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') . '/plugins', + 'file' => 'context_reaction_region.inc', + 'class' => 'context_reaction_region', + 'parent' => 'context_reaction', + ), + ); $plugins['context_reaction_breadcrumb'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_breadcrumb.inc', 'class' => 'context_reaction_breadcrumb', 'parent' => 'context_reaction_menu', @@ -283,7 +296,7 @@ function _context_context_plugins() { ); $plugins['context_reaction_menu'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_menu.inc', 'class' => 'context_reaction_menu', 'parent' => 'context_reaction', @@ -291,7 +304,7 @@ function _context_context_plugins() { ); $plugins['context_reaction_theme'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_theme.inc', 'class' => 'context_reaction_theme', 'parent' => 'context_reaction', @@ -299,7 +312,7 @@ function _context_context_plugins() { ); $plugins['context_reaction_theme_html'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_theme_html.inc', 'class' => 'context_reaction_theme_html', 'parent' => 'context_reaction_theme', @@ -307,7 +320,7 @@ function _context_context_plugins() { ); $plugins['context_reaction_debug'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_debug.inc', 'class' => 'context_reaction_debug', 'parent' => 'context_reaction', @@ -316,7 +329,7 @@ function _context_context_plugins() { if (module_exists('css_injector')) { $plugins['context_reaction_css_injector'] = array( 'handler' => array( - 'path' => drupal_get_path('module', 'context') .'/plugins', + 'path' => drupal_get_path('module', 'context') . '/plugins', 'file' => 'context_reaction_css_injector.inc', 'class' => 'context_reaction_css_injector', 'parent' => 'context_reaction', diff --git a/sites/all/modules/context/context_layouts/context_layouts.info b/sites/all/modules/context/context_layouts/context_layouts.info index b6246873cd99b48b32123a2b082378e5d2d14432..e41f56e2f9685fd538a73e74f11d45ac82fcdfbf 100644 --- a/sites/all/modules/context/context_layouts/context_layouts.info +++ b/sites/all/modules/context/context_layouts/context_layouts.info @@ -4,9 +4,11 @@ dependencies[] = context package = Context core = 7.x -; Information added by drupal.org packaging script on 2011-02-28 -version = "7.x-3.0-beta1" +files[] = plugins/context_layouts_reaction_block.inc + +; Information added by drupal.org packaging script on 2012-12-19 +version = "7.x-3.0-beta6" core = "7.x" project = "context" -datestamp = "1298925068" +datestamp = "1355879811" diff --git a/sites/all/modules/context/context_layouts/context_layouts.module b/sites/all/modules/context/context_layouts/context_layouts.module index 042576803fc522196afcf08f0379f49c94007174..a1fa86eb9148050819b9e94ceede3c5e411bdf8f 100644 --- a/sites/all/modules/context/context_layouts/context_layouts.module +++ b/sites/all/modules/context/context_layouts/context_layouts.module @@ -46,10 +46,12 @@ function context_layouts_theme() { foreach (list_themes() as $theme) { if (!empty($theme->status) && $layouts = context_layouts_get_layouts($theme->name)) { foreach ($layouts as $layout) { - $info["page__context_layouts_{$theme->name}_{$layout['layout']}"] = array( - 'template' => $layout['template'], - 'path' => drupal_get_path('theme', $theme->name), - ); + if (!empty($layout['template'])) { + $info["page__context_layouts_{$theme->name}_{$layout['layout']}"] = array( + 'template' => $layout['template'], + 'path' => drupal_get_path('theme', $theme->name), + ); + } } } } diff --git a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc index aa2d45d272de6e7a5a6714546be84377f549d88d..51bc928bb14d8fb56a5ebca36987a10f949f64b9 100644 --- a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc +++ b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc @@ -52,7 +52,7 @@ class context_layouts_reaction_block extends context_reaction_block { function add_layout_stylesheet() { if ($layout = $this->get_active_layout()) { if (!empty($layout['stylesheet'])) { - drupal_add_css(drupal_get_path('theme', $layout['theme']) .'/'. $layout['stylesheet']); + drupal_add_css(drupal_get_path('theme', $layout['theme']) . '/' . $layout['stylesheet']); } } } @@ -61,7 +61,7 @@ class context_layouts_reaction_block extends context_reaction_block { * Override of editor form. */ function editor_form($context) { - drupal_add_css(drupal_get_path('module', 'context_layouts') .'/plugins/context_layouts_reaction_block.css'); + drupal_add_css(drupal_get_path('module', 'context_layouts') . '/plugins/context_layouts_reaction_block.css'); $form = parent::editor_form($context); @@ -81,7 +81,7 @@ class context_layouts_reaction_block extends context_reaction_block { '#default_value' => isset($options['layout']) ? $options['layout'] : NULL, '#required' => FALSE, '#empty_value' => 0, - '#empty_option' => '- '. t('Site default') .' -', + '#empty_option' => '- ' . t('Site default') . ' -', ), 'update' => array( '#value' => t('Change layout'), @@ -134,13 +134,13 @@ class context_layouts_reaction_block extends context_reaction_block { '#attributes' => array('class' => array('context-blockform-layout')), '#required' => FALSE, '#empty_value' => 0, - '#empty_option' => '- '. t('Site default') .' -', + '#empty_option' => '- ' . t('Site default') . ' -', ); // Add js. // @TODO: Move this to a theme function or somewhere that will get called even // if the form is using a cached version of itself (e.g. when validate fails). - drupal_add_js(drupal_get_path('module', 'context_layouts') .'/plugins/context_layouts_reaction_block.js'); + drupal_add_js(drupal_get_path('module', 'context_layouts') . '/plugins/context_layouts_reaction_block.js'); drupal_add_js(array('contextLayouts' => array('layouts' => $this->get_layout_regions())), 'setting'); } return $form; diff --git a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js index decc0690ad3b7e981f2a427263f0932af1325dfc..2c75ebb2eed941d19078f6dc3e49c0fd19b735ea 100644 --- a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js +++ b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js @@ -9,11 +9,10 @@ Drupal.behaviors.contextLayoutsReactionBlock.attach = function(context) { $(this).change(function() { var layout = $(this).val(); if (Drupal.settings.contextLayouts.layouts[layout]) { - $('#context-blockform td.blocks table').hide(); - $('#context-blockform td.blocks div.label').hide(); + $('#context-blockform td.blocks').find('table, div.label, div.tabledrag-toggle-weight-wrapper').hide(); for (var key in Drupal.settings.contextLayouts.layouts[layout]) { var region = Drupal.settings.contextLayouts.layouts[layout][key]; - $('.context-blockform-regionlabel-'+region).show(); + $('.context-blockform-regionlabel-'+region).show().next('div.tabledrag-toggle-weight-wrapper').show(); $('#context-blockform-region-'+region).show(); } if (Drupal.contextBlockForm) { diff --git a/sites/all/modules/context/context_ui/README.txt b/sites/all/modules/context/context_ui/README.txt index 3a31d3c6d84c2dbe1a023aeacb359bde665ea631..da83fd9cd77b8ad3ee2cc67d37ff574fc05b0b11 100644 --- a/sites/all/modules/context/context_ui/README.txt +++ b/sites/all/modules/context/context_ui/README.txt @@ -49,6 +49,13 @@ Whenever a particular context is active, all of its reactions will be run. Like conditions, reactions can be added or removed and have settings that can be configured. +- **Reaction Block Groupings**: You can influence what "group" a block appears + in when listing all blocks available to be added to a region. This is done + by specifying $block->context_group via hook_block_info. If no group is + specified it will default to the module name, but if a group is specified + it will be grouped under that group name. + + Using the inline editor ----------------------- diff --git a/sites/all/modules/context/context_ui/context_ui.css b/sites/all/modules/context/context_ui/context_ui.css index 4c2231e9eaf351ed5ff44653fb41c3e099aba592..a0b3058916b32bf9709fc0f837d4c20dbdec3ab1 100644 --- a/sites/all/modules/context/context_ui/context_ui.css +++ b/sites/all/modules/context/context_ui/context_ui.css @@ -1,8 +1,17 @@ /** * Editor ============================================================= */ -div.context-editor div.label { float:left; } +div.context-editor div.label { + float:left; + font-size: 14px; +} div.context-editor div.links { float:right; } +div.context-editor div.bottom { + font-size: 12px; + font-style: italic; + font-weight:normal; + color: #999; +} div.context-editor div.context-editable { display:none; } div.context-editor div.links a.done { display:none; } @@ -89,7 +98,7 @@ table.context-admin input.form-text { width:90%; } .context-plugins { position:relative; margin:0px 0px 10px; - } +} .context-plugins .context-plugin-info { padding:10px 10px 9px; diff --git a/sites/all/modules/context/context_ui/context_ui.info b/sites/all/modules/context/context_ui/context_ui.info index ea628b316350bf9b2e11dda46ae64a08999d2f42..005caf2ed6a1547b11cf17aa56cb7b13d914232b 100644 --- a/sites/all/modules/context/context_ui/context_ui.info +++ b/sites/all/modules/context/context_ui/context_ui.info @@ -3,13 +3,14 @@ description = "Provides a simple UI for settings up a site structure using Conte dependencies[] = context package = Context core = "7.x" +configure = admin/structure/context files[] = context.module files[] = tests/context_ui.test -; Information added by drupal.org packaging script on 2011-02-28 -version = "7.x-3.0-beta1" +; Information added by drupal.org packaging script on 2012-12-19 +version = "7.x-3.0-beta6" core = "7.x" project = "context" -datestamp = "1298925068" +datestamp = "1355879811" diff --git a/sites/all/modules/context/context_ui/context_ui.module b/sites/all/modules/context/context_ui/context_ui.module index 5fcb32ccbc7bb014eedda2eaeabf13a8418c9eb0..82e355638345c6e0a723bce80e8d57ac007eee84 100644 --- a/sites/all/modules/context/context_ui/context_ui.module +++ b/sites/all/modules/context/context_ui/context_ui.module @@ -53,7 +53,7 @@ function context_ui_block_info() { function context_ui_block_view($delta = '') { switch ($delta) { case 'editor': - if (user_access('administer site configuration') && strpos($_GET['q'], 'admin/structure/context') === FALSE && $contexts = context_active_contexts()) { + if (user_access('administer contexts') && strpos($_GET['q'], 'admin/structure/context') === FALSE && $contexts = context_active_contexts()) { return array( 'subject' => t('Context editor'), 'content' => drupal_get_form('context_ui_editor', $contexts), @@ -71,6 +71,18 @@ function context_ui_block_view($delta = '') { } } +/** + * Implementation of hook_permission(). + */ +function context_ui_permission() { + $permissions = array(); + $permissions['administer contexts'] = array( + 'title' => 'Administer contexts', + 'description' => 'Associate menus, views, blocks, etc. with different contexts to structure your site.' + ); + return $permissions; +} + /** * Implementation of hook_menu(). */ @@ -79,12 +91,24 @@ function context_ui_menu() { $items['admin/structure/context/settings'] = array( 'title' => 'Settings', 'access callback' => 'user_access', - 'access arguments' => array('administer site configuration'), + 'access arguments' => array('administer contexts'), 'page callback' => 'drupal_get_form', 'page arguments' => array('context_ui_settings'), 'type' => MENU_LOCAL_TASK, 'weight' => 3, ); + $items['context-ui/activate'] = array( + 'title' => 'Activate Context UI', + 'access arguments' => array('administer contexts'), + 'page callback' => 'context_ui_activate', + 'type' => MENU_CALLBACK + ); + $items['context-ui/deactivate'] = array( + 'title' => 'Deactivate Context UI', + 'access arguments' => array('administer contexts'), + 'page callback' => 'context_ui_deactivate', + 'type' => MENU_CALLBACK + ); return $items; } @@ -106,22 +130,42 @@ function context_ui_help($path, $arg) { */ function context_ui_editor($form, &$form_state, $contexts) { $form = array( - '#attributes' => array('class' => 'context-editor'), + '#attributes' => array('class' => array('context-editor')), '#theme' => array('context_ui_editor'), 'editables' => array(), 'contexts' => array('#tree' => TRUE), 'buttons' => array('#tree' => FALSE), ); + $form['title'] = array( + '#prefix' => '<h2 class="context-editor-title">', + '#markup' => t('Select the Context/Layer to Edit'), + '#suffix' => '</h2>', + '#weight' => -2, + ); + + //add some help text to the top of the form + $form['help'] = array ( + '#prefix' => '<p class="context-help help">', + '#markup' => t('Select which context, or layer of blocks, to edit. + Each context is configured to appear on different sets of pages so read the description carefully. + When you are done editing click Done and save your changes. + You may use the Stop Editing Layout link to close the editor.'), + '#suffix' => '</p>', + '#weight' => -1, + ); + $items = array(); $form_context = array(); ksort($contexts); - foreach ($contexts as $context) { $edit = l(t('Edit'), $_GET['q'], array('fragment' => $context->name, 'attributes' => array('class' => array('edit')))); $done = l(t('Done'), $_GET['q'], array('fragment' => $context->name, 'attributes' => array('class' => array('done')))); + $readable_name = ucwords(str_replace('_', ' ', $context->name)); + $description = empty($context->description) ? '' : + "<br/><div class='label bottom'>".check_plain($context->description)."</div>"; $items[] = array( - 'data' => "<div class='label'>" . (empty($context->description) ? $context->name : check_plain($context->description)) ."</div><div class='links'>{$edit} {$done}</div>", + 'data' => "<div class='label top'>" . $readable_name. "</div><div class='links'>{$edit} {$done}</div>" . $description, 'class' => array('context-editable clearfix'), 'id' => "context-editable-trigger-{$context->name}", ); @@ -131,18 +175,6 @@ function context_ui_editor($form, &$form_state, $contexts) { 'context' => array('#type' => 'value', '#value' => $context), ); - // Edit context conditions. - foreach (array_keys(context_conditions()) as $condition) { - $plugin = context_get_plugin('condition', $condition); - if (method_exists($plugin, 'editor_form') && ($plugin_form = $plugin->editor_form($context))) { - $form_context['condition'][$condition] = $plugin_form; - } - } - if (count(element_children($form_context['condition']))) { - $form_context['condition']['#title'] = t('Conditions'); - $form_context['condition']['#description'] = t('This context is active when any of the selected conditions are true.'); - } - // Edit context reactions. foreach (array_keys(context_reactions()) as $reaction) { $plugin = context_get_plugin('reaction', $reaction); @@ -166,9 +198,18 @@ function context_ui_editor($form, &$form_state, $contexts) { ); $form['buttons']['cancel'] = array( '#type' => 'submit', - '#value' => t('Cancel'), + '#value' => t('Reset'), '#submit' => array('context_ui_editor_cancel'), ); + + $form['stop'] = array( + '#markup' => l(t('Stop Editing Layout'), 'context-ui/deactivate', array( + 'query' => array('destination' => current_path()), + 'attributes' => array('class' => array('context_ui_dialog-stop')), + ) + ), + ); + return $form; } @@ -208,10 +249,10 @@ function context_ui_editor_process($values) { */ function context_ui_editor_submit(&$form, &$form_state) { foreach ($form_state['values']['contexts'] as $name => $values) { - $original_conditions = $values['context']->conditions; - $original_reactions = $values['context']->reactions; + $original_reactions = var_export($values['context']->reactions, TRUE); $context = context_ui_editor_process($values); - if (($original_conditions !== $context->conditions) || ($original_reactions !== $context->reactions)) { + //compare string values instead of actual objects to avoid problems with aliasing + if (($original_reactions !== var_export($context->reactions, TRUE))) { if (context_save($context)) { drupal_set_message(t('Saved %title.', array( '%title' => (!empty($context->description) ? $context->description : $context->name) @@ -241,10 +282,10 @@ function context_ui_settings($form, &$form_state) { if ($plugin = context_get_plugin('condition', $condition)) { $settings_form = $plugin->settings_form(); if ($settings_form) { - $form['conditions'][$reaction] = $settings_form; - $form['conditions'][$reaction]['#tree'] = FALSE; - $form['conditions'][$reaction]['#type'] = 'fieldset'; - $form['conditions'][$reaction]['#title'] = $info['title']; + $form['conditions'][$condition] = $settings_form; + $form['conditions'][$condition]['#tree'] = FALSE; + $form['conditions'][$condition]['#type'] = 'fieldset'; + $form['conditions'][$condition]['#title'] = $info['title']; } } } @@ -259,6 +300,12 @@ function context_ui_settings($form, &$form_state) { } } } + $form['context_ui_dialog_enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Use Context Editor Dialog'), + '#default_value' => context_ui_dialog_is_enabled(), + '#description' => t('When enabled all contextual links will have a Edit Layout link that will refresh the page with the context editor in a dialog box.'), + ); $form = system_settings_form($form); $form['#submit'][] = 'context_ui_settings_submit'; return $form; @@ -271,3 +318,116 @@ function context_ui_settings($form, &$form_state) { function context_ui_settings_submit($form, &$form_state) { variable_set('menu_rebuild_needed', TRUE); } + + +/** + * context_ui_dialog_is_enabled test if the dialog is enabled + */ +function context_ui_dialog_is_enabled() { + + return variable_get("context_ui_dialog_enabled", FALSE); +} + +/** + * Implementation of hook_page_alter(). + * + * If we have the dialog enabled and active build the dialog + * and add to the page + */ +function context_ui_page_alter(&$page) { + $contexts = context_active_contexts(); + if ( + context_ui_dialog_is_enabled() && + context_isset('context_ui', 'context_ui_editor_present') + ) { + $contexts = context_active_contexts(); + $form = drupal_get_form('context_ui_editor', $contexts); + + $path = drupal_get_path('module', 'context_ui'); + drupal_add_library('system', 'ui.dialog'); + drupal_add_js($path . '/context_ui_dialog.js', array('type' => 'file', 'weight' => 50)); + drupal_add_css($path . '/context_ui_dialog.css'); + + //figure out which region to put it in - allow it to be configured for themes using different regions + $placement = variable_get('context_ui_editor_block_region', 'content'); + $page[$placement]['context_ui_editor'] = array( + 0 => array( + '#type' => 'markup', + '#markup' => '<div style="display:none;" id="context_ui_dialog-context-ui">' . drupal_render($form) . '<!--[if IE 8 ]><div id="context_ui_dialog-shadow"></div><![endif]--></div>', + ), + ); + } +} + +/** + * Implementation of hook_menu_contextual_links_alter(). + * + * we we have the dialog enabled lets add a link to all contextual links + * to activate it. + */ +function context_ui_menu_contextual_links_alter(&$links, $router_item, $root_path) { + if(context_ui_dialog_is_enabled() && + !context_isset('context_ui', 'context_ui_editor_present')) { + $links['layout'] = array( + 'href' => 'context-ui/activate', + 'title' => t('Configure Layout'), + 'localized_options' => array( + 'query' => array('destination'=> $_GET['q']), + 'options' => array('html' => FALSE, 'attributes' => array()), + ), + ); + } +} + +/** + * A page call back to activate the context_ui inline editor dialog. + */ +function context_ui_activate() { + $_SESSION['context_ui_active'] = $_GET['destination']; + drupal_goto($_GET['destination']); +} + +/** + * A page call back to deactivate the context_ui inline editor dialog. + * This is semi unecessary as context editor will auto deactivate upon going to any + * page other than the destination from the start. However, its useful as a place + * to navigate to when deactivating context_ui_editor + */ +function context_ui_deactivate() { + $_SESSION['context_ui_active'] = FALSE; + drupal_goto($_GET['destination']); +} + +/** + * Implemenation of hook_init(). + * + * If the session says we should have an active dialog set a context variable to tell everything else + * Ignores ajax requests. + */ +function context_ui_init() { + if (!empty($_SESSION['context_ui_active'])) { + $path = $_SESSION['context_ui_active']; + if( $path == request_path() || $path == drupal_get_path_alias() || $path == drupal_get_normal_path(request_path()) ) { + context_set('context_ui', 'context_ui_editor_present', TRUE); + } + } + // Turn off functionality has been moved to hook_page_build() to prevent non-pages from triggering it +} + +/** + * Implementation of hook_page_build(). + * Turn off the context_ui functionality if we move to a different page + */ +function context_ui_page_build(&$page) { + if (!context_get('context_ui', 'context_ui_editor_present') && isset($_SESSION['context_ui_active'])) { + $_SESSION['context_ui_active'] = FALSE; + } +} + +/** + * Ajax callback to get the list of available blocks + * + */ +function context_ui_get_available_blocks() { + drupal_json_output(array('lols' => 'testing')); +} diff --git a/sites/all/modules/context/context_ui/context_ui_dialog.css b/sites/all/modules/context/context_ui/context_ui_dialog.css new file mode 100644 index 0000000000000000000000000000000000000000..94e2b00ae40c8c6986b8932d88e3b905b9144be0 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui_dialog.css @@ -0,0 +1,106 @@ +#context_ui_dialog-context-ui .item-list { + overflow-y: auto; + overflow-x: hidden; + padding-right: 5px; +} +#context_ui_dialog-context-ui a, #context_ui_dialog-context-ui li a.active { + color: #222; +} +#context_ui_dialog-context-ui ul { + margin: 0; +} +#context_ui_dialog-context-ui ul li { + padding: 4px 0; + list-style: none; +} + +#context_ui_dialog-context-ui .buttons { + padding-top:15px; +} + +#context_ui_dialog-context-ui div.admin-pane-condition, +#context_ui_dialog-context-ui div.admin-pane-reaction-theme, +#context_ui_dialog-context-ui div.admin-pane-reaction-theme_html { + display: none; +} + +.boxes-box-editing { + background: none repeat scroll 0 0 #EEEEDD; + display: inline-block; + padding: 3px; +} + +body.context-editing div.contextual-links-wrapper { + right: 0; + top: 40px; +} + +#context_ui_dialog-context-ui a.context_ui_dialog-stop, +#context_ui_dialog-context-ui a:link.context_ui_dialog-stop, +#context_ui_dialog-context-ui a:visited.context_ui_dialog-stop { + float:right; + padding-top:18px; + color: #ad3f00; +} + +form.context-editing li.context-editable { + opacity: .2; +} +form.context-editing li.context-editing { + visibility : visible; + display : list-item; + opacity: 1; +} +#context_ui_dialog-shadow { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + background-color: #fff; + /* For IE 8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#555555')"; + /* For IE 5.5 - 7 */ + filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#555555'); +} +#context_ui_dialog-context-ui { + width: 550px; + position:fixed; + background:#fff; + color:#222; + top:140px; + display:none; + z-index:400; + padding:10px; + font-weight:bold; + border:1px solid #ddd; + border-left:0; + border-radius:0 6px 6px 0; + /* Shadow */ + -moz-box-shadow: 3px 3px 4px #555; + -webkit-box-shadow: 3px 3px 4px #555; + box-shadow: 3px 3px 4px #555; +} + +.context-ui-dialog-open { + border-radius:0 6px 6px 0; + position: absolute; + background:#fff; + color:#222; + top: 50px; + padding: 10px; + width: 55px; + right:-76px; + border:1px solid #ddd; + border-left:0; + font-size:12px; + /* Shadow */ + -moz-box-shadow: 3px 3px 4px #555; + -webkit-box-shadow: 3px 3px 4px #555; + box-shadow: 3px 3px 4px #555; + /* For IE 8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#555555')"; + /* For IE 5.5 - 7 */ + filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#555555'); +} diff --git a/sites/all/modules/context/context_ui/context_ui_dialog.js b/sites/all/modules/context/context_ui/context_ui_dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..dc73f91e91072431377f0c2d886423a35f078a5b --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui_dialog.js @@ -0,0 +1,47 @@ +(function ($) { + + Drupal.behaviors.context_ui_dialog = { + attach: function(context) { + var selector = $('#context_ui_dialog-context-ui', context).not('context_ui_dialog-processed'); + + if(selector) { + selector.addClass('context_ui_dialog-processed'); + selector.detach(); + $('#page').prepend(selector); + + var labelOpen = Drupal.t('Select Context'); + var labelClose = Drupal.t('Hide'); + + // Create a tab to show/hide our edit area + var tab = $('<a href="javascript:" class="context-ui-dialog-open" title="Show Context Selector">'+labelClose+'</a>'); + selector.append(tab); + + selector.toggled = false; + var width = $(selector).outerWidth(); + tab.click(function(e){ + if(selector.toggled) { + selector.stop(true, false).animate({'left':0}, 400); + selector.toggled = false; + $(this).text(labelClose); + } else { + selector.stop(true, false).animate({'left':-width-4}, 400); + selector.toggled = true; + $(this).text(labelOpen); + } + }); + + $('#context_ui_dialog-context-ui').show(); + + // Make sure the UI is 60% of the size of the window + var context_ui_height = Math.round(6 * $(window).height() / 10); + var item_list_height = context_ui_height - 200; + item_list_height = (item_list_height < 50) ? 50 : item_list_height; + $('#context_ui_dialog-context-ui').height(context_ui_height); + $('#context_ui_dialog-context-ui .item-list').height(item_list_height); + + // Add a class to body + $('body').once().addClass('context-field-editor'); + } + } + }; +})(jQuery); diff --git a/sites/all/modules/context/context_ui/export_ui/context.inc b/sites/all/modules/context/context_ui/export_ui/context.inc index 968a88b5ba60c64c44fbdcba6aa92eead80b571e..9dcab78e7dd9c89589defa10ccc26b0cc7c9264c 100644 --- a/sites/all/modules/context/context_ui/export_ui/context.inc +++ b/sites/all/modules/context/context_ui/export_ui/context.inc @@ -1,6 +1,7 @@ <?php $plugin = array( + 'access' => 'administer contexts', 'schema' => 'context', 'menu' => array( 'menu prefix' => 'admin/structure', diff --git a/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php b/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php index 30b534ffdb1ba98f53cf98e5a59120664ef46bd7..881f9ae42c563248100df213bb47ec60f016c92c 100644 --- a/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php +++ b/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php @@ -74,6 +74,24 @@ class context_export_ui extends ctools_export_ui { $this->plugin['form']['submit']($form, $form_state); } } + + /** + * Override default final validation for ctools. With import wizard + * it was possible to get default ctools export ui name validation + * rules, this ensures we always get ours. + */ + function edit_finish_validate(&$form, &$form_state) { + if ($form_state['op'] != 'edit') { + // Validate the name. Fake an element for form_error(). + $export_key = $this->plugin['export']['key']; + $element = array( + '#value' => $form_state['item']->{$export_key}, + '#parents' => array('name'), + ); + $form_state['plugin'] = $this->plugin; + context_ui_edit_name_validate($element, $form_state); + } + } } @@ -103,10 +121,8 @@ function context_ui_form(&$form, &$form_state) { $form['info']['#type'] = 'fieldset'; $form['info']['#tree'] = FALSE; - // Swap out name validator. Allow dashes. - if (isset($form['info']['name']['#element_validate'])) { - $form['info']['name']['#element_validate'] = array('context_ui_edit_name_validate'); - } + + $form['info']['name']['#element_validate'] = array('context_ui_edit_name_validate'); $form['info']['tag'] = array( '#title' => t('Tag'), @@ -245,7 +261,7 @@ function _context_ui_rebuild_from_input($context, $input, $conditions, $reaction * A context object */ function context_ui_form_process($context, $form, $submit = TRUE) { - $context->name = isset($form['name']) ? $form['name'] : NULL; + $context->name = isset($form['name']) ? $form['name'] : $context->name; $context->description = isset($form['description']) ? $form['description'] : NULL; $context->tag = isset($form['tag']) ? $form['tag'] : NULL; $context->condition_mode = isset($form['condition_mode']) ? $form['condition_mode'] : NULL; @@ -297,12 +313,14 @@ function context_ui_edit_name_validate($element, &$form_state) { $plugin = $form_state['plugin']; // Check for string identifier sanity if (!preg_match('!^[a-z0-9_-]+$!', $element['#value'])) { - form_error($element, t('The export id can only consist of lowercase letters, underscores, dashes, and numbers.')); + form_error($element, t('The name can only consist of lowercase letters, underscores, dashes, and numbers.')); return; } // Check for name collision - if (empty($form_state['item']->export_ui_allow_overwrite) && $exists = ctools_export_crud_load($plugin['schema'], $element['#value'])) { - form_error($element, t('A @plugin with this name already exists. Please choose another name or delete the existing item before creating a new one.', array('@plugin' => $plugin['title singular']))); + if ($form_state['op'] != 'edit') { + if (empty($form_state['item']->export_ui_allow_overwrite) && $exists = ctools_export_crud_load($plugin['schema'], $element['#value'])) { + form_error($element, t('A @plugin with this name already exists. Please choose another name or delete the existing item before creating a new one.', array('@plugin' => $plugin['title singular']))); + } } } diff --git a/sites/all/modules/context/context_ui/tests/context_ui.test b/sites/all/modules/context/context_ui/tests/context_ui.test index 7330e51691af44071a55fc21a28d2a57392d65ba..1e8092ce43e4ba4e811cc21c95136de05f012550 100644 --- a/sites/all/modules/context/context_ui/tests/context_ui.test +++ b/sites/all/modules/context/context_ui/tests/context_ui.test @@ -18,7 +18,7 @@ class ContextUiTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp('ctools', 'context', 'context_ui', 'blog'); + parent::setUp('ctools', 'context', 'context_ui', 'blog', 'menu'); // Create and login user $admin_user = $this->drupalCreateUser(array( @@ -46,11 +46,15 @@ class ContextUiTestCase extends DrupalWebTestCase { 'name' => $context->name, 'description' => $context->description, 'tag' => $context->tag, - 'conditions[plugins][node][values][page]' => 'blog', + 'conditions[plugins][node][values][blog]' => 'blog', 'reactions[plugins][menu]' => 'node/add/blog', ); $this->drupalPost('admin/structure/context/add', $edit, 'Save'); $this->assertText($context->name . ' has been created.', 'Context saved.'); + $edit = array(); + // export_ui confirm delete + $this->drupalPost('admin/structure/context/list/' . $context->name . '/edit', $edit, 'Delete'); + $this->assertTrue(strpos($this->getUrl(), 'admin/structure/context/list/' . $context->name . '/delete') !== FALSE, 'Context deletion confirmation page displayed'); } } diff --git a/sites/all/modules/context/context_ui/theme/filter.js b/sites/all/modules/context/context_ui/theme/filter.js new file mode 100644 index 0000000000000000000000000000000000000000..befd5b7adad2e67a80217d8995eb5916bff4f90b --- /dev/null +++ b/sites/all/modules/context/context_ui/theme/filter.js @@ -0,0 +1,66 @@ +/** + * create a simple search filter thing for a list + */ +(function ($) { + Drupal.Filter = function (list, title, type, parent){ + this.list = list; + this.title = title; + //provide defaults for type and parent so bad things don't happen + if (!type) { var type = '*'; } + this.type = type; + if (!parent) { var parent = list; } + this.parent = parent; + + this.init(); + } + + Drupal.Filter.prototype = { + init : function(){ + this.wrapper = $('<div class="filter-wrapper"></div>'); + if(this.title){ + this.title = '<h3>' + this.title + '</h3>'; + this.wrapper.append(this.title); + } + this.input = $('<input type="text" class="filter" />'); + this.wrapper.append(this.input); + + $(this.parent).append(this.wrapper); + this.createHandlers(); + }, + createHandlers : function(){ + var self = this; + $(this.input).keyup(function(e){ + self.filter(); + }); + }, + filter : function(){ + //show all first off + $('*', this.list).show(); + //hide ignored items + if(this.input.val()) { + $('*', this.list).not(this.type).hide(); + } + + var regex = new RegExp(this.input.val(), 'i'); + + var self = this; + $(this.type, this.list).each(function(ind, el) { + var string = self.strip(el.innerHTML); + if(!regex.test(string)){ + $(el).hide(); + } else { //show the parent and any labels or whatever in the parent + var parent = $(el).parent().show(); + $('*', parent).not(self.type).show(); + } + }); + }, + strip : function(string){ + var strip = /<([^<|^>]*)>/i; + while(strip.test(string)){ + var matches = string.match(strip); + string = string.replace(strip, ''); + } + return string; + } + }; +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/context/context_ui/theme/theme.inc b/sites/all/modules/context/context_ui/theme/theme.inc index 7d969e4175bcaf539f8bd622aea2d40a1a9fb8e6..8004e9ee7be024084beaf84956975d08850ee7a2 100644 --- a/sites/all/modules/context/context_ui/theme/theme.inc +++ b/sites/all/modules/context/context_ui/theme/theme.inc @@ -19,8 +19,8 @@ function template_preprocess_context_ui_editor(&$vars) { * Preprocessor for theme('context_ui_plugins'). */ function template_preprocess_context_ui_plugins(&$vars) { - drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css"); - drupal_add_js(drupal_get_path("module", "context_ui") ."/context_ui.js"); + drupal_add_css(drupal_get_path("module", "context_ui") . "/context_ui.css"); + drupal_add_js(drupal_get_path("module", "context_ui") . "/context_ui.js"); drupal_add_js(drupal_get_path('module', 'context_ui') . '/jquery.pageEditor.js'); // Provide title & desc. @@ -32,7 +32,7 @@ function template_preprocess_context_ui_plugins(&$vars) { $vars['plugins'] = array(); foreach (element_children($vars['form']['plugins']) as $plugin) { $link = array( - 'title' => $vars['form']['plugins'][$plugin]['#plugin']->title . "<span class='remove'>". t('Remove') ."</span>", + 'title' => $vars['form']['plugins'][$plugin]['#plugin']->title . "<span class='remove'>" . t('Remove') . "</span>", 'href' => $_GET['q'], 'html' => TRUE, 'fragment' => "context-plugin-form-{$plugin}", @@ -46,8 +46,8 @@ function template_preprocess_context_ui_plugins(&$vars) { * Preprocessor for theme('context_ui_form'). */ function template_preprocess_context_ui_form(&$vars) { - drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css"); - drupal_add_js(drupal_get_path("module", "context_ui") ."/context_ui.js"); + drupal_add_css(drupal_get_path("module", "context_ui") . "/context_ui.css"); + drupal_add_js(drupal_get_path("module", "context_ui") . "/context_ui.js"); drupal_add_js(drupal_get_path('module', 'context_ui') . '/jquery.pageEditor.js'); $vars['buttons'] = $vars['form']['buttons']; unset($vars['form']['buttons']); diff --git a/sites/all/modules/context/plugins/context_condition_menu.inc b/sites/all/modules/context/plugins/context_condition_menu.inc index cb58c54d0ec396e23d8beb89f92059724c9b6f6e..2f50df5f00614dede16cd50e6ef0a43e81759c27 100644 --- a/sites/all/modules/context/plugins/context_condition_menu.inc +++ b/sites/all/modules/context/plugins/context_condition_menu.inc @@ -27,7 +27,7 @@ class context_condition_menu extends context_condition { } unset($menus[$key]); } - array_unshift($menus, "-- ". t('None') ." --"); + array_unshift($menus, "-- " . t('None') . " --"); } else { $menus = array(); diff --git a/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc b/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc index ec976943dda36920816261632489ac2a9f5c038d..cfb214f934e3c0b2d71359159d3312622c2fe722 100644 --- a/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc +++ b/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc @@ -31,8 +31,8 @@ class context_condition_node_taxonomy extends context_condition_node { $options[$vocabulary->name] = array(); foreach ($tree as $term) { $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name; - } - } + } + } } $form['#options'] = $options; return $form; @@ -51,8 +51,8 @@ class context_condition_node_taxonomy extends context_condition_node { if ($this->condition_used() && !empty($check_fields)) { foreach ($check_fields as $field) { - if (isset($node->{$field}[$node->language])) { - foreach ($node->{$field}[$node->language] as $term) { + if ($terms = field_get_items('node', $node, $field)) { + foreach ($terms as $term) { foreach ($this->get_contexts($term['tid']) as $context) { // Check the node form option. if ($op === 'form') { diff --git a/sites/all/modules/context/plugins/context_condition_path.inc b/sites/all/modules/context/plugins/context_condition_path.inc index 0babde962fce4340b021e2b9259057389542f052..d3717c8b491bb2beb50fc2531a5aabde6193a573 100644 --- a/sites/all/modules/context/plugins/context_condition_path.inc +++ b/sites/all/modules/context/plugins/context_condition_path.inc @@ -47,7 +47,7 @@ class context_condition_path extends context_condition { if ($this->condition_used()) { // Include both the path alias and normal path for matching. $current_path = array(drupal_get_path_alias($_GET['q'])); - if ($current_path != $_GET['q']) { + if ($current_path[0] != $_GET['q']) { $current_path[] = $_GET['q']; } foreach ($this->get_contexts() as $context) { @@ -91,10 +91,10 @@ class context_condition_path extends context_condition { $pattern = ltrim($pattern, '~'); if (!isset($regexps[$pattern])) { if ($path) { - $regexps[$pattern] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($pattern, '/')) .')$/'; + $regexps[$pattern] = '/^(' . preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2'), preg_quote($pattern, '/')) . ')$/'; } else { - $regexps[$pattern] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/'), array('|', '.*'), preg_quote($pattern, '/')) .')$/'; + $regexps[$pattern] = '/^(' . preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/'), array('|', '.*'), preg_quote($pattern, '/')) . ')$/'; } } foreach ($subject as $value) { diff --git a/sites/all/modules/context/plugins/context_condition_views.inc b/sites/all/modules/context/plugins/context_condition_views.inc index 939a472c5b2506cf451a63046894b9f5ffe7f3db..4cb92f61c202efa2c7878baf58f6340deac24985 100644 --- a/sites/all/modules/context/plugins/context_condition_views.inc +++ b/sites/all/modules/context/plugins/context_condition_views.inc @@ -19,7 +19,7 @@ class context_condition_views extends context_condition { $displays = array(); foreach ($view->display as $id => $display) { if ($display->display_plugin == 'page') { - $displays[$view->name .":". $id] = check_plain("-- {$display->display_title}"); + $displays[$view->name . ":" . $id] = check_plain("-- {$display->display_title}"); } } $enabled_views += $displays; diff --git a/sites/all/modules/context/plugins/context_reaction.inc b/sites/all/modules/context/plugins/context_reaction.inc index 5c291da1ea16898444259290d8029ef0b07d803c..f324339594aa87bf48d57bc9c078a1622477a643 100644 --- a/sites/all/modules/context/plugins/context_reaction.inc +++ b/sites/all/modules/context/plugins/context_reaction.inc @@ -35,6 +35,7 @@ class context_reaction { } function options_form($context) { + return array(); } /** diff --git a/sites/all/modules/context/plugins/context_reaction_block.css b/sites/all/modules/context/plugins/context_reaction_block.css index f1087bc32919c1764d932c0c5d30cd96adea13a2..81083bb9aa6592732fca9c0b28af3e26489f26bd 100644 --- a/sites/all/modules/context/plugins/context_reaction_block.css +++ b/sites/all/modules/context/plugins/context_reaction_block.css @@ -9,93 +9,125 @@ /** * Browser */ -div.context-block-browser div.category { display:none; } - -div.context-block-item, -div.context-block-browser div.draggable-placeholder, -#admin-toolbar div.context-block-browser div.context-block-item { +.context-block-browser { + width: 600px; +} + +.context-block-browser .blocks { + height:98%; + overflow: auto; + float: left; + width: 320px; +} + +.context-block-browser .block-browser-sidebar { + float: left; + width: 250px; + padding: 0 0 0 15px; +} + +.context-block-item, +.context-block-browser .draggable-placeholder, +#admin-toolbar .context-block-browser .context-block-item { font-size:11px; line-height:20px; height:20px; - text-shadow:#333 0px 1px 0px; - color:#fff; + color:#333; - padding:5px 4px 4px 5px; + padding:3px 3px 3px 3px; margin:0px 1px 1px 0px; max-width:300px; white-space:nowrap; overflow:hidden; - background:url(context_reaction_block.png) 0px -40px repeat-x; + background:#efefef; + border:1px solid #ddd; position:relative; + border-radius:5px; -moz-border-radius:5px; - -webkit-border-radius:5px; -moz-user-select:none; - -webkit-user-select:none; - } +- -webkit-user-select:none; +} - div.context-block-item span.icon { + + .context-block-addable { cursor: pointer; } + + .context-block-item span.icon { background:url(context_reaction_block.png) 0px -80px no-repeat; display:block; width:20px; height:20px; float:left; margin-right:5px; - } + } - div.context-block-loading { max-width:none; } + .context-block-loading { max-width:none; } - div.context-block-loading span.icon { + .context-block-loading span.icon { background-position:-20px -80px; float:none; margin:0px auto; - } + } - div.context-block-browser div.draggable-placeholder { padding:2px 1px 1px 2px; } + .context-block-browser .draggable-placeholder { padding:2px 1px 1px 2px; } - #admin-toolbar.horizontal div.context-block-browser div.draggable-placeholder, - #admin-toolbar.horizontal div.context-block-browser div.context-block-item { + #admin-toolbar.horizontal .context-block-browser .draggable-placeholder, + #admin-toolbar.horizontal .context-block-browser .context-block-item { width:180px; margin-right:1px; padding-right:9px; float:left; - } + } -div.context-block-addable { cursor: move; } -div.context-block-added { display:none !important; } + + .context-block-added { display:none !important; } /** * Inline editing elements ============================================ */ -a.context-block-region { display:none; } +div.context-block-region {display: none;} a.context-block { display:none !important; } -body.context-editing div.context-block-region-empty a.context-block-region { +body.context-editing div.context-block-region { -moz-border-radius:5px; -webkit-border-radius:5px; background:#666; color:#fff; - opacity:.25; + opacity: 0.5; + -moz-opacity: 0.5; + filter:alpha(opacity=50); display:block; height:40px; - line-height:40px; + line-height:24px; text-align:center; font-size:18px; white-space:nowrap; +} + +.context-block-region .region-name { + width:100%; + text-align:center; + font-size:18px; + color:#fff; + white-space:nowrap; + padding:; + display:block; + -moz-user-select:none; + -webkit-user-select:none; } -body.context-editing .ui-sortable div.block { opacity:.25; } +body.context-editing .ui-sortable .block { opacity:.25; } -body.context-editing .ui-sortable div.draggable { +body.context-editing .ui-sortable .draggable { position:relative; opacity:1; } -body.context-editing div.draggable-placeholder { +body.context-editing .draggable-placeholder { -moz-border-radius:5px; -webkit-border-radius:5px; @@ -104,8 +136,8 @@ body.context-editing div.draggable-placeholder { opacity:.2; } -body.context-editing div.draggable:hover a.context-block-remove, -body.context-editing div.draggable:hover a.context-block-handle { +body.context-editing .draggable:hover a.context-block-remove, +body.context-editing .draggable:hover a.context-block-handle { background:url(context_reaction_block.png) no-repeat; cursor:move; display:block; @@ -117,15 +149,15 @@ body.context-editing div.draggable:hover a.context-block-handle { z-index:100; } -body.context-editing div.draggable:hover a.context-block-remove { +body.context-editing .draggable:hover a.context-block-remove { background-position:-40px 0px; cursor:pointer; right:-5px; } -div.context-block-hidden { display:none !important; } + .context-block-hidden { display:none !important; } -div.block div.context-block-empty { + .block .context-block-empty-content { text-align:center; padding:10px; opacity:.5; @@ -136,7 +168,7 @@ div.block div.context-block-empty { /** * Block visibility =================================================== */ -#context-blockform div.context-blockform-selector { +#context-blockform .context-blockform-selector { height:20em; overflow:auto; } @@ -150,7 +182,7 @@ div.block div.context-block-empty { width:50%; } -#context-blockform td.blocks div.label, +#context-blockform td.blocks .label, #context-blockform td.blocks td, #context-blockform td.blocks th { background:#fff; @@ -159,15 +191,15 @@ div.block div.context-block-empty { border-bottom:1px solid #ddd; } - #context-blockform td.blocks div.label { background:#eee; } - #context-blockform td.blocks div.label a { float:right; } + #context-blockform td.blocks .label { background:#eee; } + #context-blockform td.blocks .label a { float:right; } #context-ui-items #context-blockform { font-size:11px; line-height:15px; } -#context-ui-items #context-blockform div.form-checkboxes { +#context-ui-items #context-blockform .form-checkboxes { height:auto; overflow:visible; padding:0px; @@ -175,7 +207,7 @@ div.block div.context-block-empty { border:0px; } -#context-ui-items #context-blockform div.form-item { padding:0px; } +#context-ui-items #context-blockform .form-item { padding:0px; } #context-ui-items #context-blockform label { background:#eee; @@ -193,3 +225,44 @@ div.block div.context-block-empty { #context-blockform .tabledrag-toggle-weight-wrapper { margin-bottom:0; } + +a.context-ui-add-link, a:link.context-ui-add-link, a:visited.context-ui-add-link { + display:block; + width:100%; + text-align:center; + font-size:12px; + color:#fff; + cursor: pointer; + line-height:14px; +} + +.editing-context-label { + position: fixed; + top:70px; + background:#fff; + color:#222; + padding:10px; + font-weight:bold; + opacity: 0.5; + -moz-opacity: 0.5; + filter:alpha(opacity=50); + border:1px solid #ddd; + border-left:0; + border-radius:0 6px 6px 0; +} + +.context-help { + font-size:12px; + font-weight:normal; +} + +.context-editor-title { + font-size:24px; + margin:10px 0px; + padding:0; +} + +.ui-sortable-helper { + max-width: 300px; + max-height: 300px; +} \ No newline at end of file diff --git a/sites/all/modules/context/plugins/context_reaction_block.inc b/sites/all/modules/context/plugins/context_reaction_block.inc index f505e1c810b94f3c2c258be91ef4b551ef099ca6..c14aa98050ee7494b8ed97bc62e9048cfbf7de7c 100644 --- a/sites/all/modules/context/plugins/context_reaction_block.inc +++ b/sites/all/modules/context/plugins/context_reaction_block.inc @@ -39,14 +39,15 @@ class context_reaction_block extends context_reaction { '#suffix' => '</div>', ); foreach ($this->get_blocks() as $block) { - if (!isset($form['selector'][$block->module])) { - $form['selector'][$block->module] = array( + $group = isset($block->context_group) ? $block->context_group : $block->module; + if (!isset($form['selector'][$group])) { + $form['selector'][$group] = array( '#type' => 'checkboxes', - '#title' => $modules[$block->module], + '#title' => isset($block->context_group) ? $block->context_group : $modules[$block->module], '#options' => array(), ); } - $form['selector'][$block->module]['#options'][$block->bid] = check_plain($block->info); + $form['selector'][$group]['#options'][$block->bid] = check_plain($block->info); } ksort($form['selector']); @@ -115,15 +116,16 @@ class context_reaction_block extends context_reaction { $form = array(); drupal_add_library('system', 'ui.droppable'); drupal_add_library('system', 'ui.sortable'); - drupal_add_js(drupal_get_path('module', 'context_ui') .'/json2.js'); - drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); - drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/json2.js'); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/theme/filter.js'); + drupal_add_js(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.css'); // We might be called multiple times so use a static to ensure this is set just once. static $once; if (!isset($once)) { $settings = array( - 'path' => url($_GET['q']), + 'path' => drupal_is_front_page() ? base_path() : url($_GET['q']), 'params' => (object) array_diff_key($_GET, array('q' => '')), 'scriptPlaceholder' => theme('context_block_script_placeholder', array('text' => '')), ); @@ -218,7 +220,31 @@ class context_reaction_block extends context_reaction { // Load all region content assigned via blocks. foreach (array_keys($all_regions) as $region) { if ($this->is_enabled_region($region)) { - $page[$region] = isset($page[$region]) ? array_merge($page[$region], $this->block_get_blocks_by_region($region)) : $this->block_get_blocks_by_region($region); + if ($blocks = $this->block_get_blocks_by_region($region)) { + + // Are the blocks already sorted. + $blocks_sorted = TRUE; + + // If blocks have already been placed in this region (most likely by + // Block module), then merge in blocks from Context. + if (isset($page[$region])) { + $page[$region] = array_merge($page[$region], $blocks); + + // Restore the weights that Block module manufactured + // @see _block_get_renderable_array() + foreach ($page[$region] as &$block) { + if (isset($block['#block']->weight)) { + $block['#weight'] = $block['#block']->weight; + $blocks_sorted = FALSE; + } + } + } + else { + $page[$region] = $blocks; + } + + $page[$region]['#sorted'] = $blocks_sorted; + } } } } @@ -250,8 +276,8 @@ class context_reaction_block extends context_reaction { $requirements = TRUE; drupal_add_library('system', 'ui.droppable'); drupal_add_library('system', 'ui.sortable'); - drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); - drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + drupal_add_js(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.css'); } else { $requirements = FALSE; @@ -272,11 +298,22 @@ class context_reaction_block extends context_reaction { * Add markup for making a block editable. */ protected function editable_block($block) { - if (!empty($block->content['#markup']) || element_children($block->content)) { + if (!empty($block->content)) { $block->content = array( 'content' => $block->content, 'context' => array('#markup' => "<a id='context-block-{$block->module}-{$block->delta}' class='context-block editable edit-{$block->context}'></a>"), ); + //Contextual links are in the wrong spot in the render array once we've nested them + if (isset($block->content['content']['#contextual_links'])) { + $block->content['#contextual_links'] = $block->content['content']['#contextual_links']; + unset($block->content['content']['#contextual_links']); + } + } + else { + // the block alter in context.module should ensure that blocks are never + // empty if the inline editor is present but in the case that they are, + // warn that editing the context is likely to cause this block to be dropped + drupal_set_message(t('The block with delta @delta from module @module is not compatible with the inline editor and will be dropped from the context containing it if you edit contexts here', array('@delta' => $block->delta, '@module' => $block->module)), 'warning'); } return $block; } @@ -285,12 +322,25 @@ class context_reaction_block extends context_reaction { * Add markup for making a region editable. */ protected function editable_region($region, $build) { - if ($this->is_editable_region($region) && (!empty($build) || variable_get('context_reaction_block_all_regions', FALSE))) { + if ($this->is_editable_region($region) && + (!empty($build) || + variable_get('context_reaction_block_all_regions', FALSE) || + context_isset('context_ui', 'context_ui_editor_present')) + ) { global $theme; $regions = system_region_list($theme); $name = isset($regions[$region]) ? $regions[$region] : $region; - $build['context']['#markup'] = "<a class='context-block-region' id='context-block-region-{$region}'>{$name}</a>"; + // The negative weight + sorted will push our region marker to the top of the region + $build['context'] = array( + '#prefix' => "<div class='context-block-region' id='context-block-region-{$region}'>", + '#markup' => "<span class='region-name'>{$name}</span>" . + "<a class='context-ui-add-link'>" . t('Add a block here.') . '</a>', + '#suffix' => '</div>', + '#weight' => -100, + ); + $build['#sorted'] = FALSE; } + return $build; } @@ -324,13 +374,15 @@ class context_reaction_block extends context_reaction { foreach ($contexts as $context) { $options = $this->fetch_from_context($context); if (!empty($options['blocks'])) { - foreach ($options['blocks'] as $block) { - $block = (object) $block; - $block->context = $context->name; - $block->bid = "{$block->module}-{$block->delta}"; - $block->title = isset($info[$block->bid]->title) ? $info[$block->bid]->title : NULL; - $block->cache = isset($info[$block->bid]->cache) ? $info[$block->bid]->cache : DRUPAL_NO_CACHE; - $context_blocks[$block->region][$block->bid] = $block; + foreach ($options['blocks'] as $context_block) { + $bid = "{$context_block['module']}-{$context_block['delta']}"; + if (isset($info[$bid])) { + $block = (object) array_merge((array) $info[$bid], $context_block); + $block->context = $context->name; + $block->title = isset($info[$block->bid]->title) ? $info[$block->bid]->title : NULL; + $block->cache = isset($info[$block->bid]->cache) ? $info[$block->bid]->cache : DRUPAL_NO_CACHE; + $context_blocks[$block->region][$block->bid] = $block; + } } } } @@ -359,7 +411,6 @@ class context_reaction_block extends context_reaction { * needed so the save of the context inline editor does not remove the blocks with no content. */ function is_editable_check($context_blocks) { - $context_ui_editor_present = &drupal_static('context_ui_editor_present', FALSE); foreach ($context_blocks as $r => $blocks) { if (isset($blocks['context_ui-editor'])) { $block = $blocks['context_ui-editor']; @@ -371,7 +422,9 @@ class context_reaction_block extends context_reaction { $array = module_invoke($block->module, 'block_view', $block->delta); drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block); } - $context_ui_editor_present = !empty($array['content']); + if(!empty($array['content'])) { + context_set('context_ui', 'context_ui_editor_present', TRUE); + } break; } } @@ -416,6 +469,7 @@ class context_reaction_block extends context_reaction { */ function get_blocks($region = NULL, $context = NULL, $reset = FALSE) { static $block_info; + $theme_key = variable_get('theme_default', 'garland'); if (!isset($block_info) || $reset) { $block_info = array(); @@ -423,16 +477,29 @@ class context_reaction_block extends context_reaction { $block_info = context_cache_get('block_info'); } if (empty($block_info)) { - $block_info = array(); - foreach (module_implements('block_info') as $module) { - $module_blocks = module_invoke($module, 'block_info'); - if (!empty($module_blocks)) { - foreach ($module_blocks as $delta => $block) { - $block = (object) $block; - $block->module = $module; - $block->delta = $delta; - $block->bid = "{$block->module}-{$block->delta}"; - $block_info[$block->bid] = $block; + if (module_exists('block')) { + $block_blocks = _block_rehash($theme_key); + $block_info = array(); + // Change from numeric keys to module-delta. + foreach ($block_blocks as $block) { + $block = (object) $block; + unset($block->theme, $block->status, $block->weight, $block->region, $block->custom, $block->visibility, $block->pages); + $block->bid = "{$block->module}-{$block->delta}"; + $block_info[$block->bid] = $block; + } + } + else { + $block_info = array(); + foreach (module_implements('block_info') as $module) { + $module_blocks = module_invoke($module, 'block_info'); + if (!empty($module_blocks)) { + foreach ($module_blocks as $delta => $block) { + $block = (object) $block; + $block->module = $module; + $block->delta = $delta; + $block->bid = "{$block->module}-{$block->delta}"; + $block_info[$block->bid] = $block; + } } } } @@ -444,16 +511,16 @@ class context_reaction_block extends context_reaction { // Gather only region info from the database. if (module_exists('block')) { - $theme_key = variable_get('theme_default', 'garland'); $result = db_select('block') - ->fields('block', array('module','weight','delta','region', 'title')) + ->fields('block') ->condition('theme', $theme_key) ->execute(); foreach ($result as $row) { if (isset($block_info["{$row->module}-{$row->delta}"])) { - $block_info["{$row->module}-{$row->delta}"]->weight = $row->weight; - $block_info["{$row->module}-{$row->delta}"]->region = $row->region; - $block_info["{$row->module}-{$row->delta}"]->title = $row->title; + $block_info["{$row->module}-{$row->delta}"] = (object) array_merge((array) $row, (array) $block_info["{$row->module}-{$row->delta}"]); + unset($block_info["{$row->module}-{$row->delta}"]->status); + unset($block_info["{$row->module}-{$row->delta}"]->visibility); + unset($block_info["{$row->module}-{$row->delta}"]->cache); } } } @@ -520,7 +587,7 @@ class context_reaction_block extends context_reaction { * Original author: walidator.info 2009. */ static function _json_decode($json) { - $comment = false; + $comment = FALSE; $out = '$x = '; for ($i=0; $i < strlen($json); $i++) { if (!$comment) { @@ -573,6 +640,11 @@ class context_reaction_block extends context_reaction { if (strpos($param, ',') !== FALSE) { list($bid, $context) = explode(',', $param); list($module, $delta) = explode('-', $bid, 2); + // Check token to make sure user has access to block. + if (empty($_GET['context_token']) || $_GET['context_token'] != drupal_get_token($bid)) { + echo drupal_json_encode(array('status' => 0)); + exit; + } // Ensure $bid is valid. $info = $this->get_blocks(); @@ -585,7 +657,7 @@ class context_reaction_block extends context_reaction { $rendered_blocks = _block_render_blocks(array($block)); // For E_STRICT warning $block = array_shift($rendered_blocks); if (empty($block->content['#markup'])) { - $block->content['#markup'] = "<div class='context-block-empty'>". t('This block appears empty when displayed on this page.') ."</div>"; + $block->content['#markup'] = "<div class='context-block-empty'>" . t('This block appears empty when displayed on this page.') . "</div>"; } $block = $this->editable_block($block); $renderable_block = _block_get_renderable_array(array($block)); // For E_STRICT warning diff --git a/sites/all/modules/context/plugins/context_reaction_block.js b/sites/all/modules/context/plugins/context_reaction_block.js index 6326afdbd96cad7d0055e6855f1cb4603d6041c1..b51e2f3447f9a643e042b3492667ae4696f222fa 100644 --- a/sites/all/modules/context/plugins/context_reaction_block.js +++ b/sites/all/modules/context/plugins/context_reaction_block.js @@ -40,6 +40,9 @@ Drupal.behaviors.contextReactionBlock = {attach: function(context) { return false; }); }); + + // Conceal Section title, subtitle and class + $('div.context-block-browser', context).nextAll('.form-item').hide(); }}; /** @@ -54,7 +57,7 @@ DrupalContextBlockForm = function(blockForm) { var blocks = []; $('tr', $(this)).each(function() { var bid = $(this).attr('id'); - var weight = $(this).find('select').val(); + var weight = $(this).find('select,input').first().val(); blocks.push({'bid' : bid, 'weight' : weight}); }); Drupal.contextBlockForm.state[region] = blocks; @@ -75,6 +78,7 @@ DrupalContextBlockForm = function(blockForm) { $(this).parents('div.form-item').eq(0).show(); } }); + }; // make sure we update the state right before submits, this takes care of an @@ -99,23 +103,46 @@ DrupalContextBlockForm = function(blockForm) { $('td.blocks a', blockForm).each(function() { $(this).click(function() { var region = $(this).attr('href').split('#')[1]; + var base = "context-blockform-region-"+ region; var selected = $("div.context-blockform-selector input:checked"); if (selected.size() > 0) { + var weight_warn = false; + var min_weight_option = -10; + var max_weight_option = 10; + var max_observed_weight = min_weight_option - 1; + $('table#' + base + ' tr').each(function() { + var weight_input_val = $(this).find('select,input').first().val(); + if (+weight_input_val > +max_observed_weight) { + max_observed_weight = weight_input_val; + } + }); + selected.each(function() { // create new block markup var block = document.createElement('tr'); var text = $(this).parents('div.form-item').eq(0).hide().children('label').text(); var select = '<div class="form-item form-type-select"><select class="tabledrag-hide form-select">'; var i; - for (i = -10; i < 10; ++i) { - select += '<option>' + i + '</option>'; + weight_warn = true; + var selected_weight = max_weight_option; + if (max_weight_option >= (1 + +max_observed_weight)) { + selected_weight = ++max_observed_weight; + weight_warn = false; + } + + for (i = min_weight_option; i <= max_weight_option; ++i) { + select += '<option'; + if (i == selected_weight) { + select += ' selected=selected'; + } + select += '>' + i + '</option>'; } select += '</select></div>'; $(block).attr('id', $(this).attr('value')).addClass('draggable'); $(block).html("<td>"+ text + "</td><td>" + select + "</td><td><a href='' class='remove'>X</a></td>"); // add block item to region - var base = "context-blockform-region-"+ region; + //TODO : Fix it so long blocks don't get stuck when added to top regions and dragged towards bottom regions Drupal.tableDrag[base].makeDraggable(block); $('table#'+base).append(block); if ($.cookie('Drupal.tableDrag.showWeight') == 1) { @@ -131,6 +158,9 @@ DrupalContextBlockForm = function(blockForm) { Drupal.contextBlockForm.setState(); $(this).removeAttr('checked'); }); + if (weight_warn) { + alert(Drupal.t('Desired block weight exceeds available weight options, please check weights for blocks before saving')); + } } return false; }); @@ -146,289 +176,326 @@ DrupalContextBlockEditor = function(editor) { this.blocks = {}; this.regions = {}; - // Category selector handler. - // Also set to "Choose a category" option as browsers can retain - // form values from previous page load. - $('select.context-block-browser-categories', editor).change(function() { - var category = $(this).val(); - var params = { - containment: 'document', - revert: true, - dropOnEmpty: true, - placeholder: 'draggable-placeholder', - forcePlaceholderSize: true, - helper: 'clone', - appendTo: 'body', - connectWith: ($.ui.version === '1.6') ? ['.ui-sortable'] : '.ui-sortable' - }; - $('div.category', editor).hide().sortable('destroy'); - $('div.category-'+category, editor).show().sortable(params); - }); - $('select.context-block-browser-categories', editor).val(0).change(); - return this; }; -DrupalContextBlockEditor.prototype.initBlocks = function(blocks) { - var self = this; - this.blocks = blocks; - blocks.each(function() { - $(this).addClass('draggable'); - $(this).prepend($('<a class="context-block-handle"></a>')); - $(this).prepend($('<a class="context-block-remove"></a>').click(function() { - $(this).parents('div.block').eq(0).fadeOut('medium', function() { - $(this).remove(); - self.updateBlocks(); +DrupalContextBlockEditor.prototype = { + initBlocks : function(blocks) { + var self = this; + this.blocks = blocks; + blocks.each(function() { + if($(this).hasClass('context-block-empty')) { + $(this).removeClass('context-block-hidden'); + } + $(this).addClass('draggable'); + $(this).prepend($('<a class="context-block-handle"></a>')); + $(this).prepend($('<a class="context-block-remove"></a>').click(function() { + $(this).parent ('.block').eq(0).fadeOut('medium', function() { + $(this).remove(); + self.updateBlocks(); + }); + return false; + })); + }); + }, + initRegions : function(regions) { + this.regions = regions; + var ref = this; + + $(regions).not('.context-ui-processed') + .each(function(index, el) { + $('.context-ui-add-link', el).click(function(e){ + ref.showBlockBrowser($(this).parent()); + }).addClass('context-ui-processed'); }); - return false; - })); - }); -}; + $('.context-block-browser').hide(); + }, + showBlockBrowser : function(region) { + var toggled = false; + //figure out the id of the context + var activeId = $('.context-editing', this.editor).attr('id').replace('-trigger', ''), + context = $('#' + activeId)[0]; + + this.browser = $('.context-block-browser', context).addClass('active'); + + //add the filter element to the block browser + if (!this.browser.has('input.filter').size()) { + var parent = $('.block-browser-sidebar .filter', this.browser); + var list = $('.blocks', this.browser); + new Drupal.Filter (list, false, '.context-block-addable', parent); + } + //show a dialog for the blocks list + this.browser.show().dialog({ + modal : true, + close : function() { + $(this).dialog('destroy'); + //reshow all the categories + $('.category', this).show(); + $(this).hide().appendTo(context).removeClass('active'); + }, + height: (.8 * $(window).height()), + minHeight:400, + minWidth:680, + width:680 + }); -DrupalContextBlockEditor.prototype.initRegions = function(regions) { - this.regions = regions; -}; + //handle showing / hiding block items when a different category is selected + $('.context-block-browser-categories', this.browser).change(function(e) { + //if no category is selected we want to show all the items + if ($(this).val() == 0) { + $('.category', self.browser).show(); + } else { + $('.category', self.browser).hide(); + $('.category-' + $(this).val(), self.browser).show(); + } + }); -/** - * Update UI to match the current block states. - */ -DrupalContextBlockEditor.prototype.updateBlocks = function() { - var browser = $('div.context-block-browser'); - - // For all enabled blocks, mark corresponding addables as having been added. - $('div.block, div.admin-block').each(function() { - var bid = $(this).attr('id').split('block-')[1]; // Ugh. - $('#context-block-addable-'+bid, browser).draggable('disable').addClass('context-block-added').removeClass('context-block-addable'); - }); - // For all hidden addables with no corresponding blocks, mark as addable. - $('.context-block-item', browser).each(function() { - var bid = $(this).attr('id').split('context-block-addable-')[1]; - if ($('#block-'+bid).size() === 0) { - $(this).draggable('enable').removeClass('context-block-added').addClass('context-block-addable'); + //if we already have the function for a different context, rebind it so we don't get dupes + if(this.addToRegion) { + $('.context-block-addable', this.browser).unbind('click.addToRegion') } - }); - // Mark empty regions. - $(this.regions).each(function() { - if ($('div.block:has(a.context-block)', this).size() > 0) { - $(this).removeClass('context-block-region-empty'); - } - else { - $(this).addClass('context-block-region-empty'); - } - }); -}; + //protected function for adding a clicked block to a region + var self = this; + this.addToRegion = function(e){ + var ui = { + 'item' : $(this).clone(), + 'sender' : $(region) + }; + $(this).parents('.context-block-browser.active').dialog('close'); + $(region).after(ui.item); + self.addBlock(e, ui, this.editor, activeId.replace('context-editable-', '')); + }; -/** - * Live update a region. - */ -DrupalContextBlockEditor.prototype.updateRegion = function(event, ui, region, op) { - switch (op) { - case 'over': - $(region).removeClass('context-block-region-empty'); - break; - case 'out': - if ( - // jQuery UI 1.8 - $('div.draggable-placeholder', region).size() === 1 && - $('div.block:has(a.context-block)', region).size() == 0 - // jQuery UI 1.6 - // $('div.draggable-placeholder', region).size() === 0 && - // $('div.block:has(a.context-block)', region).size() == 1 && - // $('div.block:has(a.context-block)', region).attr('id') == ui.item.attr('id') - ) { - $(region).addClass('context-block-region-empty'); - } - break; - } -}; + $('.context-block-addable', this.browser).bind('click.addToRegion', this.addToRegion); + }, + // Update UI to match the current block states. + updateBlocks : function() { + var browser = $('div.context-block-browser'); -/** - * Remove script elements while dragging & dropping. - */ -DrupalContextBlockEditor.prototype.scriptFix = function(event, ui, editor, context) { - if ($('script', ui.item)) { - var placeholder = $(Drupal.settings.contextBlockEditor.scriptPlaceholder); - var label = $('div.handle label', ui.item).text(); - placeholder.children('strong').html(label); - $('script', ui.item).parent().empty().append(placeholder); - } -}; + // For all enabled blocks, mark corresponding addables as having been added. + $('.block, .admin-block').each(function() { + var bid = $(this).attr('id').split('block-')[1]; // Ugh. + }); + // For all hidden addables with no corresponding blocks, mark as addable. + $('.context-block-item', browser).each(function() { + var bid = $(this).attr('id').split('context-block-addable-')[1]; + }); -/** - * Add a block to a region through an AHAH load of the block contents. - */ -DrupalContextBlockEditor.prototype.addBlock = function(event, ui, editor, context) { - var self = this; - if (ui.item.is('.context-block-addable')) { - var bid = ui.item.attr('id').split('context-block-addable-')[1]; - - // Construct query params for our AJAX block request. - var params = Drupal.settings.contextBlockEditor.params; - params.context_block = bid + ',' + context; - - // Replace item with loading block. - var blockLoading = $('<div class="context-block-item context-block-loading"><span class="icon"></span></div>'); - ui.item.addClass('context-block-added'); - ui.item.after(blockLoading); - ui.sender.append(ui.item); - - $.getJSON(Drupal.settings.contextBlockEditor.path, params, function(data) { - if (data.status) { - var newBlock = $(data.block); - if ($('script', newBlock)) { - $('script', newBlock).remove(); - } - blockLoading.fadeOut(function() { - $(this).replaceWith(newBlock); - self.initBlocks(newBlock); - self.updateBlocks(); - Drupal.attachBehaviors(); - }); + // Mark empty regions. + $(this.regions).each(function() { + if ($('.block:has(a.context-block)', this).size() > 0) { + $(this).removeClass('context-block-region-empty'); } else { - blockLoading.fadeOut(function() { $(this).remove(); }); + $(this).addClass('context-block-region-empty'); } }); - } - else if (ui.item.is(':has(a.context-block)')) { - self.updateBlocks(); - } -}; + }, + // Live update a region + updateRegion : function(event, ui, region, op) { + switch (op) { + case 'over': + $(region).removeClass('context-block-region-empty'); + break; + case 'out': + if ( + // jQuery UI 1.8 + $('.draggable-placeholder', region).size() === 1 && + $('.block:has(a.context-block)', region).size() == 0 + ) { + $(region).addClass('context-block-region-empty'); + } + break; + } + }, + // Remove script elements while dragging & dropping. + scriptFix : function(event, ui, editor, context) { + if ($('script', ui.item)) { + var placeholder = $(Drupal.settings.contextBlockEditor.scriptPlaceholder); + var label = $('div.handle label', ui.item).text(); + placeholder.children('strong').html(label); + $('script', ui.item).parent().empty().append(placeholder); + } + }, + // Add a block to a region through an AJAX load of the block contents. + addBlock : function(event, ui, editor, context) { + var self = this; + if (ui.item.is('.context-block-addable')) { + var bid = ui.item.attr('id').split('context-block-addable-')[1]; + + // Construct query params for our AJAX block request. + var params = Drupal.settings.contextBlockEditor.params; + params.context_block = bid + ',' + context; + if (!Drupal.settings.contextBlockEditor.block_tokens || !Drupal.settings.contextBlockEditor.block_tokens[bid]) { + alert(Drupal.t('An error occurred trying to retrieve block content. Please contact a site administer.')); + return; + } + params.context_token = Drupal.settings.contextBlockEditor.block_tokens[bid]; -/** - * Update form hidden field with JSON representation of current block visibility states. - */ -DrupalContextBlockEditor.prototype.setState = function() { - var self = this; - - $(this.regions).each(function() { - var region = $('a.context-block-region', this).attr('id').split('context-block-region-')[1]; - var blocks = []; - $('a.context-block', $(this)).each(function() { - if ($(this).attr('class').indexOf('edit-') != -1) { - var bid = $(this).attr('id').split('context-block-')[1]; - var context = $(this).attr('class').split('edit-')[1].split(' ')[0]; - context = context ? context : 0; - var block = {'bid': bid, 'context': context}; - blocks.push(block); - } - }); - self.state[region] = blocks; - }); + // Replace item with loading block. + //ui.sender.append(ui.item); - // Serialize here and set form element value. - $('input.context-block-editor-state', this.editor).val(JSON.stringify(this.state)); -}; + var blockLoading = $('<div class="context-block-item context-block-loading"><span class="icon"></span></div>'); + ui.item.addClass('context-block-added'); + ui.item.after(blockLoading); -/** - * Disable text selection. - */ -DrupalContextBlockEditor.prototype.disableTextSelect = function() { - if ($.browser.safari) { - $('div.block:has(a.context-block):not(:has(input,textarea))').css('WebkitUserSelect','none'); - } - else if ($.browser.mozilla) { - $('div.block:has(a.context-block):not(:has(input,textarea))').css('MozUserSelect','none'); - } - else if ($.browser.msie) { - $('div.block:has(a.context-block):not(:has(input,textarea))').bind('selectstart.contextBlockEditor', function() { return false; }); - } - else { - $(this).bind('mousedown.contextBlockEditor', function() { return false; }); - } -}; -/** - * Enable text selection. - */ -DrupalContextBlockEditor.prototype.enableTextSelect = function() { - if ($.browser.safari) { - $('*').css('WebkitUserSelect',''); - } - else if ($.browser.mozilla) { - $('*').css('MozUserSelect',''); - } - else if ($.browser.msie) { - $('*').unbind('selectstart.contextBlockEditor'); - } - else { - $(this).unbind('mousedown.contextBlockEditor'); - } -}; + $.getJSON(Drupal.settings.contextBlockEditor.path, params, function(data) { + if (data.status) { + var newBlock = $(data.block); + if ($('script', newBlock)) { + $('script', newBlock).remove(); + } + blockLoading.fadeOut(function() { + $(this).replaceWith(newBlock); + self.initBlocks(newBlock); + self.updateBlocks(); + Drupal.attachBehaviors(newBlock); + }); + } + else { + blockLoading.fadeOut(function() { $(this).remove(); }); + } + }); + } + else if (ui.item.is(':has(a.context-block)')) { + self.updateBlocks(); + } + }, + // Update form hidden field with JSON representation of current block visibility states. + setState : function() { + var self = this; -/** - * Start editing. Attach handlers, begin draggable/sortables. - */ -DrupalContextBlockEditor.prototype.editStart = function(editor, context) { - var self = this; - - // This is redundant to the start handler found in context_ui.js. - // However it's necessary that we trigger this class addition before - // we call .sortable() as the empty regions need to be visible. - $(document.body).addClass('context-editing'); - this.editor.addClass('context-editing'); - - this.disableTextSelect(); - this.initBlocks($('div.block:has(a.context-block.edit-'+context+')')); - this.initRegions($('a.context-block-region').parent()); - this.updateBlocks(); - - // First pass, enable sortables on all regions. - $(this.regions).each(function() { - var region = $(this); - var params = { - containment: 'document', - revert: true, - dropOnEmpty: true, - placeholder: 'draggable-placeholder', - forcePlaceholderSize: true, - items: '> div.block:has(a.context-block.editable)', - handle: 'a.context-block-handle', - start: function(event, ui) { self.scriptFix(event, ui, editor, context); }, - stop: function(event, ui) { self.addBlock(event, ui, editor, context); }, - receive: function(event, ui) { self.addBlock(event, ui, editor, context); }, - over: function(event, ui) { self.updateRegion(event, ui, region, 'over'); }, - out: function(event, ui) { self.updateRegion(event, ui, region, 'out'); } - }; - region.sortable(params); - }); + $(this.regions).each(function() { + var region = $('.context-block-region', this).attr('id').split('context-block-region-')[1]; + var blocks = []; + $('a.context-block', $(this)).each(function() { + if ($(this).attr('class').indexOf('edit-') != -1) { + var bid = $(this).attr('id').split('context-block-')[1]; + var context = $(this).attr('class').split('edit-')[1].split(' ')[0]; + context = context ? context : 0; + var block = {'bid': bid, 'context': context}; + blocks.push(block); + } + }); + self.state[region] = blocks; + }); + // Serialize here and set form element value. + $('input.context-block-editor-state', this.editor).val(JSON.stringify(this.state)); + }, + //Disable text selection. + disableTextSelect : function() { + if ($.browser.safari) { + $('.block:has(a.context-block):not(:has(input,textarea))').css('WebkitUserSelect','none'); + } + else if ($.browser.mozilla) { + $('.block:has(a.context-block):not(:has(input,textarea))').css('MozUserSelect','none'); + } + else if ($.browser.msie) { + $('.block:has(a.context-block):not(:has(input,textarea))').bind('selectstart.contextBlockEditor', function() { return false; }); + } + else { + $(this).bind('mousedown.contextBlockEditor', function() { return false; }); + } + }, + //Enable text selection. + enableTextSelect : function() { + if ($.browser.safari) { + $('*').css('WebkitUserSelect',''); + } + else if ($.browser.mozilla) { + $('*').css('MozUserSelect',''); + } + else if ($.browser.msie) { + $('*').unbind('selectstart.contextBlockEditor'); + } + else { + $(this).unbind('mousedown.contextBlockEditor'); + } + }, + // Start editing. Attach handlers, begin draggable/sortables. + editStart : function(editor, context) { + var self = this; + // This is redundant to the start handler found in context_ui.js. + // However it's necessary that we trigger this class addition before + // we call .sortable() as the empty regions need to be visible. + $(document.body).addClass('context-editing'); + this.editor.addClass('context-editing'); + this.disableTextSelect(); + this.initBlocks($('.block:has(a.context-block.edit-'+context+')')); + this.initRegions($('.context-block-region').parent()); + this.updateBlocks(); + + $('a.context_ui_dialog-stop').hide(); + + $('.editing-context-label').remove(); + var label = $('#context-editable-trigger-'+context+' .label').text(); + label = Drupal.t('Now Editing: ') + label; + editor.parent().parent() + .prepend('<div class="editing-context-label">'+ label + '</div>'); + + // First pass, enable sortables on all regions. + $(this.regions).each(function() { + var region = $(this); + var params = { + revert: true, + dropOnEmpty: true, + placeholder: 'draggable-placeholder', + forcePlaceholderSize: true, + items: '> .block:has(a.context-block.editable)', + handle: 'a.context-block-handle', + start: function(event, ui) { self.scriptFix(event, ui, editor, context); }, + stop: function(event, ui) { self.addBlock(event, ui, editor, context); }, + receive: function(event, ui) { self.addBlock(event, ui, editor, context); }, + over: function(event, ui) { self.updateRegion(event, ui, region, 'over'); }, + out: function(event, ui) { self.updateRegion(event, ui, region, 'out'); }, + cursorAt: {left: 300, top: 0} + }; + region.sortable(params); + }); - // Second pass, hook up all regions via connectWith to each other. - $(this.regions).each(function() { - $(this).sortable('option', 'connectWith', ['.ui-sortable']); - }); + // Second pass, hook up all regions via connectWith to each other. + $(this.regions).each(function() { + $(this).sortable('option', 'connectWith', ['.ui-sortable']); + }); - // Terrible, terrible workaround for parentoffset issue in Safari. - // The proper fix for this issue has been committed to jQuery UI, but was - // not included in the 1.6 release. Therefore, we do a browser agent hack - // to ensure that Safari users are covered by the offset fix found here: - // http://dev.jqueryui.com/changeset/2073. - if ($.ui.version === '1.6' && $.browser.safari) { - $.browser.mozilla = true; - } -}; + // Terrible, terrible workaround for parentoffset issue in Safari. + // The proper fix for this issue has been committed to jQuery UI, but was + // not included in the 1.6 release. Therefore, we do a browser agent hack + // to ensure that Safari users are covered by the offset fix found here: + // http://dev.jqueryui.com/changeset/2073. + if ($.ui.version === '1.6' && $.browser.safari) { + $.browser.mozilla = true; + } + }, + // Finish editing. Remove handlers. + editFinish : function() { + this.editor.removeClass('context-editing'); + this.enableTextSelect(); + + $('.editing-context-label').remove(); + + // Remove UI elements. + $(this.blocks).each(function() { + $('a.context-block-handle, a.context-block-remove', this).remove(); + if($(this).hasClass('context-block-empty')) { + $(this).addClass('context-block-hidden'); + } + $(this).removeClass('draggable'); + }); -/** - * Finish editing. Remove handlers. - */ -DrupalContextBlockEditor.prototype.editFinish = function() { - this.editor.removeClass('context-editing'); - this.enableTextSelect(); - - // Remove UI elements. - $(this.blocks).each(function() { - $('a.context-block-handle, a.context-block-remove', this).remove(); - $(this).removeClass('draggable'); - }); - this.regions.sortable('destroy'); + $('a.context_ui_dialog-stop').show(); - this.setState(); + this.regions.sortable('destroy'); - // Unhack the user agent. - if ($.ui.version === '1.6' && $.browser.safari) { - $.browser.mozilla = false; + this.setState(); + + // Unhack the user agent. + if ($.ui.version === '1.6' && $.browser.safari) { + $.browser.mozilla = false; + } } -}; +}; //End of DrupalContextBlockEditor prototype })(jQuery); diff --git a/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc b/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc index caa1cad2f7ebaa8028a2c33315d5eaa7780468bd..17efb0a24fb87b2867d4546c94b06b9eedce821e 100644 --- a/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc +++ b/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc @@ -9,7 +9,7 @@ class context_reaction_breadcrumb extends context_reaction_menu { */ function execute(&$vars = NULL) { if ($active_paths = $this->get_active_paths()) { - $breadcrumb = array(l(t('Home'), '<front>', array('purl' =>array('disabled' => TRUE)))); + $breadcrumb = array(l(t('Home'), '<front>', array('purl' => array('disabled' => TRUE)))); foreach ($active_paths as $path) { $result = db_select('menu_links') ->fields('menu_links', array('p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8')) diff --git a/sites/all/modules/context/plugins/context_reaction_css_injector.inc b/sites/all/modules/context/plugins/context_reaction_css_injector.inc index 5fedb795ef27a13cf1005cc28d9e2607f92c64e8..0ff790ffb771457189c9766b15461868e3ec2382 100644 --- a/sites/all/modules/context/plugins/context_reaction_css_injector.inc +++ b/sites/all/modules/context/plugins/context_reaction_css_injector.inc @@ -22,12 +22,12 @@ class context_reaction_css_injector extends context_reaction { $contexts = $this->get_contexts(); foreach ($contexts as $context) { if (!empty($context->reactions[$this->plugin])) { - foreach ($context->reactions[$this->plugin] as $crid) { - if ($css_rule = _css_injector_load_rule($crid)) { - drupal_add_css(file_create_path($css_rule['file_path']), 'module', $css_rule['media'], $css_rule['preprocess']); + foreach ($context->reactions[$this->plugin] as $crid => $enabled) { + if ($enabled && $css_rule = _css_injector_load_rule($crid)) { + drupal_add_css(_css_injector_rule_uri($crid), 'module', $css_rule['media'], $css_rule['preprocess']); } } } } } -} \ No newline at end of file +} diff --git a/sites/all/modules/context/plugins/context_reaction_menu.inc b/sites/all/modules/context/plugins/context_reaction_menu.inc index d285717112c6ab60751a1726bf487dedfc3f0e1e..e51143bafe2788579efd596c7ae8c2581068af62 100644 --- a/sites/all/modules/context/plugins/context_reaction_menu.inc +++ b/sites/all/modules/context/plugins/context_reaction_menu.inc @@ -27,7 +27,7 @@ class context_reaction_menu extends context_reaction { } unset($menus[$key]); } - array_unshift($menus, "-- ". t('None') ." --"); + array_unshift($menus, "-- " . t('None') . " --"); } else { $menus = array(); @@ -98,7 +98,7 @@ class context_reaction_menu extends context_reaction { $link['attributes']['class'][] = 'active'; if (strpos(' active', $key) === FALSE) { - $new_links[$key .' active'] = $link; + $new_links[$key . ' active'] = $link; } } else { @@ -116,6 +116,7 @@ class context_reaction_menu extends context_reaction { function menu_navigation_links($menu_name, $level = 0) { // Retrieve original path so we can repair it after our hack. $original_path = $_GET['q']; + $original_menu_trail = drupal_static('menu_set_active_trail'); // Retrieve the first active menu path found. if ($active_paths = $this->get_active_paths()) { @@ -126,10 +127,16 @@ class context_reaction_menu extends context_reaction { } // Build the links requested - $links = menu_navigation_links($menu_name, $level); + if (module_exists('i18n_menu')) { + $links = i18n_menu_navigation_links($menu_name, $level); + } else { + $links = menu_navigation_links($menu_name, $level); + } // Repair and get out menu_set_active_item($original_path); + $repair_menu_trail = &drupal_static('menu_set_active_trail'); + $repair_menu_trail = $original_menu_trail; return $links; } } diff --git a/sites/all/modules/context/plugins/context_reaction_region.inc b/sites/all/modules/context/plugins/context_reaction_region.inc new file mode 100644 index 0000000000000000000000000000000000000000..c4f0fd7c3bbb1c149df92b2ebf47fa29e4830a3b --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_region.inc @@ -0,0 +1,46 @@ +<?php + +class context_reaction_region extends context_reaction { + + function editor_form($context) { + } + + function options_form($context) { + $values = $this->fetch_from_context($context); + $form = array(); + foreach (list_themes() as $theme) { + if ($theme->status) { + $regions = system_region_list($theme->name); + $default = isset($values[$theme->name]) ? $values[$theme->name]['disable'] : array(); + + $form[$theme->name] = array( + '#type' => 'fieldset', + '#title' => "Disable Regions in {$theme->name} Theme", + '#collapsible' => TRUE, + '#collapsed' => !array_reduce($default, create_function('$a, $b', 'return $a || $b;')), + ); + $form[$theme->name]['disable'] = array( + '#type' => 'checkboxes', + '#title' => t("Disable the following"), + '#options' => $regions, + '#default_value' => $default, + ); + } + } + return $form; + } + + function execute(&$page) { + global $theme; + foreach ($this->get_contexts() as $k => $v) { + if (isset($v->reactions[$this->plugin][$theme])) { + $regions = $v->reactions[$this->plugin][$theme]['disable']; + foreach ($regions as $region => $disable) { + if ($disable && isset($page[$region])) { + unset($page[$region]); + } + } + } + } + } +} diff --git a/sites/all/modules/context/tests/context.reactions.test b/sites/all/modules/context/tests/context.reactions.test index 42bcfa6f88b514085d4a216da246128cae2a2b3e..fab54e02153076da2899551f54214569f0208104 100644 --- a/sites/all/modules/context/tests/context.reactions.test +++ b/sites/all/modules/context/tests/context.reactions.test @@ -138,7 +138,7 @@ class ContextReactionMenuTest extends DrupalWebTestCase { $output = $this->drupalGet('user'); $url = url('node/add'); - $active = $this->xpath('//li[contains(@class, "active")]/a[@href="'. $url .'"]'); + $active = $this->xpath('//li[contains(@class, "active")]/a[@href="' . $url . '"]'); $this->assertTrue(!empty($active), t('Active menu item found.')); // Cleanup @@ -227,3 +227,56 @@ class ContextReactionThemeHtmlTest extends DrupalWebTestCase { context_delete($context); } } + +class ContextReactionRegionTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: Region', + 'description' => 'Test Region disable reaction.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer nodes', + 'administer site configuration' + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + theme_enable(array('bartik')); + variable_set('theme_default', 'bartik'); + global $theme; + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('sitewide' => array('values' => array(1))); + $context->reactions = array( + 'block' => array( + 'blocks' => array( + 'user-online' => array( + 'module' => 'user', + 'delta' => 'online', + 'region' => 'sidebar_first', + 'weight' => '-10', + ), + ), + ), + 'region' => array('bartik' => array('disable' => array('sidebar_first' => 'sidebar_first'))) + ); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $output = $this->drupalGet('node'); + $this->assertNoText("Who's online"); + + // Cleanup + context_delete($context); + } +} diff --git a/sites/all/modules/context/tests/context.test b/sites/all/modules/context/tests/context.test index 8b7c9d5a7cc937db58275a5e7ef8d9e641de28e2..5061d65abfd0854632152a0f297de9c51107dfba 100644 --- a/sites/all/modules/context/tests/context.test +++ b/sites/all/modules/context/tests/context.test @@ -61,7 +61,7 @@ class ContextUnitTest extends DrupalWebTestCase { $this->assertIdentical(true, (context_get($val, $val2) == $val2), 'Namespace and attribute get successful.'); $this->assertIdentical(true, context_exists($val, $val2), 'Namespace and attribute exists.'); } - else if (in_array($type2, array('array', 'object'))) { + elseif (in_array($type2, array('array', 'object'))) { $this->assertIdentical(true, (context_get($val) == $val2), 'Namespace and attribute get successful.'); $this->assertIdentical(true, context_exists($val), 'Namespace and attribute exists.'); } diff --git a/sites/all/modules/context/theme/context-block-browser.tpl.php b/sites/all/modules/context/theme/context-block-browser.tpl.php index 2359951a1d21caa753ff27288a3eccf4b6f8ed03..69e8a150f62eed232e57ea029ece89a29969885f 100644 --- a/sites/all/modules/context/theme/context-block-browser.tpl.php +++ b/sites/all/modules/context/theme/context-block-browser.tpl.php @@ -1,17 +1,21 @@ <div class='context-block-browser clearfix'> + <div class="blocks"> + <?php foreach ($blocks as $module => $module_blocks): ?> - <div class='categories'><?php print render($categories) ?></div> + <?php if (!empty($module_blocks)): ?> + <div class='category category-<?php print $module ?> clearfix'> + <div class="category-label"><?php print $module; ?></div> + <?php foreach ($module_blocks as $block): ?> + <?php print theme('context_block_browser_item', array('block' => $block)); ?> + <?php endforeach; ?> + </div> + <?php endif; ?> - <?php foreach ($blocks as $module => $module_blocks): ?> - - <?php if (!empty($module_blocks)): ?> - <div class='category category-<?php print $module ?> clearfix'> - <?php foreach ($module_blocks as $block): ?> - <?php print theme('context_block_browser_item', array('block' => $block)); ?> <?php endforeach; ?> </div> - <?php endif; ?> - - <?php endforeach; ?> - + <div class="block-browser-sidebar"> + <div class='categories'><?php print render($categories); ?></div> + <div class='filter'><?php print render($filter_label); ?></div> + <?php print render($help_text); ?> + </div> </div> diff --git a/sites/all/modules/context/theme/context_reaction_block.theme.inc b/sites/all/modules/context/theme/context_reaction_block.theme.inc index aeabda930e16b9474be765a2f8dc27e13a591d36..c103bab0a0093a27184187414a87849c3f1e8a22 100644 --- a/sites/all/modules/context/theme/context_reaction_block.theme.inc +++ b/sites/all/modules/context/theme/context_reaction_block.theme.inc @@ -27,8 +27,8 @@ function theme_context_block_regions_form($vars) { // Add draggable weights drupal_add_js('misc/tableheader.js'); - drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); - drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + drupal_add_js(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') . '/plugins/context_reaction_block.css'); $output = ''; @@ -42,7 +42,7 @@ function theme_context_block_regions_form($vars) { foreach (element_children($form[$region]) as $id) { $form[$region][$id]['weight']['#attributes'] = array('class' => array('tabledrag-hide')); $label = $form[$region][$id]['#value']; - $remove = l('X', $_GET['q'], array('fragment' => 'remove', 'attributes' => array('class' => array('remove')))); + $remove = l(t('X'), $_GET['q'], array('fragment' => 'remove', 'attributes' => array('class' => array('remove')))); $rows[] = array( 'data' => array($label, drupal_render($form[$region][$id]['weight']), $remove), 'class' => array('draggable'), @@ -50,7 +50,7 @@ function theme_context_block_regions_form($vars) { ); } $output .= "<div class='label context-blockform-regionlabel-{$region}'>"; - $output .= l('+ '. t('Add'), $_GET['q'], array('fragment' => $region, 'attributes' => array('class' => array('add-block')))); + $output .= l(t('+') . ' ' . t('Add'), $_GET['q'], array('fragment' => $region, 'attributes' => array('class' => array('add-block')))); $output .= $form[$region]['#title']; $output .= "</div>"; $output .= theme('table', array('rows' => $rows, 'attributes' => $attr)); @@ -72,7 +72,7 @@ function theme_context_block_script_placeholder($text = '') { function template_preprocess_context_block_browser(&$vars) { $categories = array( '#type' => 'select', - '#options' => array(0 => '<'. t('Choose a category') .'>'), + '#options' => array(0 => '<' . t('All Categories') . '>'), '#attributes' => array('class' => array('context-block-browser-categories')), '#value' => 0, '#size' => 1, @@ -85,20 +85,46 @@ function template_preprocess_context_block_browser(&$vars) { $blocks = array(); // Group blocks by module. foreach ($vars['blocks'] as $block) { - if (!isset($categories[$block->module])) { + $group = isset($block->context_group) ? $block->context_group : $block->module; + // Normalize the $group, borrowed from drupal_html_id + $group = strtr(drupal_strtolower($group), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); + if (!isset($categories[$group])) { $info = system_get_info('module', $block->module); - $categories['#options'][$block->module] = !empty($info['name']) ? $info['name'] : $block->module; + $title = isset($block->context_group) ? $block->context_group : (!empty($info['name']) ? $info['name'] : $block->module); + $categories['#options'][$group] = $title; } - $blocks[$block->module][$block->bid] = $block; // Don't call theme('context_block_browser_item') to allow others to alter. + $blocks[$group][$block->bid] = $block; // Don't call theme('context_block_browser_item') to allow others to alter. } + + //add help text to tell people how to use the block browser + $help_text = array( + '#prefix' => '<div class="context_ui-help-text">', + '#markup' => t('To add a block to the current region, simply click on the block. You may use the category filter to filter by + block type or the search filter to find the block that you wish to add.'), + '#suffix' => '</div>', + ); + + $filter_label = array( + '#prefix' => '<div class="filter-label">', + '#markup' => t('Search Filter'), + '#suffix' => '</div>', + ); + $vars['categories'] = $categories; // Don't call theme('select') here to allow further preprocesses to alter the element. $vars['blocks'] = $blocks; + $vars['help_text'] = $help_text; + $vars['filter_label'] = $filter_label; } /** * Preprocessor for theme('context_block_browser_item'). */ function template_preprocess_context_block_browser_item(&$vars) { + static $added = array(); $vars['bid'] = $vars['block']->bid; $vars['info'] = check_plain($vars['block']->info); + if (empty($added[$vars['bid']])) { + drupal_add_js(array('contextBlockEditor' => array('block_tokens' => array($vars['bid'] => drupal_get_token($vars['bid'])))), 'setting'); + $added[$vars['bid']] = TRUE; + } } diff --git a/sites/all/modules/ctools/API.txt b/sites/all/modules/ctools/API.txt index 2df7e22ce830b903bb72ebba86413af28e8b998e..e0441d620515fd484f4583a03486a2296756005c 100644 --- a/sites/all/modules/ctools/API.txt +++ b/sites/all/modules/ctools/API.txt @@ -1,8 +1,12 @@ -Current API Version: 2.0.5 +Current API Version: 2.0.8 Please note that the API version is an internal number and does not match release numbers. It is entirely possible that releases will not increase the API version number, and increasing this number too often would burden contrib module maintainers who need to keep up with API changes. This file contains a log of changes to the API. +API Version 2.0.8 + Introduce ctools_class_add(). + Introduce ctools_class_remove(). + API Version 2.0.7 All ctools object cache database functions can now accept session_id as an optional argument to facilitate using non-session id keys. diff --git a/sites/all/modules/ctools/bulk_export/bulk_export.info b/sites/all/modules/ctools/bulk_export/bulk_export.info index 6f57712ddf51dddf7259c10d165fad96052b25c0..d27ea9abaa65f0eda2a1d05864037e46bb20f957 100644 --- a/sites/all/modules/ctools/bulk_export/bulk_export.info +++ b/sites/all/modules/ctools/bulk_export/bulk_export.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = ctools package = Chaos tool suite -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/css/button.css b/sites/all/modules/ctools/css/button.css index 91a6044915e34d0b38d17d5c9e893cc0a667ec37..15e484be3cb6676643f113f470ae6278d1e9d116 100644 --- a/sites/all/modules/ctools/css/button.css +++ b/sites/all/modules/ctools/css/button.css @@ -1,4 +1,3 @@ -/* $Id$ */ .ctools-button-processed { border-style: solid; @@ -29,4 +28,4 @@ .ctools-button li a { padding-left: 12px; padding-right: 12px; -} \ No newline at end of file +} diff --git a/sites/all/modules/ctools/css/dropbutton.css b/sites/all/modules/ctools/css/dropbutton.css index 7825111aec7fc72382a6379a8bdccedfaac06f48..5e3ea242d00283a0a3a252add97f24dabc00744f 100644 --- a/sites/all/modules/ctools/css/dropbutton.css +++ b/sites/all/modules/ctools/css/dropbutton.css @@ -1,4 +1,3 @@ -/* $Id$ */ .ctools-dropbutton-processed { padding-right: 18px; diff --git a/sites/all/modules/ctools/ctools.api.php b/sites/all/modules/ctools/ctools.api.php index 1aef57cf6e9daa1e9e18eec81d43c1ce9d1a0a90..a7ab78395a8acca71b53cfd495b407855cdb7ffd 100644 --- a/sites/all/modules/ctools/ctools.api.php +++ b/sites/all/modules/ctools/ctools.api.php @@ -202,6 +202,67 @@ function hook_ctools_content_subtype_alter($subtype, $plugin) { $subtype['render last'] = TRUE; } +/** + * Alter the definition of an entity context plugin. + * + * @param array $plugin + * An associative array defining a plugin. + * @param array $entity + * The entity info array of a specific entity type. + * @param string $plugin_id + * The plugin ID, in the format NAME:KEY. + */ +function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { + ctools_include('context'); + switch ($plugin_id) { + case 'entity_id:taxonomy_term': + $plugin['no ui'] = TRUE; + case 'entity:user': + $plugin = ctools_get_context('user'); + unset($plugin['no ui']); + unset($plugin['no required context ui']); + break; + } +} + +/** + * Alter the definition of entity context plugins. + * + * @param array $plugins + * An associative array of plugin definitions, keyed by plugin ID. + * + * @see hook_ctools_entity_context_alter() + */ +function hook_ctools_entity_contexts_alter(&$plugins) { + $plugins['entity_id:taxonomy_term']['no ui'] = TRUE; +} + +/** + * Change cleanstring settings. + * + * @param array $settings + * An associative array of cleanstring settings. + * + * @see ctools_cleanstring() + */ +function hook_ctools_cleanstring_alter(&$settings) { + // Convert all strings to lower case. + $settings['lower case'] = TRUE; +} + +/** + * Change cleanstring settings for a specific clean ID. + * + * @param array $settings + * An associative array of cleanstring settings. + * + * @see ctools_cleanstring() + */ +function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) { + // Convert all strings to lower case. + $settings['lower case'] = TRUE; +} + /** * @} End of "addtogroup hooks". */ diff --git a/sites/all/modules/ctools/ctools.info b/sites/all/modules/ctools/ctools.info index c3e56fb3f74d1d57205ece746ccbb33b6e8bcfde..8a7b0b60795d91d20b835253f8ed97d7faf8b4c9 100644 --- a/sites/all/modules/ctools/ctools.info +++ b/sites/all/modules/ctools/ctools.info @@ -6,9 +6,9 @@ files[] = includes/context.inc files[] = includes/math-expr.inc files[] = includes/stylizer.inc -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/ctools.install b/sites/all/modules/ctools/ctools.install index 4a6f55c74ba028b7025808dd246cc5f8fe947247..1f961f15bca9cc40d0f0f6c11c0fb80856918c5e 100644 --- a/sites/all/modules/ctools/ctools.install +++ b/sites/all/modules/ctools/ctools.install @@ -26,7 +26,7 @@ function ctools_requirements($phase) { } if (!function_exists('error_get_last')) { - $requirements['ctools_php_52']['title'] = t('CTools PHP requirements'); + $requirements['ctools_php_52']['title'] = t('CTools PHP requirements'); $requirements['ctools_php_52']['description'] = t('CTools requires certain features only available in PHP 5.2.0 or higher.'); $requirements['ctools_php_52']['severity'] = REQUIREMENT_WARNING; $requirements['ctools_php_52']['value'] = t('PHP !version', array('!version' => phpversion())); @@ -52,6 +52,14 @@ function ctools_schema_2() { // update the 'name' field to be 128 bytes long: $schema['ctools_object_cache']['fields']['name']['length'] = 128; + // Update the 'data' field to be type 'blob'. + $schema['ctools_object_cache']['fields']['data'] = array( + 'type' => 'blob', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ); + // DO NOT MODIFY THIS TABLE -- this definition is used to create the table. // Changes to this table must be made in schema_3 or higher. $schema['ctools_css_cache'] = array( @@ -194,4 +202,18 @@ function ctools_update_6007() { return $ret; } - +/** + * ctools_object_cache needs to be defined as a blob. + */ +function ctools_update_6008() { + db_delete('ctools_object_cache') + ->execute(); + + db_change_field('ctools_object_cache', 'data', 'data', array( + 'type' => 'blob', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ) + ); +} diff --git a/sites/all/modules/ctools/ctools.module b/sites/all/modules/ctools/ctools.module index 7dfc989b2a778290b7f59edbbf37934ef7f7d2e1..f014e74d0fc1f3f35f4ef2c496bce65c1fca1d0e 100644 --- a/sites/all/modules/ctools/ctools.module +++ b/sites/all/modules/ctools/ctools.module @@ -367,6 +367,52 @@ function ctools_set_no_blocks($blocks = FALSE) { $status = $blocks; } +/** + * Add an array of classes to the body. + * + * @param mixed $classes + * A string or an array of class strings to add. + * @param string $hook + * The theme hook to add the class to. The default is 'html' which will + * affect the body tag. + */ +function ctools_class_add($classes, $hook = 'html') { + if (!is_array($classes)) { + $classes = array($classes); + } + + $static = &drupal_static('ctools_process_classes', array()); + if (!isset($static[$hook]['add'])) { + $static[$hook]['add'] = array(); + } + foreach ($classes as $class) { + $static[$hook]['add'][] = $class; + } +} + +/** + * Remove an array of classes from the body. + * + * @param mixed $classes + * A string or an array of class strings to remove. + * @param string $hook + * The theme hook to remove the class from. The default is 'html' which will + * affect the body tag. + */ +function ctools_class_remove($classes, $hook = 'html') { + if (!is_array($classes)) { + $classes = array($classes); + } + + $static = &drupal_static('ctools_process_classes', array()); + if (!isset($static[$hook]['remove'])) { + $static[$hook]['remove'] = array(); + } + foreach ($classes as $class) { + $static[$hook]['remove'][] = $class; + } +} + // ----------------------------------------------------------------------- // Drupal core hooks @@ -574,6 +620,36 @@ function ctools_page_token_processing($children, $elements) { return $children; } +/** + * Implements hook_process(). + * + * Add and remove CSS classes from the variables array. We use process so that + * we alter anything added in the preprocess hooks. + */ +function ctools_process(&$variables, $hook) { + if (!isset($variables['classes'])) { + return; + } + + $classes = drupal_static('ctools_process_classes', array()); + + // Process the classses to add. + if (!empty($classes[$hook]['add'])) { + $add_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['add']); + $variables['classes_array'] = array_unique(array_merge($variables['classes_array'], $add_classes)); + } + + // Process the classes to remove. + if (!empty($classes[$hook]['remove'])) { + $remove_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['remove']); + $variables['classes_array'] = array_diff($variables['classes_array'], $remove_classes); + } + + // Since this runs after template_process(), we need to re-implode the + // classes array. + $variables['classes'] = implode(' ', $variables['classes_array']); +} + // ----------------------------------------------------------------------- // Menu callbacks that must be in the .module file. @@ -698,22 +774,6 @@ function ctools_export_ui_task_access($plugin_name, $op, $item = NULL) { return FALSE; } -/** - * Cache callback on behalf of ctools_export_ui. - */ -function ctools_export_ui_context_cache_get($plugin_name, $key) { - dsm('should not be called!'); - return; -} - -/** - * Cache callback on behalf of ctools_export_ui. - */ -function ctools_export_ui_context_cache_set($plugin_name, $key, $item) { - dsm('should not be called!'); - return; -} - /** * Callback for access control ajax form on behalf of export ui. * @@ -815,6 +875,9 @@ function ctools_ajax_theme_callback() { } } +/** + * Implements hook_ctools_entity_context_alter(). + */ function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { ctools_include('context'); switch ($plugin_id) { diff --git a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 04c92c349310e2a5aa0324cbad38f1cdc3684843..10192538675698f7337c49d3f8098cf3b602a42a 100644 --- a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -4,9 +4,9 @@ core = 7.x package = Chaos tool suite dependencies[] = ctools -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install index b1613d8b51eb1b7bea23bc82c31a71ec867e4341..3f0087725a01f236b0132aa6092966e7d002825d 100644 --- a/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install +++ b/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.install @@ -17,7 +17,7 @@ function ctools_access_ruleset_schema_1() { 'bulk export' => TRUE, 'primary key' => 'rsid', 'api' => array( - 'owner' => 'ctools', + 'owner' => 'ctools_access_ruleset', 'api' => 'ctools_rulesets', 'minimum_version' => 1, 'current_version' => 1, diff --git a/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info index bbf708cd1e083152f6123f257276396fe91d2788..03ccb34155e99c8844fe4f26afc023541516e34c 100644 --- a/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -4,9 +4,9 @@ package = Chaos tool suite dependencies[] = ctools core = 7.x -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info index b56e3be7ac92e2b7bda77e1cf5df94a52259d897..d9c45673f8ba7d9e9bc5787cb4e8dc22ec4955fe 100644 --- a/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info +++ b/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info @@ -4,9 +4,9 @@ core = 7.x package = Chaos tool suite dependencies[] = ctools -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info index c9f12e7d2005fdb80189f0ffe4051705c240bfba..e4fa534355e4d2f4a4c64d4b12398d12513ef8ea 100644 --- a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -7,9 +7,9 @@ dependencies[] = page_manager dependencies[] = advanced_help core = 7.x -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module index 442969d7b10ced9f91e9a1b05b733e1701a51b7f..01d533826e4f8bffc4ea06bc68892fcf283de6ee 100644 --- a/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module +++ b/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.module @@ -85,8 +85,8 @@ function ctools_plugin_example_explanation_page() { You can find it in %path.', array( '@demo_url' => url('ctools_plugin_example/xxxxx'), - '!ctools_plugin_example_help' => theme('advanced_help_topic', 'ctools_plugin_example', 'Chaos-Tools--CTools--Plugin-Examples', 'title'), - '!ctools_help' => theme('advanced_help_topic', 'ctools', 'plugins', 'title'), + '!ctools_plugin_example_help' => theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')), + '!ctools_help' => theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')), '%path' => drupal_get_path('module', 'ctools_plugin_example'), )) . '</p>'; diff --git a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc index 8b73408ebb524f1507f20fc82f535fe1b3404989..3c02ab84fac4cdd0516313f454213ccd96b8495c 100644 --- a/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc +++ b/sites/all/modules/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc @@ -53,8 +53,8 @@ $plugin = array( function no_context_content_type_render($subtype, $conf, $args, $context) { $block = new stdClass(); - $ctools_help = theme('advanced_help_topic', 'ctools', 'plugins', 'title'); - $ctools_plugin_example_help = theme('advanced_help_topic', 'ctools_plugin_example', 'Chaos-Tools--CTools--Plugin-Examples', 'title'); + $ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')); + $ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')); // The title actually used in rendering $block->title = check_plain("No-context content type"); diff --git a/sites/all/modules/ctools/drush/ctools.drush.inc b/sites/all/modules/ctools/drush/ctools.drush.inc index bf1df1f2bfc01400280c7a2f731c7547cce949e7..1862dbe9480fb63940c5f5fee2db8837d2d64f22 100644 --- a/sites/all/modules/ctools/drush/ctools.drush.inc +++ b/sites/all/modules/ctools/drush/ctools.drush.inc @@ -381,6 +381,7 @@ function ctools_drush_export_info() { // Get info on these tables, or all tables if none specified. $info = _ctools_drush_export_info($table_names, $load); + $schemas = $info['schemas']; $exportables = $info['exportables']; if (empty($exportables)) { @@ -416,11 +417,12 @@ function ctools_drush_export_info() { } // Build a tabular output as default. else { - $header = $tables_only ? array() : array(dt('Base table'), dt('Exportables')); + $header = $tables_only ? array() : array(dt('Module'), dt('Base table'), dt('Exportables')); $rows = array(); foreach ($exportables as $table => $info) { if (is_array($info)) { $row = array( + $schemas[$table]['module'], $table, // Machine name is better for this? shellColours::getColouredOutput(implode("\n", array_keys($info)), 'light_green') . "\n", @@ -434,7 +436,7 @@ function ctools_drush_export_info() { if (!empty($rows)) { drush_print("\n"); array_unshift($rows, $header); - drush_print_table($rows, TRUE, array(20)); + drush_print_table($rows, TRUE, array(20, 20)); drush_print(dt('Total exportables found: !total', array('!total' => $exportable_counts['total']))); foreach ($exportable_counts['exportables'] as $table_name => $count) { drush_print(dt('!table_name (!count)', array('!table_name' => $table_name, '!count' => $count)), 2); diff --git a/sites/all/modules/ctools/help/plugins-implementing.html b/sites/all/modules/ctools/help/plugins-implementing.html index 070f08ded1b292d5f83741127cfa7ab9f6e480e1..c95e72d42eae5c6594e58290de83383b6a637eff 100644 --- a/sites/all/modules/ctools/help/plugins-implementing.html +++ b/sites/all/modules/ctools/help/plugins-implementing.html @@ -1,6 +1,7 @@ -<p>To implement plugins, you need to implement a single hook in your module to tell the system where your plugins live, and then you need to implement one or more .inc files that contain the plugin data.</p> +<p>There are two parts to implementing a plugin: telling the system where it is, and implementing one or more .inc files that contain the plugin data.</p> -<h2>Telling it where your plugins live</h2> +<h2>Telling the system where your plugins live</h2> +<h3>How a module implements plugins</h3> <p>To implement any plugins at all, you must implement a single function for all plugins: <strong>hook_ctools_plugin_directory</strong>. Every time a module loads plugins, this hook will be called to see which modules implement those plugins and in what directory those plugins will live.</p> <pre> @@ -30,13 +31,13 @@ plugins/ my_content_type.inc layouts/ my_layout.inc - my_laout.css + my_layout.css my_layout.tpl.php my_layout_image.png </pre> -<h2>How a theme can implement plugins</h2> -<p>Themes can implement plugins if the plugin owner specified that it's possible in its hook_ctools_api_TYPE() call. If so, it is generally exactly the same as modules, except for one important difference: themes don't get hook_ctools_plugin_directory(). Instead, themes add a line to their info file:</p> +<h3>How a theme implements plugins</h3> +<p>Themes can implement plugins if the plugin owner specified that it's possible in its hook_ctools_plugin_type() call. If so, it is generally exactly the same as modules, except for one important difference: themes don't get hook_ctools_plugin_directory(). Instead, themes add a line to their .info file:</p> <pre> plugins[module][type] = directory @@ -54,7 +55,7 @@ $plugin = array( ); </pre> -<p>Several values will be filled in for you automatically, but you can override them if necessary. They include 'name', 'path', 'file' and 'module'. Additionally, the plugin can owner can provide other defaults as well.</p> +<p>Several values will be filled in for you automatically, but you can override them if necessary. They include 'name', 'path', 'file' and 'module'. Additionally, the plugin owner can provide other defaults as well.</p> <p>There are no required keys by the plugin system itself. The only requirements in the $plugin array will be defined by the plugin type.</p> diff --git a/sites/all/modules/ctools/help/plugins.html b/sites/all/modules/ctools/help/plugins.html index b9484118308f5f17b9a8a8a85f3e52d9027f297b..1e64da09506633011d82a366d02a0b28142eef80 100644 --- a/sites/all/modules/ctools/help/plugins.html +++ b/sites/all/modules/ctools/help/plugins.html @@ -1,5 +1,5 @@ <p>The plugins tool allows a module to allow <b>other</b> modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.</p> -<p>A module which uses plugins can implement a hook describing the plugin (which is not necessary, as defaults will be filled in) and then calls a ctools function which loads either all the known plugins (used for listing/choosing) or loads a specific plugin (used when its known which plugin is needed). From the perspective of the plugin system, a plugin is a packet of data, usually some printable info and a list of callbacks. It is up to the module implementing plugins to determine what that info means and what the callbacks do.</p> +<p>A module which uses plugins can implement a hook describing the plugin (which is not necessary, as defaults will be filled in) and then calls a ctools function which loads either all the known plugins (used for listing/choosing) or a specific plugin (used when it's known which plugin is needed). From the perspective of the plugin system, a plugin is a packet of data, usually some printable info and a list of callbacks. It is up to the module implementing plugins to determine what that info means and what the callbacks do.</p> -<p>A module which implements plugins must first implement the <strong>hook_ctools_plugin_directory</strong> function, which simply tells the system which plugins are supported and what directory to look in. Each plugin will then be in a .inc file in the directory supplied. The .inc file will contain a specially named hook which returns the data necessary to implement the plugin.</p> \ No newline at end of file +<p>A module which implements plugins must first implement the <strong>hook_ctools_plugin_directory</strong> function, which simply tells the system which plugins are supported and what directory to look in. Each plugin will then be in a separate .inc file in the directory supplied. The .inc file will contain a specially named hook which returns the data necessary to implement the plugin.</p> diff --git a/sites/all/modules/ctools/includes/action-links.theme.inc b/sites/all/modules/ctools/includes/action-links.theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..3a2398a1fd35416c07a946a1d03d89c5d5dedab1 --- /dev/null +++ b/sites/all/modules/ctools/includes/action-links.theme.inc @@ -0,0 +1,33 @@ +<?php +/** + * @file + * Theme function for wrapping menu local actions. + */ + +/** + * Delegated implementation of hook_theme() + */ +function ctools_action_links_theme(&$items) { + $items['ctools_menu_local_actions_wrapper'] = array( + 'render element' => 'links', + 'file' => 'includes/action-links.theme.inc', + ); +} + +/** + * Render a menu local actions wrapper. + * + * @param $links + * Local actions links. + * @param $attributes + * An array of attributes to append to the wrapper. + */ +function theme_ctools_menu_local_actions_wrapper($variables) { + $links = drupal_render($variables['links']); + + if (empty($links)) { + return; + } + + return '<ul class="action-links">' . $links . '</ul>'; +} \ No newline at end of file diff --git a/sites/all/modules/ctools/includes/content.menu.inc b/sites/all/modules/ctools/includes/content.menu.inc index 11a686abffd06fa05b83abb50af98dde67976d71..93884fce7fbe0ad0ff9eae5a07ce8abb04d1bffd 100644 --- a/sites/all/modules/ctools/includes/content.menu.inc +++ b/sites/all/modules/ctools/includes/content.menu.inc @@ -50,6 +50,21 @@ function ctools_content_autocomplete_entity($type, $string = '') { $matches = array(); if ($type == 'node') { + if (!user_access('bypass node access')) { + // If the user is able to view their own unpublished nodes, allow them + // to see these in addition to published nodes. + if (user_access('view own unpublished content')) { + $query->condition(db_or() + ->condition('b.status', NODE_PUBLISHED) + ->condition('b.uid', $GLOBALS['user']->uid) + ); + } + else { + // If not, restrict the query to published nodes. + $query->condition('b.status', NODE_PUBLISHED); + } + } + $query->addTag('node_access'); $query->join('users', 'u', 'b.uid = u.uid'); $query->addField('u', 'name', 'name'); diff --git a/sites/all/modules/ctools/includes/content.plugin-type.inc b/sites/all/modules/ctools/includes/content.plugin-type.inc index 4c767ae80f776bb884292358700f69f1084cfb74..a0debc3e5ee779060d40442dc2891e46889d3b3a 100644 --- a/sites/all/modules/ctools/includes/content.plugin-type.inc +++ b/sites/all/modules/ctools/includes/content.plugin-type.inc @@ -10,7 +10,7 @@ function ctools_content_plugin_type(&$items) { 'cache' => FALSE, 'process' => array( 'function' => 'ctools_content_process', - 'file' => 'export-ui.inc', + 'file' => 'content.inc', 'path' => drupal_get_path('module', 'ctools') . '/includes', ), ); diff --git a/sites/all/modules/ctools/includes/context-task-handler.inc b/sites/all/modules/ctools/includes/context-task-handler.inc index 617f20eb35b5e0e9137fc9ecf3119820b0aad913..37aa6df09711d56b0c34a1c14f0d67953128fed0 100644 --- a/sites/all/modules/ctools/includes/context-task-handler.inc +++ b/sites/all/modules/ctools/includes/context-task-handler.inc @@ -143,6 +143,10 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte return MENU_ACCESS_DENIED; case 404: return MENU_NOT_FOUND; + case 410: + drupal_add_http_header('Status', '410 Gone'); + drupal_exit(); + break; case 301: case 302: case 303: @@ -451,7 +455,7 @@ function ctools_context_handler_edit_criteria($form, &$form_state) { ctools_include('ajax'); ctools_modal_add_plugin_js(ctools_get_access_plugins()); ctools_include('context-access-admin'); - $form_state['module'] = 'page_manager_task_handler'; + $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler'; // Encode a bunch of info into the argument so we can get our cache later $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name; $form_state['access'] = $form_state['handler']->conf['access']; diff --git a/sites/all/modules/ctools/includes/context.inc b/sites/all/modules/ctools/includes/context.inc index 5c7a4ceedae803acce77bf4da9be7124e3039627..93be748f6f6db64072b7c19612045e6c35b0abe9 100644 --- a/sites/all/modules/ctools/includes/context.inc +++ b/sites/all/modules/ctools/includes/context.inc @@ -508,6 +508,17 @@ function ctools_context_get_all_converters() { /** * Let the context convert an argument based upon the converter that was given. + * + * @param $context + * The context object + * @param $converter + * The converter to use, which should be a string provided by the converter list. + * @param $converter_options + * A n array of options to pass on to the generation function. For contexts + * that use token module, of particular use is 'sanitize' => FALSE which can + * get raw tokens. This should ONLY be used in values that will later be + * treated as unsafe user input since these values are by themselves unsafe. + * It is particularly useful to get raw values from Field API. */ function ctools_context_convert_context($context, $converter, $converter_options = array()) { // Contexts without plugins might be optional placeholders. @@ -633,7 +644,7 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve // Look for context matches we we only have to convert known matches. $matches = array(); - if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)?)/us', $string, $matches)) { + if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) { foreach ($matches[1] as $keyword) { // Ignore anything it finds with %%. if ($keyword[0] == '%') { diff --git a/sites/all/modules/ctools/includes/dropbutton.theme.inc b/sites/all/modules/ctools/includes/dropbutton.theme.inc index 0848666e5d08a2e664924b701013aa7e5b4bb51b..fcdd5a37a7f9506a6059441b2cd936108a368062 100644 --- a/sites/all/modules/ctools/includes/dropbutton.theme.inc +++ b/sites/all/modules/ctools/includes/dropbutton.theme.inc @@ -1,10 +1,11 @@ <?php -// $Id$ /** * @file * Provide a javascript based dropbutton menu. * + * An example are the edit/disable/delete links on the views listing page. + * * The dropbutton menu will show up as a button with a clickable twisty pointer * to the right. When clicked the button will expand, showing the list of links. * diff --git a/sites/all/modules/ctools/includes/dropdown.theme.inc b/sites/all/modules/ctools/includes/dropdown.theme.inc index 2e21559d382573b23f102959e56bbbf3690fa8d9..7e748f5e9c5b7226365708d7475b98aedd926955 100644 --- a/sites/all/modules/ctools/includes/dropdown.theme.inc +++ b/sites/all/modules/ctools/includes/dropdown.theme.inc @@ -4,6 +4,9 @@ * @file * Provide a javascript based dropdown menu. * + * An example are the dropdown settings in the panels ui, like for adding + * new panes. + * * The dropdown menu will show up as a clickable link; when clicked, * a small menu will slide down beneath it, showing the list of links. * diff --git a/sites/all/modules/ctools/includes/fields.inc b/sites/all/modules/ctools/includes/fields.inc index dcd6139fe7a30887794910edecddf37870292dba..162262c7817c1700b0dd8c6da5d7b7336e3b56d5 100644 --- a/sites/all/modules/ctools/includes/fields.inc +++ b/sites/all/modules/ctools/includes/fields.inc @@ -170,6 +170,8 @@ function ctools_field_label($field_name) { * * @param $field_name * Either a field instance object or the name of the field. + * If the 'field' key is populated it will be used as the field + * settings. * @param $op * Possible operations include: * - form @@ -222,6 +224,7 @@ function ctools_field_label($field_name) { function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) { if (is_array($field_name)) { $instance = $field_name; + $field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field']; $field_name = $instance['field_name']; } else { @@ -245,7 +248,6 @@ function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a // Everything from here is unmodified code from _field_invoke() formerly // inside a foreach loop over the instances. - $field = field_info_field($field_name); $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (function_exists($function)) { // Determine the list of languages to iterate on. diff --git a/sites/all/modules/ctools/includes/plugins.inc b/sites/all/modules/ctools/includes/plugins.inc index 2dbdd48494404009259b7cf4bd1ff116b259b94f..0363fcb2baddf8da1f0d93f712bfaf7c04b6aca1 100644 --- a/sites/all/modules/ctools/includes/plugins.inc +++ b/sites/all/modules/ctools/includes/plugins.inc @@ -440,7 +440,7 @@ function ctools_plugin_load_includes($info, $filename = NULL) { $all_files[$info['module']][$info['type']] = array(); // Load all our plugins. $directories = ctools_plugin_get_directories($info); - $extension = empty($info['info file']) ? $info['extension'] : 'info'; + $extension = (empty($info['info file']) || ($info['extension'] != 'inc')) ? $info['extension'] : 'info'; foreach ($directories as $module => $path) { $all_files[$info['module']][$info['type']][$module] = file_scan_directory($path, '/\.' . $extension . '$/', array('key' => 'name')); diff --git a/sites/all/modules/ctools/js/auto-submit.js b/sites/all/modules/ctools/js/auto-submit.js index 890dd1ffde6da0e0664390b71680701f865c2e59..a3e9aa42a07970d10998693e51f25cd1606ddd4a 100644 --- a/sites/all/modules/ctools/js/auto-submit.js +++ b/sites/all/modules/ctools/js/auto-submit.js @@ -45,11 +45,11 @@ Drupal.behaviors.CToolsAutoSubmit = { // the change event bubbles so we only need to bind it to the outer form $('form.ctools-auto-submit-full-form', context) .add('.ctools-auto-submit', context) - .filter('form, select, input:not(:text, :submit, .ctools-auto-submit-exclude)') + .filter('form, select, input:not(:text, :submit)') .once('ctools-auto-submit') .change(function (e) { // don't trigger on text change for full-form - if ($(e.target).is(':not(:text, :submit)')) { + if ($(e.target).is(':not(:text, :submit, .ctools-auto-submit-exclude)')) { triggerSubmit.call(e.target.form); } }); @@ -88,6 +88,11 @@ Drupal.behaviors.CToolsAutoSubmit = { if ($.inArray(e.keyCode, discardKeyCode) === -1) { timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500); } + }) + .bind('change', function (e) { + if ($.inArray(e.keyCode, discardKeyCode) === -1) { + timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500); + } }); }); } diff --git a/sites/all/modules/ctools/js/dependent.js b/sites/all/modules/ctools/js/dependent.js index 07ff021eac27d86074b7f64e95478bb91a718689..e9e2447c4cd367484fd9ae3aba381e73bbf82917 100644 --- a/sites/all/modules/ctools/js/dependent.js +++ b/sites/all/modules/ctools/js/dependent.js @@ -97,7 +97,7 @@ else { switch ($(trigger).attr('type')) { case 'checkbox': - var val = $(trigger).attr('checked') || 0; + var val = $(trigger).attr('checked') ? true : false; if (val) { $(trigger).siblings('label').removeClass('hidden-options').addClass('expanded-options'); diff --git a/sites/all/modules/ctools/js/dropbutton.js b/sites/all/modules/ctools/js/dropbutton.js index 49bc10749205f09f1f671e24aa4c9bb614a411a1..f505550b6cc4ac799d9f2415d4742caaa145a890 100644 --- a/sites/all/modules/ctools/js/dropbutton.js +++ b/sites/all/modules/ctools/js/dropbutton.js @@ -1,4 +1,3 @@ -// $Id$ /** * @file * Implement a simple, clickable dropbutton menu. diff --git a/sites/all/modules/ctools/js/modal.js b/sites/all/modules/ctools/js/modal.js index 5b417d02be2aa8f7fc2c7de4ea4390db516ee11b..831649fc83b9fefe5373fba794f8d779ebcc8982 100644 --- a/sites/all/modules/ctools/js/modal.js +++ b/sites/all/modules/ctools/js/modal.js @@ -399,7 +399,7 @@ } var parents = $(target).parents().get(); - for (var i in $(target).parents().get()) { + for (var i = 0; i < parents.length; ++i) { var position = $(parents[i]).css('position'); if (position == 'absolute' || position == 'fixed') { return true; @@ -434,7 +434,7 @@ } }; - $(document).bind('keypress', modalEventEscapeCloseHandler); + $(document).bind('keydown', modalEventEscapeCloseHandler); // Close the open modal content and backdrop function close() { diff --git a/sites/all/modules/ctools/page_manager/page_manager.admin.inc b/sites/all/modules/ctools/page_manager/page_manager.admin.inc index c01961be87cae74ef8415132b6ae96d33a9010f5..3dce041ae10e744d5695421b64ed6b664087e125 100644 --- a/sites/all/modules/ctools/page_manager/page_manager.admin.inc +++ b/sites/all/modules/ctools/page_manager/page_manager.admin.inc @@ -158,7 +158,7 @@ function page_manager_get_pages($tasks, &$pages, $task_id = NULL) { $visible_path = ''; if (!empty($task['admin path'])) { foreach (explode('/', $task['admin path']) as $bit) { - if ($bit[0] != '!') { + if (isset($bit[0]) && $bit[0] != '!') { $path[] = $bit; } } @@ -1126,9 +1126,11 @@ function page_manager_render_operations(&$page, $operations, $active_trail, $att // We only render an li for things in the same nav tree. if (empty($operation['location']) || $operation['location'] == $location) { if (!is_array($attributes['class'])) { - dsm($attributes['class']); + $attributes['class'] = array($attributes['class']); } + $class = empty($attributes['class']) || !is_array($attributes['class']) ? array() : $attributes['class']; + if ($id == $first) { $class[] = 'operation-first'; } diff --git a/sites/all/modules/ctools/page_manager/page_manager.api.php b/sites/all/modules/ctools/page_manager/page_manager.api.php new file mode 100644 index 0000000000000000000000000000000000000000..03e2e75f6cbb13ca4e68c6955100da7730b2a24e --- /dev/null +++ b/sites/all/modules/ctools/page_manager/page_manager.api.php @@ -0,0 +1,39 @@ +<?php + +/** + * @file + * Describe hooks provided by the Page Manager module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * @todo. + * + * @param array $result + * @todo. + * @param object $page + * @todo. + */ +function hook_page_manager_operations_alter(&$result, &$page) { + // @todo. +} + +/** + * @todo. + * + * @param array $operations + * @todo. + * @param object $handler + * @todo. + */ +function hook_page_manager_variant_operations_alter(&$operations, &$handler) { + // @todo. +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/ctools/page_manager/page_manager.info b/sites/all/modules/ctools/page_manager/page_manager.info index d3e358809d12dce56a64b2eb55dd07bf863860d0..76095ff1b6667677121efa9c0df546fc40a11e99 100644 --- a/sites/all/modules/ctools/page_manager/page_manager.info +++ b/sites/all/modules/ctools/page_manager/page_manager.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = ctools package = Chaos tool suite -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/page_manager/page_manager.module b/sites/all/modules/ctools/page_manager/page_manager.module index 62f6d275cfa3afcff48f6f88e6bc4cdfc1372e08..da99a578fc9e9e9e660fcc6a8f8c1b4ecb2b66b2 100644 --- a/sites/all/modules/ctools/page_manager/page_manager.module +++ b/sites/all/modules/ctools/page_manager/page_manager.module @@ -838,9 +838,6 @@ function page_manager_get_task_subtasks($task) { if (is_array($retval)) { return $retval; } - else { - dsm($retval); - } } return array(); diff --git a/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc b/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc index c11dcd74aa5dd435d11222cff01a8a6369bb92f2..e5c01414da878c4312d88e7a29e4d5939fa0c0c6 100644 --- a/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc +++ b/sites/all/modules/ctools/page_manager/plugins/task_handlers/http_response.inc @@ -105,8 +105,9 @@ $plugin = array( */ function page_manager_http_response_codes() { return array( - 404 => t('404 Page not found'), 403 => t('403 Access denied'), + 404 => t('404 Page not found'), + 410 => t('410 Gone'), 301 => t('301 Redirect'), ); } @@ -282,4 +283,4 @@ function page_manager_http_response_render($handler, $base_contexts, $args, $tes } return $info; -} \ No newline at end of file +} diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc new file mode 100644 index 0000000000000000000000000000000000000000..0fcf9e2a725d56a2a637d025f5ac37077ac053c2 --- /dev/null +++ b/sites/all/modules/ctools/page_manager/plugins/tasks/comment_reply.inc @@ -0,0 +1,162 @@ +<?php +/** + * Specialized implementation of hook_page_manager_task_tasks(). See api-task.html for + * more information. + */ +function page_manager_comment_reply_page_manager_tasks() { + if (!module_exists('comment')) { + return; + } + + return array( + // This is a 'page' task and will fall under the page admin UI + 'task type' => 'page', + + 'title' => t('Comment Reply page'), + 'admin title' => t('Comment Reply page'), + 'admin description' => t('When enabled, this overrides the default Drupal behavior for the site contact page at <em>/contact</em>. If no variant is selected, the default Drupal contact form will be used.'), + 'admin path' => 'comment/reply/%node', + + // Menu hooks so that we can alter the node/%node menu entry to point to us. + 'hook menu alter' => 'page_manager_comment_reply_menu_alter', + + // This is task uses 'context' handlers and must implement these to give the + // handler data it needs. + 'handler type' => 'context', + 'get arguments' => 'page_manager_comment_reply_get_arguments', + 'get context placeholders' => 'page_manager_comment_reply_get_contexts', + + // Allow this to be enabled or disabled: + 'disabled' => variable_get('page_manager_comment_reply_disabled', TRUE), + 'enable callback' => 'page_manager_comment_reply_enable', + 'access callback' => 'page_manager_comment_reply_check', + ); +} + +/** + * Callback to enable/disable the page from the UI. + */ +function page_manager_comment_reply_enable($cache, $status) { + variable_set('page_manager_comment_reply_disabled', $status); + // Set a global flag so that the menu routine knows it needs + // to set a message if enabling cannot be done. + if (!$status) { + $GLOBALS['page_manager_enabling_comment_reply'] = TRUE; + } +} + + +/** + * Entry point for our overridden comment. + * + */ +function page_manager_comment_reply_page($node, $pid = NULL){ + // Load my task plugin + $task = page_manager_get_task('comment_reply'); + + // Load the node into a context. + ctools_include('context'); + ctools_include('context-task-handler'); + + $contexts = ctools_context_handler_get_task_contexts($task, '', array($node, $pid)); + + if (array_key_exists('argument_cid_3', $contexts) && $contexts['argument_cid_3']->data->nid != $node->nid) { + // Attempting to reply to a comment not belonging to the current nid. + drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); + drupal_goto("node/$node->nid"); + } + + $output = ctools_context_handler_render($task, '', $contexts, array($node, $pid)); + if ($output != FALSE) { + return $output; + } + + $function = 'comment_reply'; + foreach (module_implements('page_manager_override') as $module) { + $call = $module . '_page_manager_override'; + if (($rc = $call('comment_reply')) && function_exists($rc)) { + $function = $rc; + break; + } + } + + module_load_include('inc', 'comment', 'comment.pages'); + return $function($node, $pid); +} + +/** + * Callback to get arguments provided by this task handler. + * + * Since this is the node view and there is no UI on the arguments, we + * create dummy arguments that contain the needed data. + */ +function page_manager_comment_reply_get_arguments($task, $subtask_id) { + return array( + array( + 'keyword' => 'node', + 'identifier' => t('Node being commented on'), + 'id' => 2, + 'name' => 'entity_id:node', + 'settings' => array(), + ), + array( + 'keyword' => 'comment', + 'identifier' => t('Comment being replied to'), + 'id' => 3, + 'name' => 'entity_id:comment', + 'settings' => array(), + ), + ); +} + +/** + * Callback to get context placeholders provided by this handler. + */ +function page_manager_comment_reply_get_contexts($task, $subtask_id) { + return ctools_context_get_placeholders_from_argument(page_manager_comment_reply_get_arguments($task, $subtask_id)); +} + +/** + * Callback defined by page_manager_node_view_page_manager_tasks(). + * + * Alter the node view input so that node view comes to us rather than the + * normal node view process. + */ +function page_manager_comment_reply_menu_alter(&$items, $task) { + if (variable_get('page_manager_comment_reply_disabled', TRUE)) { + return; + } + // Override the node view handler for our purpose. + $callback = $items['comment/reply/%node']['page callback']; + if ($callback == 'comment_reply' || variable_get('page_manager_override_anyway', FALSE)) { + $items['comment/reply/%node']['page callback'] = 'page_manager_comment_reply_page'; + $items['comment/reply/%node']['file path'] = $task['path']; + $items['comment/reply/%node']['file'] = $task['file']; + } + else { + // automatically disable this task if it cannot be enabled. + variable_set('page_manager_comment_reply_disabled', TRUE); + if (!empty($GLOBALS['page_manager_enabling_comment_reply'])) { + drupal_set_message(t('Page manager module is unable to enable comment/reply/%node because some other module already has overridden with %callback.', array('%callback' => $callback)), 'error'); + } + } + + // @todo override node revision handler as well? +} + +/** + * Callback to determine if a page is accessible. + * + * @param $task + * The task plugin. + * @param $subtask_id + * The subtask id + * @param $contexts + * The contexts loaded for the task. + * @return + * TRUE if the current user can access the page. + */ +function page_manager_comment_reply_access_check($task, $subtask_id, $contexts) { + $context = reset($contexts); + return TRUE; +} diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc index 6933a548069771a6c8b89912a9210d604f78ae1b..e2765708e287b447b51dfce6cd0baea897198643 100644 --- a/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc +++ b/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc @@ -106,44 +106,90 @@ function page_manager_term_view_menu_alter(&$items, $task) { * to run with it. If no one does, it passes through to Drupal core's * term view, which is term_page_view(). */ -function page_manager_term_view_page($term) { - // Assign the term name as the page title, just as the original page - // callback did. +function page_manager_term_view_page($term, $depth = NULL) { + // Prep the term to be displayed so all of the regular hooks are triggered. + // Rather than calling taxonomy_term_page() directly, as it that would + // potentially load nodes that were not necessary, execute some of the code + // prior to identifying the correct CTools or Page Manager task handler and + // only proceed with the rest of the code if necessary. + + // Assign the term name as the page title. drupal_set_title($term->name); - // While we ordinarily should never actually get feeds through here, - // just in case + // If there is a menu link to this term, 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. + $uri = entity_uri('taxonomy_term', $term); + + // Set the term path as the canonical URL to prevent duplicate content. + drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE); + // Set the non-aliased path as a default shortlink. + drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE); + + // Trigger the main + $build = taxonomy_term_show($term); + // Load my task plugin $task = page_manager_get_task('term_view'); // Load the term into a context. ctools_include('context'); ctools_include('context-task-handler'); - $contexts = ctools_context_handler_get_task_contexts($task, '', array($term)); + $contexts = ctools_context_handler_get_task_contexts($task, '', array($term, $depth)); if (empty($contexts)) { return drupal_not_found(); } + // Build the full output using the configured CTools plugin. $output = ctools_context_handler_render($task, '', $contexts, array($term->tid)); if ($output !== FALSE) { return $output; } - $function = 'taxonomy_term_page'; + // Try loading an override plugin. foreach (module_implements('page_manager_override') as $module) { $call = $module . '_page_manager_override'; if (($rc = $call('term_view')) && function_exists($rc)) { - $function = $rc; - break; + return $rc($node); } } - // Otherwise, fall back. - if ($function == 'taxonomy_term_page') { - module_load_include('inc', 'taxonomy', 'taxonomy.pages'); + // Otherwise, fall back to replicating the output normally generated by + // taxonomy_term_page(). + + // Build breadcrumb based on the hierarchy of the term. + $current = (object) array( + 'tid' => $term->tid, + ); + // @todo This overrides any other possible breadcrumb and is a pure hard-coded + // presumption. Make this behavior configurable per vocabulary or term. + $breadcrumb = array(); + while ($parents = taxonomy_get_parents($current->tid)) { + $current = array_shift($parents); + $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid); + } + $breadcrumb[] = l(t('Home'), NULL); + $breadcrumb = array_reverse($breadcrumb); + drupal_set_breadcrumb($breadcrumb); + drupal_add_feed('taxonomy/term/' . $term->tid . '/feed', 'RSS - ' . $term->name); + + if ($nids = taxonomy_select_nodes($term->tid, TRUE, variable_get('default_nodes_main', 10))) { + $nodes = node_load_multiple($nids); + $build += node_view_multiple($nodes); + $build['pager'] = array( + '#theme' => 'pager', + '#weight' => 5, + ); + } + else { + $build['no_content'] = array( + '#prefix' => '<p>', + '#markup' => t('There is currently no content classified with this term.'), + '#suffix' => '</p>', + ); } - return $function($term); + return $build; } /** diff --git a/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc b/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc index 3d98426422b202db010c5c76bfd6fa60d8688f8a..6d0135dee5e4583648966bb16469e23f115659ca 100644 --- a/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc +++ b/sites/all/modules/ctools/page_manager/plugins/tasks/user_edit.inc @@ -52,6 +52,17 @@ function page_manager_user_edit_menu_alter(&$items, $task) { $items['user/%user/edit']['page arguments'] = array(1); $items['user/%user/edit']['file path'] = $task['path']; $items['user/%user/edit']['file'] = $task['file']; + if (($categories = _user_categories()) && (count($categories) > 1)) { + foreach ($categories as $key => $category) { + // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK. + if ($category['name'] != 'account') { + $items['user/%user_category/edit/' . $category['name']]['page callback'] = 'page_manager_user_edit_page'; + $items['user/%user_category/edit/' . $category['name']]['page arguments'] = array(1, 3); + $items['user/%user_category/edit/' . $category['name']]['file path'] = $task['path']; + $items['user/%user_category/edit/' . $category['name']]['file'] = $task['file']; + } + } + } } else { // automatically disable this task if it cannot be enabled. @@ -69,7 +80,9 @@ function page_manager_user_edit_menu_alter(&$items, $task) { * to run with it. If no one does, it passes through to Drupal core's * user edit, which is drupal_get_form('user_profile_form',$account). */ -function page_manager_user_edit_page($account) { +function page_manager_user_edit_page($account, $category = 'account') { + // Store the category on the user for later usage. + $account->user_category = $category; // Load my task plugin: $task = page_manager_get_task('user_edit'); diff --git a/sites/all/modules/ctools/plugins/access/entity_bundle.inc b/sites/all/modules/ctools/plugins/access/entity_bundle.inc index ed680965a5e9d76d5e3455c9c2f186143e88dbfa..e07a048decc74700dacb03306a3f9ef81d63fc9b 100644 --- a/sites/all/modules/ctools/plugins/access/entity_bundle.inc +++ b/sites/all/modules/ctools/plugins/access/entity_bundle.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/sites/all/modules/ctools/plugins/access/entity_field_value.inc b/sites/all/modules/ctools/plugins/access/entity_field_value.inc index 59b01fbb8600467d86b54f623c51ee225c392d52..e5857d89739bc0c3719f8ddf9340b85d5d77b2ef 100644 --- a/sites/all/modules/ctools/plugins/access/entity_field_value.inc +++ b/sites/all/modules/ctools/plugins/access/entity_field_value.inc @@ -98,6 +98,7 @@ function ctools_entity_field_value_ctools_access_settings($form, &$form_state, $ foreach (element_children($form['settings']) as $element) { unset($form['settings'][$element]['#weight']); } + // Need more logic here to handle compound fields. foreach ($columns as $column) { if (isset($conf[$column]) && is_array($conf[$column])) { @@ -105,12 +106,13 @@ function ctools_entity_field_value_ctools_access_settings($form, &$form_state, $ if (is_numeric($delta) && is_array($conf_value)) { $form['settings'][$field_name][LANGUAGE_NONE][$delta]['value']['#default_value'] = $conf_value['value']; } - else { - $form['settings'][$field_name][LANGUAGE_NONE]['#default_value'] = $conf[$column]; - } } } + else { + $form['settings'][$field_name][LANGUAGE_NONE]['#default_value'] = $conf[$column]; + } } + return $form; } @@ -148,29 +150,29 @@ function ctools_entity_field_value_ctools_access_check($conf, $context, $plugin) foreach ($field['columns'] as $column => $attributes) { $columns[$column] = _field_sql_storage_columnname($field_name, $column); } - foreach ($conf as $potential_field => $values) { - if ($field_name === $potential_field) { - $conf_value_array = _ctools_entity_field_value_ctools_access_get_conf_field_values($values, $langcode); - if (empty($conf_value_array)) { - return FALSE; - } + if (isset($conf[$field_name])) { + // We have settings for this field. + $conf_value_array = _ctools_entity_field_value_ctools_access_get_conf_field_values($conf[$field_name], $langcode); + if (empty($conf_value_array)) { + return FALSE; + } - // Check field value. - foreach ($field_items as $field_value) { - foreach ($field_value as $field_column => $value) { - // Iterate through config values. - foreach ($conf_value_array as $conf_value) { - // - if ($value == $conf_value[$field_column]) { - return TRUE; - } + // Check field value. + foreach ($field_items as $field_value) { + foreach ($field_value as $field_column => $value) { + // Iterate through config values. + foreach ($conf_value_array as $conf_value) { + // Check access only for stored in config column values. + if (isset($conf_value[$field_column]) && $value == $conf_value[$field_column]) { + return TRUE; } } } } } } + return FALSE; } @@ -221,15 +223,17 @@ function ctools_entity_field_value_ctools_access_summary($conf, $context, $plugi $options = array('language' => LANGUAGE_NONE); ctools_include('fields'); $display = field_get_display($instance, $view_mode, $entity); - $display['type'] = 'list_default'; - $function = $display['module'] . '_field_formatter_view'; - $items = isset($entity->{$field_name}[LANGUAGE_NONE]) ? $entity->{$field_name}[LANGUAGE_NONE] : array(); - if (function_exists($function)) { - $elements = $function($entity_type, $entity, $field, $instance, LANGUAGE_NONE, $items, $display); - } - $value_keys = array_keys($values); - foreach ($value_keys as $key => $value) { - $values[$value] = $elements[$key]['#markup']; + if (isset($display['module'])) { + $display['type'] = 'list_default'; + $function = $display['module'] . '_field_formatter_view'; + $items = isset($entity->{$field_name}[LANGUAGE_NONE]) ? $entity->{$field_name}[LANGUAGE_NONE] : array(); + if (function_exists($function)) { + $elements = $function($entity_type, $entity, $field, $instance, LANGUAGE_NONE, $items, $display); + } + $value_keys = array_keys($values); + foreach ($value_keys as $key => $value) { + $values[$value] = isset($elements[$key]['#markup']) ? $elements[$key]['#markup'] : ''; + } } $values = array_merge($keys, $values); return t($string, $values); diff --git a/sites/all/modules/ctools/plugins/arguments/user_edit.inc b/sites/all/modules/ctools/plugins/arguments/user_edit.inc index fc77f806e771774a6ffb05c62c053e1a2a39b48d..32b2b812abca85465df138b4474321e9f7b4eebb 100644 --- a/sites/all/modules/ctools/plugins/arguments/user_edit.inc +++ b/sites/all/modules/ctools/plugins/arguments/user_edit.inc @@ -44,5 +44,4 @@ function ctools_user_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) { // This will perform a node_access check, so we don't have to. return ctools_context_create('user_edit_form', $account); - return NULL; } \ No newline at end of file diff --git a/sites/all/modules/ctools/plugins/content_types/block/block.inc b/sites/all/modules/ctools/plugins/content_types/block/block.inc index f5b00632d97f4798c3940d0e1310734c7978a774..9d55e8ac280652fcf27dffabceaabfa77d18bb56 100644 --- a/sites/all/modules/ctools/plugins/content_types/block/block.inc +++ b/sites/all/modules/ctools/plugins/content_types/block/block.inc @@ -85,6 +85,10 @@ function _ctools_block_content_type_content_type($module, $delta, $block) { * doesn't cache its results anyway. */ function _ctools_block_load_blocks() { + if (!module_exists('block')) { + return array(); + } + $blocks = &drupal_static(__FUNCTION__, NULL); if (!isset($blocks)) { global $theme_key; @@ -142,8 +146,8 @@ function ctools_block_content_type_render($subtype, $conf) { // Allow modules to modify the block before it is viewed, via either // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter(). drupal_alter(array('block_view', "block_view_{$module}_{$delta}"), $block, $info); - $block = (object) $block; } + $block = (object) $block; if (empty($block)) { return; @@ -152,7 +156,7 @@ function ctools_block_content_type_render($subtype, $conf) { $block->module = $module; $block->delta = $delta; - if ($module == 'block') { + if ($module == 'block' && !empty($info) && isset($info->title)) { $block->title = $info->title; } else if (isset($block->subject)) { @@ -280,7 +284,7 @@ function ctools_block_content_type_admin_title($subtype, $conf) { */ function ctools_block_content_type_admin_info($subtype, $conf) { list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf); - $block = (object) module_invoke($module, 'block', 'view', $delta); + $block = (object) module_invoke($module, 'block_view', $delta); // Sanitize the block because <script> tags can hose javascript up: if (!empty($block->content)) { @@ -493,11 +497,11 @@ function ctools_user_login_pane_render($subtype, $conf, $panel_args, $contexts) $info->module = $module; $info->delta = $delta; - $block = new stdClass; - $block->subject = t('User login'); + $block = array(); + $block['subject'] = t('User login'); // Manually set the content (rather than invoking block_view) because the // block implementation won't render on certain URLs. - $block->content = drupal_get_form('user_login_block'); + $block['content'] = drupal_get_form('user_login_block'); // Allow modules to modify the block before it is viewed, via either // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter(). diff --git a/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc b/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc new file mode 100644 index 0000000000000000000000000000000000000000..f31a7028d2b41908033348e599365b02e68246e5 --- /dev/null +++ b/sites/all/modules/ctools/plugins/content_types/comment/comment_reply_form.inc @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Ctools content-type plugin to provide a comment-reply form (replying either + * to a node or to another comment). + */ + +// Only provide the plugin in the comment module is enabled. +if (module_exists('comment')) { + $plugin = array( + 'single' => TRUE, + 'title' => t('Comment Reply Form'), + 'icon' => 'icon_comment.png', + 'description' => t('A form to add a new comment reply.'), + 'required context' => array( + new ctools_context_required(t('Node'), 'node'), + new ctools_context_optional(t('Comment'), 'comment'), + ), + 'category' => t('Comment'), + 'render callback' => 'ctools_comment_reply_form_content_type_render', + 'defaults' => array('anon_links' => false), + ); +} + +function ctools_comment_reply_form_content_type_render($subtype, $conf, $panel_args, $context) { + + $comment = ($context[1]->identifier == 'No context') ? NULL : clone($context[1]->data); + $block = new stdClass(); + $block->module = 'comments'; + if ($comment) $block->delta = $comment->cid; + $block->title = t('Add comment'); + $node = $context[0]->data; + + module_load_include('inc', 'comment', 'comment.pages'); + $block->content = comment_reply($node, ($comment ? $comment->cid : NULL)); + + return $block; +} + +function ctools_comment_reply_form_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" comment form', array('@s' => $context[0]->identifier)); +} + +function ctools_comment_reply_form_content_type_edit_form($form, &$form_state) { + return $form; +} + +function ctools_comment_reply_form_content_type_edit_form_submit($form, &$form_state) { +} diff --git a/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc b/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc index 0183fc6dcc289d2e6c4e7737cdc497a827451abd..56fb76a0157211a152da4bed784ba115a901aa3f 100644 --- a/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc +++ b/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc @@ -104,7 +104,12 @@ function ctools_entity_form_field_content_type_render($subtype, $conf, $panel_ar function ctools_entity_form_field_content_type_admin_title($subtype, $conf, $context) { list($entity_type, $field_name) = explode(':', $subtype, 2); - $field = field_info_instance($entity_type, $field_name, $context->restrictions['type'][0]); + if (!empty($context->restrictions)) { + $field = field_info_instance($entity_type, $field_name, $context->restrictions['type'][0]); + } + else { + $field = array('label' => $subtype); + } return t('"@s" @field form', array('@s' => $context->identifier, '@field' => $field['label'])); } diff --git a/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc b/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc index 6a52306ba7392de8a77bf29bf20f5aff24ff3b29..bec8982563230961b86d3307c1769c2f7d2dbb33 100644 --- a/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc +++ b/sites/all/modules/ctools/plugins/content_types/node_context/node_title.inc @@ -13,6 +13,9 @@ $plugin = array( 'category' => t('Node'), 'defaults' => array( 'link' => TRUE, + 'markup' => 'none', + 'id' => '', + 'class' => '', ), ); @@ -30,11 +33,27 @@ function ctools_node_title_content_type_render($subtype, $conf, $panel_args, $co // Load information about the node type. $type = node_type_get_type($node); + // Generate the title + $content = !empty($conf['link']) ? l($node->title, 'node/' . $node->nid) : check_plain($node->title); + + // Build any surrounding markup if so configured + if (isset($conf['markup']) && $conf['markup'] != 'none') { + $markup = '<' . $conf['markup']; + if (!empty($conf['id'])) { + $markup .= ' id="' . $conf['id'] . '"'; + } + if (!empty($conf['class'])) { + $markup .= ' class="' . $conf['class'] . '"'; + } + $markup .= '>' . $content . '</' . $conf['markup'] . '>' . "\n"; + $content = $markup; + } + // Build the content type block. $block = new stdClass(); $block->module = 'node_title'; $block->title = $type->title_label; - $block->content = !empty($conf['link']) ? l($node->title, 'node/' . $node->nid) : check_plain($node->title); + $block->content = $content; $block->delta = $node->nid; return $block; @@ -46,6 +65,34 @@ function ctools_node_title_content_type_render($subtype, $conf, $panel_args, $co function ctools_node_title_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; + $form['markup'] = array( + '#title' => t('Title tag'), + '#type' => 'select', + '#options' => array( + 'none' => t('- No tag -'), + 'h1' => t('h1'), + 'h2' => t('h2'), + 'h3' => t('h3'), + 'h4' => t('h4'), + 'h5' => t('h5'), + 'h6' => t('h6'), + 'div' => t('div'), + ), + '#default_value' => $conf['markup'], + ); + + $form['id'] = array( + '#title' => t('CSS id to use'), + '#type' => 'textfield', + '#default_value' => $conf['id'], + ); + + $form['class'] = array( + '#title' => t('CSS class to use'), + '#type' => 'textfield', + '#default_value' => $conf['class'], + ); + $form['link'] = array( '#title' => t('Link to node'), '#type' => 'checkbox', diff --git a/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc new file mode 100644 index 0000000000000000000000000000000000000000..2043c1c52f0ade80b96149fc28021a6af66b9b81 --- /dev/null +++ b/sites/all/modules/ctools/plugins/content_types/node_form/node_form_language.inc @@ -0,0 +1,41 @@ +<?php + +/** + * Plugins are described by creating a $plugin array which will be used + * by the system that includes this file. + */ +$plugin = array( + 'single' => TRUE, + 'icon' => 'icon_node_form.png', + 'title' => t('Node form languages'), + 'description' => t('The language selection form.'), + 'required context' => new ctools_context_required(t('Form'), 'node_form'), + 'category' => t('Form'), +); + +function ctools_node_form_language_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('node_form'); + + $block->delta = 'language-options'; + + if (isset($context->form)) { + if (!empty($context->form['language'])) { + $block->content['language'] = $context->form['language']; + unset($context->form['language']); + } + } + else { + $block->content = t('Node language form.'); + } + return $block; +} + +function ctools_node_form_language_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" node form language field', array('@s' => $context->identifier)); +} + +function ctools_node_form_language_content_type_edit_form($form, &$form_state) { + // provide a blank form so we have a place to have context setting. + return $form; +} \ No newline at end of file diff --git a/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc b/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc index e8762d44c13e5be148131ca59d57477a6169a988..c20c4082299ff268a1e36f18bba903f8cccb4088 100644 --- a/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc +++ b/sites/all/modules/ctools/plugins/content_types/page/page_actions.inc @@ -26,7 +26,7 @@ $plugin = array( */ function ctools_page_actions_content_type_render($subtype, $conf, $panel_args) { $block = new stdClass(); - $block->content = menu_local_actions(); + $block->content = theme('ctools_menu_local_actions_wrapper', array('links' => menu_local_actions())); return $block; } diff --git a/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc b/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc index 4585611fa580d08a359d5c607eae1928ec000405..dc3124b1c6d8f3ca07a49a4463e098e64bea4467 100644 --- a/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc +++ b/sites/all/modules/ctools/plugins/content_types/term_context/term_list.inc @@ -49,7 +49,7 @@ function ctools_term_list_content_type_render($subtype, $conf, $panel_args, $con case 'parent': $terms = taxonomy_get_parents($term->tid); - $block->title = format_plural(count($terms), 'Parent term', 'Parent terms'); + $block->title = count($terms) == 1 ? t('Parent term') : t('Parent terms'); break; case 'sibling': diff --git a/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc b/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc index 3e769d610ccd80c9589fa07d3c557f1797dbc925..6c41882ff136fe919f9a683d7d1df550d4ae8c29 100644 --- a/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc +++ b/sites/all/modules/ctools/plugins/content_types/user_context/user_profile.inc @@ -21,7 +21,7 @@ $plugin = array( */ function ctools_user_profile_content_type_render($subtype, $conf, $panel_args, $context) { $account = isset($context->data) ? clone($context->data) : NULL; - if (!$account || ($account->access == 0 && !user_access('administer users'))) { + if (!$account) { return NULL; } diff --git a/sites/all/modules/ctools/plugins/contexts/string.inc b/sites/all/modules/ctools/plugins/contexts/string.inc index c53acaeacffe22f6c07c131ce4e425553d6ad07f..0ea5e526764d1ecbae98789bbe077a56388052aa 100644 --- a/sites/all/modules/ctools/plugins/contexts/string.inc +++ b/sites/all/modules/ctools/plugins/contexts/string.inc @@ -15,7 +15,7 @@ $plugin = array( 'description' => t('A context that is just a string.'), 'context' => 'ctools_context_create_string', 'keyword' => 'string', - 'no ui' => TRUE, + 'no ui' => FALSE, 'context name' => 'string', 'convert list' => array( 'raw' => t('Raw string'), @@ -45,7 +45,7 @@ function ctools_context_create_string($empty, $data = NULL, $conf = FALSE) { if ($data !== FALSE ) { $context->data = $data; - $context->title = check_plain($data); + $context->title = ($conf) ? check_plain($data['identifier']) : check_plain($data); return $context; } } diff --git a/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc b/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc index 89ee2b4bd693389d505ecc0346ce082b39e740b8..da0cca0a552545b274f8c4c51c3d6df7fc8a594f 100644 --- a/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc +++ b/sites/all/modules/ctools/plugins/contexts/user_edit_form.inc @@ -30,16 +30,29 @@ $plugin = array( * are not always created from the UI. */ function ctools_context_create_user_edit_form($empty, $user = NULL, $conf = FALSE) { - static $created; + // Determine the user category. + $category = !empty($conf['category']) ? $conf['category'] : FALSE; + unset($conf['category']); + + // Return previously created contexts, per category. + static $created = array(); + if (!empty($created[$category])) { + return $created[$category]; + } + // If no category was specified, use the default 'account'. + if (!$category) { + $category = 'account'; + } + $context = new ctools_context(array('form', 'user_edit', 'user_form', 'user_edit_form', 'user', 'entity:user')); + // Store this context for later. + $created[$category] = $context; $context->plugin = 'user_edit_form'; - - if ($empty || (isset($created) && $created)) { + if ($empty) { return $context; } - $created = TRUE; - if ($conf) { + if (!empty($conf)) { // In this case, $user is actually our $conf array. $uid = is_array($user) && isset($user['uid']) ? $user['uid'] : (is_object($user) ? $user->uid : 0); @@ -58,7 +71,7 @@ function ctools_context_create_user_edit_form($empty, $user = NULL, $conf = FALS if (!empty($user)) { $form_id = 'user_profile_form'; - $form_state = array('want form' => TRUE, 'build_info' => array('args' => array($user))); + $form_state = array('want form' => TRUE, 'build_info' => array('args' => array($user, $category))); $file = drupal_get_path('module', 'user') . '/user.pages.inc'; require_once DRUPAL_ROOT . '/' . $file; diff --git a/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc b/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc new file mode 100644 index 0000000000000000000000000000000000000000..28dac72c52f107eb9da70d03a6564aa31efe18be --- /dev/null +++ b/sites/all/modules/ctools/plugins/relationships/user_category_edit_form_from_user.inc @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Plugin to provide an relationship handler for term from node. + */ + +/** + * Plugins are described by creating a $plugin array which will be used + * by the system that includes this file. + */ +$plugin = array( + 'title' => t('User category edit form from user'), + 'keyword' => 'user_category_form', + 'description' => t('Adds user category edit form from a user context.'), + 'required context' => new ctools_context_required(t('User'), 'user'), + 'context' => 'ctools_user_category_edit_form_from_user_context', +); + +/** + * Return a new context based on an existing context. + */ +function ctools_user_category_edit_form_from_user_context($context, $conf) { + if (empty($context->data)) { + return ctools_context_create_empty('user_edit_form', NULL); + } + + if (isset($context->data->user_category)) { + return ctools_context_create('user_edit_form', $context->data, array('category' => $context->data->user_category)); + } +} diff --git a/sites/all/modules/ctools/stylizer/stylizer.info b/sites/all/modules/ctools/stylizer/stylizer.info index 9c7f1173ab7231e863f5d7e2fdfc62cf85b90dd1..888cc8e9ba9010e46ae954719fddf9b27185660c 100644 --- a/sites/all/modules/ctools/stylizer/stylizer.info +++ b/sites/all/modules/ctools/stylizer/stylizer.info @@ -5,9 +5,9 @@ package = Chaos tool suite dependencies[] = ctools dependencies[] = color -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/tests/context.test b/sites/all/modules/ctools/tests/context.test index b5f6fb10a16074c8a36e18c538eca29e0d71d9b8..bdf14e3f4e85974e61d325c716429c172f46205f 100644 --- a/sites/all/modules/ctools/tests/context.test +++ b/sites/all/modules/ctools/tests/context.test @@ -23,6 +23,10 @@ class CtoolsContextKeywordsSubstitutionTestCase extends DrupalWebTestCase { // Run tests on some edge cases. $checks = array( + '%node:changed:raw:' => array( + "{$node->changed}:", + t('Multi-level token has been replaced. Colon left untouched.'), + ), '%node:title' => array( "{$node->title}", t('Keyword and converter have been replaced.'), diff --git a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info index 2d975c37bc6453831864631ab18a48ea7e3cd4f4..669c4aee478327beef2322bf85e88f24393a416e 100644 --- a/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info +++ b/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info @@ -7,9 +7,9 @@ hidden = TRUE files[] = ctools_export.test -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/tests/ctools_plugin_test.info b/sites/all/modules/ctools/tests/ctools_plugin_test.info index 6d5e9cd4e19cfc5af152351f228df49ed696aa39..5bb61ed78fd33ce9e6eaf3e0476c2ceac7bba3eb 100644 --- a/sites/all/modules/ctools/tests/ctools_plugin_test.info +++ b/sites/all/modules/ctools/tests/ctools_plugin_test.info @@ -7,11 +7,13 @@ files[] = ctools.plugins.test files[] = object_cache.test files[] = css.test files[] = context.test +files[] = math_expression.test +files[] = math_expression_stack.test hidden = TRUE -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/ctools/tests/math_expression.test b/sites/all/modules/ctools/tests/math_expression.test new file mode 100644 index 0000000000000000000000000000000000000000..730e079a5ca669bcd3bbede34269d498240150a3 --- /dev/null +++ b/sites/all/modules/ctools/tests/math_expression.test @@ -0,0 +1,129 @@ +<?php + +/** + * @file + * Contains \CtoolsMathExpressionTestCase. + */ + +/** + * Tests the MathExpression library of ctools. + */ +class CtoolsMathExpressionTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'CTools math expression tests', + 'description' => 'Test the math expression library of ctools.', + 'group' => 'Chaos Tools Suite', + ); + } + + public function setUp() { + parent::setUp('ctools', 'ctools_plugin_test'); + } + + /** + * Returns a random double between 0 and 1. + */ + protected function rand01() { + return rand(0, PHP_INT_MAX) / PHP_INT_MAX; + } + + /** + * A custom assertion with checks the values in a certain range. + */ + protected function assertFloat($first, $second, $delta = 0.0000001, $message = '', $group = 'Other') { + return $this->assert(abs($first - $second) <= $delta, $message ? $message : t('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group); + } + + public function testArithmetic() { + $math_expression = new ctools_math_expr(); + + // Test constant expressions. + $this->assertEqual($math_expression->e('2'), 2); + $random_number = rand(0, 10); + $this->assertEqual($random_number, $math_expression->e((string) $random_number)); + + // Test simple arithmetic. + $random_number_a = rand(5, 10); + $random_number_b = rand(5, 10); + $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("$random_number_a + $random_number_b")); + $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("$random_number_a - $random_number_b")); + $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("$random_number_a * $random_number_b")); + $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("$random_number_a / $random_number_b")); + + // Test Associative property. + $random_number_c = rand(5, 10); + $this->assertEqual($math_expression->e("$random_number_a + ($random_number_b + $random_number_c)"), $math_expression->e("($random_number_a + $random_number_b) + $random_number_c")); + $this->assertEqual($math_expression->e("$random_number_a * ($random_number_b * $random_number_c)"), $math_expression->e("($random_number_a * $random_number_b) * $random_number_c")); + + // Test Commutative property. + $this->assertEqual($math_expression->e("$random_number_a + $random_number_b"), $math_expression->e("$random_number_b + $random_number_a")); + $this->assertEqual($math_expression->e("$random_number_a * $random_number_b"), $math_expression->e("$random_number_b * $random_number_a")); + + // Test Distributive property. + $this->assertEqual($math_expression->e("($random_number_a + $random_number_b) * $random_number_c"), $math_expression->e("($random_number_a * $random_number_c + $random_number_b * $random_number_c)")); + + $this->assertEqual(pow($random_number_a, $random_number_b), $math_expression->e("$random_number_a ^ $random_number_b")); + } + + public function testBuildInFunctions() { + $math_expression = new ctools_math_expr(); + + // @todo: maybe run this code multiple times to test different values. + $random_double = $this->rand01(); + $random_int = rand(5, 10); + $this->assertFloat(sin($random_double), $math_expression->e("sin($random_double)")); + $this->assertFloat(cos($random_double), $math_expression->e("cos($random_double)")); + $this->assertFloat(tan($random_double), $math_expression->e("tan($random_double)")); + $this->assertFloat(exp($random_double), $math_expression->e("exp($random_double)")); + $this->assertFloat(sqrt($random_double), $math_expression->e("sqrt($random_double)")); + $this->assertFloat(log($random_double), $math_expression->e("ln($random_double)")); + $this->assertFloat(round($random_double), $math_expression->e("round($random_double)")); + $this->assertFloat(abs($random_double + $random_int), $math_expression->e('abs(' . ($random_double + $random_int) . ')')); + $this->assertEqual(round($random_double + $random_int), $math_expression->e('round(' . ($random_double + $random_int) . ')')); + $this->assertEqual(ceil($random_double + $random_int), $math_expression->e('ceil(' . ($random_double + $random_int) . ')')); + $this->assertEqual(floor($random_double + $random_int), $math_expression->e('floor(' . ($random_double + $random_int) . ')')); + + // @fixme: you can't run time without an argument. + $this->assertFloat(time(), $math_expression->e('time(1)')); + + $random_double_a = $this->rand01(); + $random_double_b = $this->rand01(); + // @fixme: max/min don't work at the moment. +// $this->assertFloat(max($random_double_a, $random_double_b), $math_expression->e("max($random_double_a, $random_double_b)")); +// $this->assertFloat(min($random_double_a, $random_double_b), $math_expression->e("min($random_double_a, $random_double_b)")); + } + + public function testVariables() { + $math_expression = new ctools_math_expr(); + + $random_number_a = rand(5, 10); + $random_number_b = rand(5, 10); + + // Store the first random number and use it on calculations. + $math_expression->e("var = $random_number_a"); + $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("var + $random_number_b")); + $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("var * $random_number_b")); + $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("var - $random_number_b")); + $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("var / $random_number_b")); + } + + public function testCustomFunctions() { + $math_expression = new ctools_math_expr(); + + $random_number_a = rand(5, 10); + $random_number_b = rand(5, 10); + + // Create a one-argument function. + $math_expression->e("f(x) = 2 * x"); + $this->assertEqual($random_number_a * 2, $math_expression->e("f($random_number_a)")); + $this->assertEqual($random_number_b * 2, $math_expression->e("f($random_number_b)")); + + // Create a two-argument function. + $math_expression->e("g(x, y) = 2 * x + y"); + $this->assertEqual($random_number_a * 2 + $random_number_b, $math_expression->e("g($random_number_a, $random_number_b)")); + + // Use a custom function in another function. + $this->assertEqual(($random_number_a * 2 + $random_number_b) * 2, $math_expression->e("f(g($random_number_a, $random_number_b))")); + } +} diff --git a/sites/all/modules/ctools/tests/math_expression_stack.test b/sites/all/modules/ctools/tests/math_expression_stack.test new file mode 100644 index 0000000000000000000000000000000000000000..8143a55b82c103fa0fd24a6d58225ee380efc77c --- /dev/null +++ b/sites/all/modules/ctools/tests/math_expression_stack.test @@ -0,0 +1,63 @@ +<?php + +/** + * @file + * Contains \CtoolsMathExpressionStackTestCase + */ + +/** + * Tests the simple MathExpressionStack class. + */ +class CtoolsMathExpressionStackTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'CTools math expression stack tests', + 'description' => 'Test the stack class of the math expression library.', + 'group' => 'Chaos Tools Suite', + ); + } + + public function setUp() { + parent::setUp('ctools', 'ctools_plugin_test'); + } + + public function testStack() { + $stack = new ctools_math_expr_stack(); + + // Test the empty stack. + $this->assertNull($stack->last()); + $this->assertNull($stack->pop()); + + // Add an element and see whether it's the right element. + $value = $this->randomName(); + $stack->push($value); + $this->assertIdentical($value, $stack->last()); + $this->assertIdentical($value, $stack->pop()); + $this->assertNull($stack->pop()); + + // Add multiple elements and see whether they are returned in the right order. + $values = array($this->randomName(), $this->randomName(), $this->randomName()); + foreach ($values as $value) { + $stack->push($value); + } + + // Test the different elements at different positions with the last() method. + $count = count($values); + foreach ($values as $key => $value) { + $this->assertEqual($value, $stack->last($count - $key)); + } + + // Pass in a non-valid number to last. + $non_valid_number = rand(10, 20); + $this->assertNull($stack->last($non_valid_number)); + + // Test the order of the poping. + $values = array_reverse($values); + foreach ($values as $key => $value) { + $this->assertEqual($stack->last(), $value); + $this->assertEqual($stack->pop(), $value); + } + $this->assertNull($stack->pop()); + + } +} diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views.inc index deea7411a7af047fecb4f73041e161f660956b84..262d81defd85d2d7e4fe1fc7fb7050be1fe9dfac 100644 --- a/sites/all/modules/ctools/views_content/plugins/content_types/views.inc +++ b/sites/all/modules/ctools/views_content/plugins/content_types/views.inc @@ -144,7 +144,7 @@ function views_content_views_content_type_render($subtype, $conf, $panel_args, $ list($cid, $converter) = explode('.', $context_info, 2); } if (!empty($contexts[$cid])) { - $arg = ctools_context_convert_context($contexts[$cid], $converter); + $arg = ctools_context_convert_context($contexts[$cid], $converter, array('sanitize' => FALSE)); array_splice($args, $count, 0, array($arg)); } else { diff --git a/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc b/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc index a27a26c588828c38ece9a1974a328d4350e9a5c5..10a6a83d94ca2b5121f1eb627a65868ca8dc0e05 100644 --- a/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc +++ b/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc @@ -156,7 +156,7 @@ function views_content_views_panes_content_type_render($subtype, $conf, $panel_a if (isset($contexts[$key])) { if (strpos($argument['context'], '.')) { list($context, $converter) = explode('.', $argument['context'], 2); - $args[] = ctools_context_convert_context($contexts[$key], $converter); + $args[] = ctools_context_convert_context($contexts[$key], $converter, array('sanitize' => FALSE)); } else { $args[] = $contexts[$key]->argument; diff --git a/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc b/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc index 4da5fe558bc62f7a0816bccb331a005a9598bf60..cefc6dba005df309a1ee10e2002f9cd53dec02a2 100644 --- a/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc +++ b/sites/all/modules/ctools/views_content/plugins/relationships/view_from_argument.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -77,7 +76,7 @@ function views_content_view_from_argument_context($contexts, $conf) { if (isset($contexts [$key])) { if (strpos($argument['context'], '.')) { list($context, $converter) = explode('.', $argument['context'], 2); - $args[] = ctools_context_convert_context($contexts[$key], $converter); + $args[] = ctools_context_convert_context($contexts[$key], $converter, array('sanitize' => FALSE)); } else { $args[] = $contexts[$key]->argument; diff --git a/sites/all/modules/ctools/views_content/views_content.info b/sites/all/modules/ctools/views_content/views_content.info index cd7ce44c580028f25fe14fe8b2585dc24d8f8808..1782d37ce8486d9f113d51a666e59627a49a9739 100644 --- a/sites/all/modules/ctools/views_content/views_content.info +++ b/sites/all/modules/ctools/views_content/views_content.info @@ -9,9 +9,9 @@ files[] = plugins/views/views_content_plugin_display_ctools_context.inc files[] = plugins/views/views_content_plugin_display_panel_pane.inc files[] = plugins/views/views_content_plugin_style_ctools_context.inc -; Information added by drupal.org packaging script on 2012-08-18 -version = "7.x-1.2" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.x-1.3" core = "7.x" project = "ctools" -datestamp = "1345319204" +datestamp = "1365013512" diff --git a/sites/all/modules/date/CHANGELOG.txt b/sites/all/modules/date/CHANGELOG.txt index 38ecaf947210b5b8c4b6092ffb718f387cfcd90c..99e8fb04613a1042bf525889a0a1ef877e226499 100644 --- a/sites/all/modules/date/CHANGELOG.txt +++ b/sites/all/modules/date/CHANGELOG.txt @@ -5,6 +5,30 @@ Date Module 7.x Version 7.x-2.x-dev =================== +====================== +Version 7.x-2.6 +====================== + +- Issue #1423364 by catmat, Add file and path to hook_context_plugins(). +- Issue #1713248, Remove incorrect use of date_popup() function in previous commit. +- Issue #1143680 by kristiaanvandeneynde, Make Date Popup widget able to accept custom settings. +- Issue #1596546 by applicity_sam, Make sure incorrect month name creates validation error. +- Issue #1432702, Fix some miscellaneous problems with date example dates and formats. +- Issue #1512464 by Liam Moreland, Allow for undefined formatter in Date upgrade handling. +- Issue #1659638 by bojanz, Hide install message if Drupal is not installed (i.e. in install profiles). +- Issue #1605158 by covenantd, Be sure value2 is initiated in Date Context handler. +- Issue #1355256 by barraponto develCuy and KarenS, set default value for date text widget increment to 1, and update existing fields. +- Issue #1561306 by Cyberwolf, Date pager replacement was not working correctly when used with multiple dates. +- Issue #1541740 by hass, Make Date requirements more easily translatable. +- Issue #1543524 by iamEAP, Add update hook to remove the D6 date_timezone field from users. +- Issue #1603640 by bevan, Don't return anything for empty date interval. +- Issue #1235508, Make sure that ISO strings with '00' in the month and/or day don't create dates for the previous month and day. +- Issue #1289270 by pdrake: Fixed date arguments and filters do not work with relationships. +- Issue #895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc. +- Issue #606658 by johnmunro and KarenS, Make sure ical import processes multi-day all-day events correctly. +- Issue #1408216 follow up, Need to be sure that NULL is the default state for the sql functions. +- Issue #1540410, Update the Date Tools calendar creation wizard to use the new calendar path. + ====================== Version 7.x-2.5 ====================== diff --git a/sites/all/modules/date/date.field.inc b/sites/all/modules/date/date.field.inc index 5a4fa8134c9282d714f72149b47a3d321665283e..b104702a146acd99b8e4d0d49a8c4ffa12985834 100644 --- a/sites/all/modules/date/date.field.inc +++ b/sites/all/modules/date/date.field.inc @@ -294,12 +294,17 @@ function date_field_widget_info() { 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings, ); + if (module_exists('date_popup')) { $info['date_popup'] = array( 'label' => t('Pop-up calendar'), 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings; } + + // The date text widget should use an increment of 1. + $info['date_text']['increment'] = 1; + return $info; } diff --git a/sites/all/modules/date/date.info b/sites/all/modules/date/date.info index 88eaa6c2ef49f773627a6b863c87376dd83476b2..e5b00c21e51b5ba37f615b2ef82cd28e9359b8f3 100644 --- a/sites/all/modules/date/date.info +++ b/sites/all/modules/date/date.info @@ -10,9 +10,9 @@ files[] = tests/date_field.test files[] = tests/date_validation.test files[] = tests/date_timezone.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date.install b/sites/all/modules/date/date.install index 3f250585c2017ff05de89ff4a67f11633b8c140c..23fb07eb29c1ecb7f5de3208598a674a7a048ebf 100644 --- a/sites/all/modules/date/date.install +++ b/sites/all/modules/date/date.install @@ -160,3 +160,35 @@ function date_update_7003() { module_enable(array('date_repeat_field')); } } + +/** + * Date text widgets should always use an increment of 1. + */ +function date_update_7004() { + + // Select date fields. + $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); + $query->join('field_config', 'fc', 'fc.id = fci.field_id'); + $query->fields('fci'); + $query->condition(db_or()->condition('fc.type', 'date')->condition('fc.type', 'datestamp')->condition('fc.type', 'datetime')); + $results = $query->execute(); + + // Find the ones that use the date_text widget. + foreach ($results as $record) { + $instance = unserialize($record['data']); + if (in_array($instance['widget']['type'], array('date_text'))) { + $instance['widget']['settings']['increment'] = 1; + db_update('field_config_instance') + ->fields(array( + 'data' => serialize($instance), + )) + ->condition('field_name', $record['field_name']) + ->condition('entity_type', $record['entity_type']) + ->condition('bundle', $record['bundle']) + ->execute(); + } + } + field_cache_clear(); + drupal_set_message(t('Date text widgets have been updated to use an increment of 1.')); +} + diff --git a/sites/all/modules/date/date.theme b/sites/all/modules/date/date.theme index 0d28be021f238fbffc34d4b6cb7340403d13c0ba..70c6be41f7b87dcc98801330c5de44c8833dc52c 100644 --- a/sites/all/modules/date/date.theme +++ b/sites/all/modules/date/date.theme @@ -301,7 +301,13 @@ function theme_date_display_interval($variables) { 'interval' => $options['interval'], 'interval_display' => $options['interval_display'], ); - return '<span class="date-display-interval"' . drupal_attributes($attributes) . '>' . theme('date_time_ago', $time_ago_vars) . '</span>'; + + if ($return = theme('date_time_ago', $time_ago_vars)) { + return '<span class="date-display-interval"' . drupal_attributes($attributes) . ">$return</span>"; + } + else { + return ''; + } } /** diff --git a/sites/all/modules/date/date_admin.inc b/sites/all/modules/date/date_admin.inc index f5a345b4e1bfb5f5f05e9e364fb858eb3bf38349..993aa091151606d1fe20a997fef38b568e0ea20a 100644 --- a/sites/all/modules/date/date_admin.inc +++ b/sites/all/modules/date/date_admin.inc @@ -122,6 +122,7 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) $formatter = $display['type']; $format_types = date_format_type_options(); $summary = array(); + $format = FALSE; switch ($formatter) { case 'date_plain': $format = t('Plain'); @@ -130,9 +131,16 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) $format = t('Interval'); break; default: - $format = $format_types[$settings['format_type']]; + if (!empty($format_types[$settings['format_type']])) { + $format = $format_types[$settings['format_type']]; + } + } + if ($format) { + $summary[] = t('Display dates using the @format format', array('@format' => $format)); + } + else { + $summary[] = t('Display dates using the default format because the specified format (@format) is not defined', array('@format' => $settings['format_type'])); } - $summary[] = t('Display dates using the @format format', array('@format' => $format)); if (array_key_exists('fromto', $settings) && $field['settings']['todate']) { $options = array( diff --git a/sites/all/modules/date/date_all_day/date_all_day.info b/sites/all/modules/date/date_all_day/date_all_day.info index ba5d0f185e26f1f8b8a7177db83c447a2be6ad06..8f8bf48b72019bcbe73b831cdd4900629f4a431d 100644 --- a/sites/all/modules/date/date_all_day/date_all_day.info +++ b/sites/all/modules/date/date_all_day/date_all_day.info @@ -5,9 +5,9 @@ dependencies[] = date package = Date/Time core = 7.x -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_api/date_api.info b/sites/all/modules/date/date_api/date_api.info index 09c0983ba95656b9ecbe8dd35240987f2e393db0..ee1a38423bf2c5de0b06785ee9974dced551fac7 100644 --- a/sites/all/modules/date/date_api/date_api.info +++ b/sites/all/modules/date/date_api/date_api.info @@ -9,9 +9,9 @@ stylesheets[all][] = date.css files[] = date_api.module files[] = date_api_sql.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_api/date_api.install b/sites/all/modules/date/date_api/date_api.install index d2bfbb36794f2c1d00565a4a017f68a24ea51c20..ce5b746dd77b95e7c84cdf37ec22708afaf7a614 100644 --- a/sites/all/modules/date/date_api/date_api.install +++ b/sites/all/modules/date/date_api/date_api.install @@ -61,17 +61,20 @@ function date_api_requirements($phase) { * Implements hook_install(). */ function date_api_install() { - // Ensure translations don't break at install time. - $t = get_t(); + // Only set the message if Drupal itself is already installed. + if (variable_get('install_task') == 'done') { + // Ensure translations don't break at install time. + $t = get_t(); - // date_api_set_variables can install date_timezone. The - // date_timezone_install() function does a module_enable('date_api'). This - // means that date_api_enable() can be called before date_api_install() - // finishes! So the date_api schema needs to be installed before this line! - date_api_set_variables(); + // date_api_set_variables can install date_timezone. The + // date_timezone_install() function does a module_enable('date_api'). This + // means that date_api_enable() can be called before date_api_install() + // finishes! So the date_api schema needs to be installed before this line! + date_api_set_variables(); - $message = $t('The Date API requires that you set up the !timezone_link and the !format_link to function correctly.', array('!timezone_link' => l($t('site timezone and first day of week settings'), 'admin/config/regional/settings'), '!format_link' => l($t('date format settings'), 'admin/config/regional/date-time'))); - drupal_set_message(filter_xss_admin($message), 'warning'); + $message = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone and first day of week settings</a> and the <a href="@regional_date_time">date format settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'), '@regional_date_time' => url('admin/config/regional/date-time'))); + drupal_set_message(filter_xss_admin($message), 'warning'); + } } /** @@ -237,3 +240,13 @@ function date_api_update_7000() { db_drop_table('d6_date_format_locale'); } } + + +/** + * Drop D6 timezone_name field on {users} after upgrading to D7. + */ +function date_api_update_7001() { + if (db_field_exists('users', 'timezone_name')) { + db_drop_field('users', 'timezone_name'); + } +} diff --git a/sites/all/modules/date/date_api/date_api.module b/sites/all/modules/date/date_api/date_api.module index b8c95f775fb68ed1d58dde4a191e73b886ce8c9d..d85a8a5e5a132400f59f18373ec1d4a47f9d413c 100644 --- a/sites/all/modules/date/date_api/date_api.module +++ b/sites/all/modules/date/date_api/date_api.module @@ -83,28 +83,28 @@ function date_api_status() { $value = variable_get('date_default_timezone'); if (isset($value)) { - $success_messages[] = $t('The timezone has been set to !value_link.', array('!value_link' => l($value, 'admin/config/regional/settings'))); + $success_messages[] = $t('The timezone has been set to <a href="@regional_settings">@timezone</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@timezone' => $value)); } else { - $error_messages[] = $t('The Date API requires that you set up the !timezone_link to function correctly.', array('!timezone_link' => l($t('site timezone'), 'admin/config/regional/settings'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'))); } $value = variable_get('date_first_day'); if (isset($value)) { $days = date_week_days(); - $success_messages[] = $t('The first day of the week has been set to !value_link.', array('!value_link' => l($days[$value], 'admin/config/regional/settings'))); + $success_messages[] = $t('The first day of the week has been set to <a href="@regional_settings">@day</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@day' => $days[$value])); } else { - $error_messages[] = $t('The Date API requires that you set up the !first_day_link to function correctly.', array('!first_day_link' => l($t('site first day of week settings'), 'admin/config/regional/settings'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site first day of week settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'))); } $value = variable_get('date_format_medium'); if (isset($value)) { $now = date_now(); - $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at !value_link.', array('@value' => $now->format($value), '!value_link' => l(t('Date and time'), 'admin/config/regional/date-time'))); + $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at <a href="@regional_date_time">Date and time</a> settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); } else { - $error_messages[] = $t('The Date API requires that you set up the !format_link to function correctly.', array('!format_link' => l($t('system date formats'), 'admin/config/regional/date-time'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_date_time">system date formats</a> to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time'))); } return array('errors', $error_messages, 'success' => $success_messages); @@ -185,7 +185,7 @@ class DateObject extends DateTime { * Constructs a date object. * * @param string $time - * A date/time string. Defaults to 'now'. + * A date/time string or array. Defaults to 'now'. * @param object|string|null $tz * PHP DateTimeZone object, string or NULL allowed. Defaults to NULL. * @param string $format @@ -198,6 +198,9 @@ class DateObject extends DateTime { $this->timeOnly = FALSE; $this->dateOnly = FALSE; + // Store the raw time input so it is available for validation. + $this->originalTime = $time; + // Allow string timezones. if (!empty($tz) && !is_object($tz)) { $tz = new DateTimeZone($tz); @@ -221,8 +224,7 @@ class DateObject extends DateTime { $format = DATE_FORMAT_DATETIME; $this->addGranularity('timezone'); } - - if (is_array($time)) { + elseif (is_array($time)) { // Assume we were passed an indexed array. if (empty($time['year']) && empty($time['month']) && empty($time['day'])) { $this->timeOnly = TRUE; @@ -237,6 +239,12 @@ class DateObject extends DateTime { // We checked for errors already, skip parsing the input values. $format = NULL; } + else { + // Make sure dates like 2010-00-00T00:00:00 get converted to + // 2010-01-01T00:00:00 before creating a date object + // to avoid unintended changes in the month or day. + $time = date_make_iso_valid($time); + } // The parse function will also set errors on the date parts. if (!empty($format)) { @@ -590,12 +598,12 @@ class DateObject extends DateTime { break; case 'F': $array_month_long = array_flip(date_month_names()); - $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : ''; + $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1; $this->addGranularity('month'); break; case 'M': $array_month = array_flip(date_month_names_abbr()); - $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : ''; + $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1; $this->addGranularity('month'); break; case 'Y': @@ -2667,3 +2675,33 @@ function date_is_date($date) { } return TRUE; } + +/** + * This function will replace ISO values that have the pattern 9999-00-00T00:00:00 + * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO + * dates and ensure that date objects created from this value contain a valid month + * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as + * November 30, 2019 (the previous day in the previous month). + * + * @param string $iso_string + * An ISO string that needs to be made into a complete, valid date. + * + * @TODO Expand on this to work with all sorts of partial ISO dates. + */ +function date_make_iso_valid($iso_string) { + // If this isn't a value that uses an ISO pattern, there is nothing to do. + if (is_numeric($iso_string) || !preg_match(DATE_REGEX_ISO, $iso_string)) { + return $iso_string; + } + // First see if month and day parts are '-00-00'. + if (substr($iso_string, 4, 6) == '-00-00') { + return preg_replace('/([\d]{4}-)(00-00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01-01${3}', $iso_string); + } + // Then see if the day part is '-00'. + elseif (substr($iso_string, 7, 3) == '-00') { + return preg_replace('/([\d]{4}-[\d]{2}-)(00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01${3}', $iso_string); + } + + // Fall through, no changes required. + return $iso_string; +} diff --git a/sites/all/modules/date/date_api/date_api_elements.inc b/sites/all/modules/date/date_api/date_api_elements.inc index b5a90a0e8a9e37c8e39b637b6c5c1446a86aac8a..7da4e5892c79dbc4c9997f202ee69e161b1be6cf 100644 --- a/sites/all/modules/date/date_api/date_api_elements.inc +++ b/sites/all/modules/date/date_api/date_api_elements.inc @@ -294,7 +294,7 @@ function date_text_element_value_callback($element, $input = FALSE, &$form_state $date = date_default_date($element); } if (date_is_date($date)) { - $return['date'] = $date->format($element['#date_format']); + $return['date'] = date_format_date($date, 'custom', $element['#date_format']); } return $return; } @@ -320,7 +320,7 @@ function date_text_element_process($element, &$form_state, $form) { $element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight']; $element['date']['#attributes'] = array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-date') : array('date-date')); $now = date_example_date(); - $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_now()->format($element['#date_format']))); + $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_format_date(date_example_date(), 'custom', $element['#date_format']))); $element['date']['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; // Keep the system from creating an error message for the sub-element. diff --git a/sites/all/modules/date/date_api/date_api_ical.inc b/sites/all/modules/date/date_api/date_api_ical.inc index 7cb2b957745d9d7ed22459e15c4e7d84484dc0f5..2ca484e7fdc320b525ad9fc1b7cdc5c28daba4d5 100644 --- a/sites/all/modules/date/date_api/date_api_ical.inc +++ b/sites/all/modules/date/date_api/date_api_ical.inc @@ -215,15 +215,31 @@ function date_ical_parse($icaldatafolded = array()) { // even when you are working with only a portion of the VEVENT // array, like in Feed API parsers. $subgroup['all_day'] = FALSE; - if (!empty($subgroup['DTSTART']) && (!empty($subgroup['DTSTART']['all_day']) || - (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT'])))) { - // Many programs output DTEND for an all day event as the - // following day, reset this to the same day for internal use. - $subgroup['all_day'] = TRUE; + + // iCal spec states 'The "DTEND" property for a "VEVENT" calendar + // component specifies the non-inclusive end of the event'. Adjust + // multi-day events to remove the extra day because the Date code + // assumes the end date is inclusive. + if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) { + // Make the end date one day earlier. + $date = new DateObject ($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); + date_modify($date, '-1 day'); + $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); + } + // If a start datetime is defined AND there is no definition for + // the end datetime THEN make the end datetime equal the start + // datetime and if it is an all day event define the entire event + // as a single all day event. + if (!empty($subgroup['DTSTART']) && + (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT']))) { $subgroup['DTEND'] = $subgroup['DTSTART']; } // Add this element to the parent as an array under the component // name. + if (!empty($subgroup['DTSTART']['all_day'])) { + $subgroup['all_day'] = TRUE; + } + // Add this element to the parent as an array under the prev($subgroups); $parent = &$subgroups[key($subgroups)]; @@ -291,6 +307,7 @@ function date_ical_parse($icaldatafolded = array()) { $parse_result = date_ical_parse_rrule($field, $data); break; + case 'STATUS': case 'SUMMARY': case 'DESCRIPTION': $parse_result = date_ical_parse_text($field, $data); @@ -712,9 +729,12 @@ function date_api_ical_build_rrule($form_values) { // We only collect a date for UNTIL, but we need it to be inclusive, so // force it to a full datetime element at the last second of the day. if (!is_object($form_values['UNTIL']['datetime'])) { - $form_values['UNTIL']['datetime'] .= ' 23:59:59'; - $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); - $form_values['UNTIL']['all_day'] = 0; + // If this is a date without time, give it time. + if (strlen($form_values['UNTIL']['datetime']) < 11) { + $form_values['UNTIL']['datetime'] .= ' 23:59:59'; + $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $form_values['UNTIL']['all_day'] = FALSE; + } $until = date_ical_date($form_values['UNTIL'], 'UTC'); } else { @@ -745,7 +765,7 @@ function date_api_ical_build_rrule($form_values) { foreach ($form_values['EXDATE'] as $value) { if (!empty($value['datetime'])) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? $date->format(DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } @@ -765,7 +785,7 @@ function date_api_ical_build_rrule($form_values) { $ex_dates = array(); foreach ($form_values['RDATE'] as $value) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? $date->format(DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } diff --git a/sites/all/modules/date/date_api/date_api_sql.inc b/sites/all/modules/date/date_api/date_api_sql.inc index ede8b1ef33fbd1071b828de1c863078693b300d9..a2adc652b414c3f72ae3e8b91af3735b6aeab9af 100644 --- a/sites/all/modules/date/date_api/date_api_sql.inc +++ b/sites/all/modules/date/date_api/date_api_sql.inc @@ -598,7 +598,7 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_date($type, $field, $operator, $value, $adjustment = 0) { + function sql_where_date($type, $field, $operator, $value, $adjustment = NULL) { $type = strtoupper($type); if (strtoupper($value) == 'NOW') { $value = $this->sql_field('NOW', $adjustment); @@ -644,12 +644,12 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_extract($part, $field, $operator, $value) { - if ($this->local_timezone != $this->db_timezone) { + function sql_where_extract($part, $field, $operator, $value, $adjustment = NULL) { + if (empty($adjustment) && $this->local_timezone != $this->db_timezone) { $field = $this->sql_field($field); } else { - $field = $this->sql_field($field, 0); + $field = $this->sql_field($field, $adjustment); } return $this->sql_extract($part, $field) . " $operator $value"; } @@ -670,12 +670,12 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_format($format, $field, $operator, $value) { - if ($this->local_timezone != $this->db_timezone) { + function sql_where_format($format, $field, $operator, $value, $adjustment = NULL) { + if (empty($adjustment) && $this->local_timezone != $this->db_timezone) { $field = $this->sql_field($field); } else { - $field = $this->sql_field($field, 0); + $field = $this->sql_field($field, $adjustment); } return $this->sql_format($format, $field) . " $operator '$value'"; } diff --git a/sites/all/modules/date/date_context/date_context.info b/sites/all/modules/date/date_context/date_context.info index 63f012901831d930e3b9a4f374a3673df64143c4..a69a599b74ee048a9babe66521e4772a57e6b071 100644 --- a/sites/all/modules/date/date_context/date_context.info +++ b/sites/all/modules/date/date_context/date_context.info @@ -8,9 +8,9 @@ dependencies[] = context files[] = date_context.module files[] = plugins/date_context_date_condition.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_context/date_context.module b/sites/all/modules/date/date_context/date_context.module index a5d265084083123a4a40fe63b5f55121f3a84037..44e975acd84ecbe39175584bcdc007a7d3a46591 100644 --- a/sites/all/modules/date/date_context/date_context.module +++ b/sites/all/modules/date/date_context/date_context.module @@ -30,6 +30,8 @@ function date_context_context_plugins() { 'handler' => array( 'class' => 'date_context_date_condition', 'parent' => 'context_condition_node', + 'path' => drupal_get_path('module', 'date_context') . '/plugins', + 'file' => 'date_context_date_condition.inc', ), ); return $plugins; diff --git a/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc b/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc index 3fcb042857105e04007ad75afb0ff4b1a3dce4e6..733fb5a84709a4e6aaeae24f7c390406f3d7f3e3 100644 --- a/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc +++ b/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc @@ -82,6 +82,9 @@ class date_context_date_condition extends context_condition_node { $date = new DateObject($item['value'], $timezone_db); date_timezone_set($date, timezone_open($timezone)); $date1 = $date->format(DATE_FORMAT_DATETIME); + if (empty($item['value2'])) { + $item['value2'] = $item['value']; + } $date = new DateObject($item['value2'], $timezone_db); date_timezone_set($date, timezone_open($timezone)); $date2 = $date->format(DATE_FORMAT_DATETIME); diff --git a/sites/all/modules/date/date_migrate/date_migrate.info b/sites/all/modules/date/date_migrate/date_migrate.info index 487fec8446743635a0395b76d7e667b6d545a47d..c0d536f5b7090acc08aacce717f48f00bd702348 100644 --- a/sites/all/modules/date/date_migrate/date_migrate.info +++ b/sites/all/modules/date/date_migrate/date_migrate.info @@ -8,9 +8,9 @@ dependencies[] = date files[] = date.migrate.inc files[] = date_migrate.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info b/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info index ad43d983fa49cd94ecf8f245e24e2b002bb2f496..b54a3f7515d045c4dd048224a7cf34d81202e77a 100644 --- a/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info +++ b/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info @@ -21,9 +21,9 @@ package = "Features" project = "date_migrate_example" version = "7.x-2.0" -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_popup/date_popup.info b/sites/all/modules/date/date_popup/date_popup.info index f781721292ad9385fb9cee69af3564fc59c8fcb5..767aacac22470bc8ae08115ea6841a5700c4cf6c 100644 --- a/sites/all/modules/date/date_popup/date_popup.info +++ b/sites/all/modules/date/date_popup/date_popup.info @@ -7,9 +7,9 @@ configure = admin/config/date/date_popup stylesheets[all][] = themes/datepicker.1.7.css -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_popup/date_popup.module b/sites/all/modules/date/date_popup/date_popup.module index aa780333361d3ebf464df26e55a0e2decbc704d7..ca292ef9ad75c83fe9d90422fa2d36260c537c8f 100644 --- a/sites/all/modules/date/date_popup/date_popup.module +++ b/sites/all/modules/date/date_popup/date_popup.module @@ -185,6 +185,15 @@ function date_popup_theme() { * The number of years to go back and forward in a year selector, * default is -3:+3 (3 back and 3 forward). * + * #datepicker_options + * An associative array representing the jQuery datepicker options you want + * to set for this element. Use the jQuery datepicker option names as keys. + * Hard coded defaults are: + * - changeMonth => TRUE + * - changeYear => TRUE + * - autoPopUp => 'focus' + * - closeAtTop => FALSE + * - speed => 'immediate' */ function date_popup_element_info() { $timepicker = date_popup_get_preferred_timepicker(); @@ -194,6 +203,7 @@ function date_popup_element_info() { '#date_timezone' => date_default_timezone(), '#date_flexible' => 0, '#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'), + '#datepicker_options' => array(), '#timepicker' => variable_get('date_popup_timepicker', $timepicker), '#date_increment' => 1, '#date_year_range' => '-3:+3', @@ -317,20 +327,22 @@ function date_popup_process_date_part(&$element) { $range = date_range_years($element['#date_year_range'], $date); $year_range = date_range_string($range); - $settings = array( + // Add the dynamic datepicker options. Allow element-specific datepicker + // preferences to override these options for whatever reason they see fit. + $settings = $element['#datepicker_options'] + array( 'changeMonth' => TRUE, 'changeYear' => TRUE, - 'firstDay' => intval(variable_get('date_first_day', 0)), - //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", - //'buttonImageOnly' => TRUE, 'autoPopUp' => 'focus', 'closeAtTop' => FALSE, 'speed' => 'immediate', + 'firstDay' => intval(variable_get('date_first_day', 0)), + //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", + //'buttonImageOnly' => TRUE, 'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'), 'yearRange' => $year_range, // Custom setting, will be expanded in Drupal.behaviors.date_popup() 'fromTo' => isset($fromto), - ); + ); // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings); @@ -353,7 +365,7 @@ function date_popup_process_date_part(&$element) { ); $sub_element['#value'] = $sub_element['#default_value']; // TODO, figure out exactly when we want this description. In many places it is not desired. - $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_now(), 'custom', date_popup_date_format($element)))); + $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_example_date(), 'custom', date_popup_date_format($element)))); return $sub_element; } diff --git a/sites/all/modules/date/date_repeat/date_repeat.info b/sites/all/modules/date/date_repeat/date_repeat.info index c1ff662e19769a6c144bdf220097953430ca633f..1d1720a47f82467b75afda26f91a26000d0f8aca 100644 --- a/sites/all/modules/date/date_repeat/date_repeat.info +++ b/sites/all/modules/date/date_repeat/date_repeat.info @@ -7,9 +7,9 @@ php = 5.2 files[] = tests/date_repeat.test files[] = tests/date_repeat_form.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_repeat_field/date_repeat_field.info b/sites/all/modules/date/date_repeat_field/date_repeat_field.info index 0b57d62d74d320f302bdb706fa41e02a02836397..803105c24ccc8a2d49f53cd04ff4f7da0c59a296 100644 --- a/sites/all/modules/date/date_repeat_field/date_repeat_field.info +++ b/sites/all/modules/date/date_repeat_field/date_repeat_field.info @@ -7,9 +7,9 @@ stylesheets[all][] = date_repeat_field.css package = Date/Time core = 7.x -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_tools/date_tools.info b/sites/all/modules/date/date_tools/date_tools.info index a34dcc8339b8eb035e768d0af587a0c4ee14ae99..3f392a4257d310a1fcc0e6d2a25a5dba1b247bd2 100644 --- a/sites/all/modules/date/date_tools/date_tools.info +++ b/sites/all/modules/date/date_tools/date_tools.info @@ -6,9 +6,9 @@ core = 7.x configure = admin/config/date/tools files[] = tests/date_tools.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_tools/date_tools.wizard.inc b/sites/all/modules/date/date_tools/date_tools.wizard.inc index 034cf3756494507dbdadc360dd4624ec2eade829..14bc2759ead0ffe598303ff3a0690ca7f8bcf849 100644 --- a/sites/all/modules/date/date_tools/date_tools.wizard.inc +++ b/sites/all/modules/date/date_tools/date_tools.wizard.inc @@ -264,7 +264,7 @@ function date_tools_wizard_build($form_values) { $field = field_create_field($field); $instance = field_create_instance($instance); - $view_name = 'calendar_' . $field_name; + $view_name = 'calendar_node_' . $field_name; field_info_cache_clear(TRUE); field_cache_clear(TRUE); diff --git a/sites/all/modules/date/date_views/date_views.info b/sites/all/modules/date/date_views/date_views.info index 9216f8e01a38d8ea2f3e18e4415ef42e84134576..ef7da6c0c137f27ea2a3d19c93c5b828f6efbc19 100644 --- a/sites/all/modules/date/date_views/date_views.info +++ b/sites/all/modules/date/date_views/date_views.info @@ -13,9 +13,9 @@ files[] = includes/date_views.views_default.inc files[] = includes/date_views.views.inc files[] = includes/date_views_plugin_pager.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc b/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc index bba39a6be8380d817004ede7eb15fac684e861b4..61aeafc1802b1bcf50bc25c8f8206b1bf1fe7bd8 100644 --- a/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc +++ b/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc @@ -150,6 +150,9 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->query->set_where_group($this->options['date_method'], $this->options['date_group']); $this->granularity = $this->date_handler->arg_granularity($this->argument); $format = $this->date_handler->views_formats($this->granularity, 'sql'); + + $this->placeholders = array(); + if (!empty($this->query_fields)) { // Use set_where_group() with the selected date_method // of 'AND' or 'OR' to create the where clause. @@ -161,7 +164,7 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->table = $field['table_name']; $this->original_table = $field['table_name']; if ($field['table_name'] != $this->table || !empty($this->relationship)) { - $this->table = $this->query->queue_table($field['table_name'], $this->relationship); + $this->table = $this->query->ensure_table($field['table_name'], $this->relationship); } // $this->table_alias gets set when the first field is processed if otherwise empty. // For subsequent fields, we need to be sure it is emptied again. @@ -169,6 +172,8 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->table_alias = NULL; } parent::query($group_by); + + $this->placeholders = array_merge($this->placeholders, $this->date_handler->placeholders); } } } diff --git a/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc b/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc index 63467a2615b1cce2148b00394de3eb64d7b194b3..5eb5ebc90242a59c86acb7bbadbf43279949b7e4 100644 --- a/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc +++ b/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc @@ -58,7 +58,7 @@ class date_views_filter_handler extends date_views_filter_handler_simple { // Respect relationships when determining the table alias. if ($field['table_name'] != $this->table || !empty($this->relationship)) { - $this->related_table_alias = $this->query->queue_table($field['table_name'], $this->relationship); + $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); } $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; $field_name = $table_alias . '.' . $field['field_name']; @@ -174,4 +174,4 @@ class date_views_filter_handler extends date_views_filter_handler_simple { } } } -} \ No newline at end of file +} diff --git a/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc b/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc index 10c6581be39c74f5e4c9b6f05768b3dc1b1b3e5f..f9594a72f208e001a8cdadf64b89d5d5fee58b71 100644 --- a/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc +++ b/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc @@ -168,7 +168,7 @@ class date_views_plugin_pager extends views_plugin_pager { if (empty($this->view->date_info)) $this->view->date_info = new stdClass(); $this->view->date_info->granularity = $argument->date_handler->granularity; $format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format; - $this->view->date_info->placeholders = $argument->date_handler->placeholders; + $this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders; $this->view->date_info->date_arg = $argument->argument; $this->view->date_info->date_arg_pos = $i; $this->view->date_info->year = date_format($argument->min_date, 'Y'); diff --git a/sites/all/modules/date/tests/date_api.test b/sites/all/modules/date/tests/date_api.test index aa39ac319abf32581eda851554b295f6e075df39..f50020c15db78d7b356360e1b47f466785cc29a3 100644 --- a/sites/all/modules/date/tests/date_api.test +++ b/sites/all/modules/date/tests/date_api.test @@ -388,6 +388,14 @@ class DateAPITestCase extends DrupalWebTestCase { foreach ($invalid as $range) { $this->assertFalse(date_range_valid($range), "$range recognized as an invalid date range."); } + + // Test for invalid month names when we are using a short version of the month + $input = '23 abc 2012'; + $timezone = NULL; + $format = 'd M Y'; + $date = new dateObject($input, $timezone, $format); + $this->assertNotEqual(count($date->errors), 0, '23 abc 2012 should be an invalid date'); + } /** diff --git a/sites/all/modules/diff b/sites/all/modules/diff new file mode 160000 index 0000000000000000000000000000000000000000..adb43040a4e8f950ae604b7d3698f493075cb04f --- /dev/null +++ b/sites/all/modules/diff @@ -0,0 +1 @@ +Subproject commit adb43040a4e8f950ae604b7d3698f493075cb04f diff --git a/sites/all/modules/form_builder/examples/form_builder_examples.info b/sites/all/modules/form_builder/examples/form_builder_examples.info index 373b881017af301f7b7a6eefe90db30ca7dd0f92..8523dd5388c94e0d0a1f40a390a9278d06152154 100644 --- a/sites/all/modules/form_builder/examples/form_builder_examples.info +++ b/sites/all/modules/form_builder/examples/form_builder_examples.info @@ -3,9 +3,9 @@ description = Form builder support for CCK, Webform, and Profile modules. core = 7.x dependencies[] = form_builder -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/examples/form_builder_examples.module b/sites/all/modules/form_builder/examples/form_builder_examples.module index 4766f8bfcbc3d31e64f502ddb79764726533a0d2..e3407b2dee42635207589cce27ead16fd213f819 100644 --- a/sites/all/modules/form_builder/examples/form_builder_examples.module +++ b/sites/all/modules/form_builder/examples/form_builder_examples.module @@ -446,10 +446,10 @@ function form_builder_examples_export_recurse($form, $parents = array()) { } else { if (($property == '#title') || ($property == '#description')) { - $output .= " '". $property . "' => t('" . $form[$property] ."'),\n"; + $output .= " '". $property . "' => t('" . str_replace("'", "\'", $form[$property]) ."'),\n"; } else { - $output .= " '". $property . "' => '" . $form[$property] ."',\n"; + $output .= " '". $property . "' => '" . str_replace("'", "\'", $form[$property]) ."',\n"; } } } diff --git a/sites/all/modules/form_builder/form_builder.info b/sites/all/modules/form_builder/form_builder.info index 20e893b27fe3630a13c2d0c58aa738d3f0e0f475..7a0ed0db0e1f64cd9d4e937cf2c7ab170012c1ff 100644 --- a/sites/all/modules/form_builder/form_builder.info +++ b/sites/all/modules/form_builder/form_builder.info @@ -3,9 +3,9 @@ description = Form building framework. dependencies[] = options_element core = 7.x -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/form_builder.install b/sites/all/modules/form_builder/form_builder.install index 04d5e5bfc4de132b061dffab71815526f26273cf..c7e691e6e83e3ee52bc4b2820b8a3cb9c4394892 100644 --- a/sites/all/modules/form_builder/form_builder.install +++ b/sites/all/modules/form_builder/form_builder.install @@ -21,6 +21,7 @@ function form_builder_requirements($phase) { if (empty($form_builder_types)) { $requirements['form_builder_types']['title'] = $t('Form builder'); $requirements['form_builder_types']['severity'] = REQUIREMENT_ERROR; + $requirements['form_builder_types']['value'] = $t('No dependent modules found.'); $requirements['form_builder_types']['description'] = t('Form builder module is installed but no modules implement support for it. You may want to disable Form builder module until it is needed.'); } } diff --git a/sites/all/modules/form_builder/form_builder.js b/sites/all/modules/form_builder/form_builder.js index 5968e7fadba4ce554fccfa6495369f21ac7d55cc..f49159093abb785690ade4f683cdd6bfb6cd5714 100644 --- a/sites/all/modules/form_builder/form_builder.js +++ b/sites/all/modules/form_builder/form_builder.js @@ -546,8 +546,11 @@ Drupal.formBuilder.addElement = function(response) { // Set the variable stating we're done updating. Drupal.formBuilder.updatingElement = false; - // Insert the new position form containing the new element. + // Insert the new position form containing the new element, but maintain + // the existing form action. + var positionAction = $('#form-builder-positions').attr('action'); $('#form-builder-positions').replaceWith(response.positionForm); + $('#form-builder-positions').attr('action', positionAction); // Submit the new positions form to save the new element position. Drupal.formBuilder.updateElementPosition($new.get(0)); diff --git a/sites/all/modules/form_builder/includes/form_builder.admin.inc b/sites/all/modules/form_builder/includes/form_builder.admin.inc index 5f0e1a2b2cf63481eb92863d3910144f2ec70a3d..478ec3afd31f237c47cc16e09f23e9b81f7578c9 100644 --- a/sites/all/modules/form_builder/includes/form_builder.admin.inc +++ b/sites/all/modules/form_builder/includes/form_builder.admin.inc @@ -67,14 +67,14 @@ function form_builder_add_page($form_type, $form_id, $element_type) { if (isset($_REQUEST['js'])) { $element = form_builder_cache_field_load($form_type, $form_id, $element_id); - $preview_form = form_builder_cache_load($form_type, $form_id); + $form_cache = form_builder_cache_load($form_type, $form_id); $data = array( 'formType' => $form_type, 'formId' => $form_id, 'elementId' => $element_id, 'html' => form_builder_field_render($form_type, $form_id, $element_id), - 'positionForm' => drupal_render(drupal_get_form('form_builder_positions', $preview_form, $form_type, $form_id)), + 'positionForm' => drupal_render(drupal_get_form('form_builder_positions', $form_cache, $form_type, $form_id)), ); form_builder_json_output($data); @@ -147,7 +147,7 @@ function form_builder_field_palette() { $groups = module_invoke_all('form_builder_palette_groups'); // TODO: We shouldn't have to clear the cache here. $form = form_builder_cache_load($active['form_type'], $active['form_id'], NULL, TRUE); - $active_fields = form_builder_get_element_ids($form); + $active_fields = form_builder_get_element_types($form); foreach ($fields as $key => $field) { if ($field['unique'] && in_array($key, $active_fields)) { $fields[$key]['in_use'] = TRUE; @@ -199,6 +199,9 @@ function form_builder_preview($f, &$form_state, $form, $form_type, $form_id) { $form['#attached']['js'][] = 'misc/form.js'; $form['#attached']['js'][] = 'misc/collapse.js'; + $form['#attached']['js'][] = drupal_get_path('module', 'filter') . '/filter.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'filter') . '/filter.css'; + $form['#attached']['js'][] = array('data' => array('machineName' => array()), 'type' => 'setting'); $form['#attached']['js'][] = 'misc/machine-name.js'; @@ -220,17 +223,17 @@ function form_builder_preview($f, &$form_state, $form, $form_type, $form_id) { /** * Form containing all the current weights and parents of elements. */ -function form_builder_positions($form, &$form_state, $preview_form, $form_type, $form_id) { +function form_builder_positions($form, &$form_state, $form_cache, $form_type, $form_id) { $form = array( '#tree' => TRUE, '#form_builder' => array( 'form_type' => $form_type, 'form_id' => $form_id, - 'form' => $preview_form, + 'form' => $form_cache, ), ); - form_builder_positions_prepare($form, $preview_form); + _form_builder_positions_prepare($form, $form_cache); // Drupal MUST have a button to register submissions. // Add a button even though the form is only submitted via AJAX. @@ -245,30 +248,30 @@ function form_builder_positions($form, &$form_state, $preview_form, $form_type, /** * Recursive helper for form_builder_positions(). Add weight fields. */ -function form_builder_positions_prepare(&$form, &$preview_form, $parent_id = FORM_BUILDER_ROOT) { - foreach (element_children($preview_form) as $key) { +function _form_builder_positions_prepare(&$form, $form_cache, $parent_id = FORM_BUILDER_ROOT) { + foreach (element_children($form_cache) as $key) { // Keep record of the current parent ID. $previous_parent_id = $parent_id; - if (isset($preview_form[$key]['#form_builder']['element_id'])) { + if (isset($form_cache[$key]['#form_builder']['element_id'])) { // Set the parent ID for this element. - $preview_form[$key]['#form_builder']['parent_id'] = $parent_id; - $element_id = $preview_form[$key]['#form_builder']['element_id']; + $form_cache[$key]['#form_builder']['parent_id'] = $parent_id; + $element_id = $form_cache[$key]['#form_builder']['element_id']; $parent_id = $element_id; $form[$element_id]['weight'] = array( '#type' => 'hidden', - '#default_value' => isset($preview_form[$key]['#weight']) ? $preview_form[$key]['#weight'] : 0, + '#default_value' => isset($form_cache[$key]['#weight']) ? $form_cache[$key]['#weight'] : 0, '#attributes' => array('class' => array('form-builder-weight form-builder-element-' . $element_id)), ); $form[$element_id]['parent'] = array( '#type' => 'hidden', - '#default_value' => $preview_form[$key]['#form_builder']['parent_id'], + '#default_value' => $form_cache[$key]['#form_builder']['parent_id'], '#attributes' => array('class' => array('form-builder-parent form-builder-element-' . $element_id)), ); } - form_builder_positions_prepare($form, $preview_form[$key], $parent_id); + _form_builder_positions_prepare($form, $form_cache[$key], $parent_id); $parent_id = $previous_parent_id; } } @@ -282,7 +285,7 @@ function form_builder_positions_submit(&$form, &$form_state) { $form_type = $form['#form_builder']['form_type']; $form_id = $form['#form_builder']['form_id']; - $preview_form = $form['#form_builder']['form']; + $form_cache = $form['#form_builder']['form']; foreach (element_children($form) as $element_id) { // Skip items without weight value (like the form token, build_id, etc). @@ -291,14 +294,17 @@ function form_builder_positions_submit(&$form, &$form_state) { } // Check for changed weights or parents. - $element = form_builder_get_element($preview_form, $element_id); + $element = form_builder_get_element($form_cache, $element_id); $element['#weight'] = $form_state['values'][$element_id]['weight']; $element['#form_builder']['parent_id'] = $form_state['values'][$element_id]['parent']; - form_builder_set_element($preview_form, $element); + form_builder_set_element($form_cache, $element); } // Save all the changes made. - form_builder_cache_save($form_type, $form_id, $preview_form); + form_builder_cache_save($form_type, $form_id, $form_cache); + + // Don't redirect, which will cause an unnecessary HTTP request. + $form_state['redirect'] = FALSE; } /** diff --git a/sites/all/modules/form_builder/includes/form_builder.api.inc b/sites/all/modules/form_builder/includes/form_builder.api.inc index b22cb4a2240c6120d9dace19cfb4b0256c41c30c..700808873d2de8c1a222869a1dfced675e4bdbde 100644 --- a/sites/all/modules/form_builder/includes/form_builder.api.inc +++ b/sites/all/modules/form_builder/includes/form_builder.api.inc @@ -278,6 +278,22 @@ function form_builder_get_element_ids($form) { return $element_ids; } +/** + * Recursive function to get the types of all element within a form. + */ +function form_builder_get_element_types($form) { + $element_types = array(); + foreach (element_children($form) as $key) { + if (isset($form[$key]['#form_builder']['element_type'])) { + $element_types[] = $form[$key]['#form_builder']['element_type']; + } + $additional_types = form_builder_get_element_types($form[$key]); + $element_types = array_merge($element_types, $additional_types); + } + + return $element_types; +} + /** * Loader function to retrieve a form builder configuration array. * @@ -333,7 +349,7 @@ function form_builder_add_default_properties($form, $form_type, $key = NULL, $pa else { // If the type cannot be found, prevent editing of this field. unset($form['#form_builder']); - return; + return $form; } } diff --git a/sites/all/modules/form_builder/includes/form_builder.cache.inc b/sites/all/modules/form_builder/includes/form_builder.cache.inc index 5f91cb5a35c0a5172ea8af0deb7d62f4beff47af..2b803759e22ec9084e62a6552918c810c7926abc 100644 --- a/sites/all/modules/form_builder/includes/form_builder.cache.inc +++ b/sites/all/modules/form_builder/includes/form_builder.cache.inc @@ -92,8 +92,6 @@ function form_builder_cache_purge($expire_threshold = NULL) { return db_delete('form_builder_cache') ->condition('updated', REQUEST_TIME - $expire_threshold, '<') - ->condition('type', $form_type) - ->condition('form_id', $form_id) ->execute(); } diff --git a/sites/all/modules/form_builder/includes/form_builder.properties.inc b/sites/all/modules/form_builder/includes/form_builder.properties.inc index 42edc687f4f2a501db3dde14c6f313030c63432b..f8e7391523d40b532dd6f49fbdd7a3d76fcc619b 100644 --- a/sites/all/modules/form_builder/includes/form_builder.properties.inc +++ b/sites/all/modules/form_builder/includes/form_builder.properties.inc @@ -242,7 +242,7 @@ function form_builder_property_default_value_form(&$form_state, $form_type, $ele // exception, though. '#type' => $element['#type'] == 'textarea' ? 'textarea' : 'textfield', '#title' => t('Default value'), - '#default_value' => $element['#default_value'], + '#default_value' => $element['#type'] == 'value' ? $element['#value'] : $element['#default_value'], '#weight' => 1, ); diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc b/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc index b487734670526e7080dc21f7d84234a303cc41cc..3466bebc30949c65d51198de82d15001eaa72fd6 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc @@ -37,13 +37,13 @@ function _form_builder_webform_form_builder_map_date() { 'form_parents' => array('default', 'timezone'), 'storage_parents' => array('extra', 'timezone'), ), - 'year_start' => array( - 'form_parents' => array('validation', 'year_start'), - 'storage_parents' => array('extra', 'year_start'), + 'start_date' => array( + 'form_parents' => array('validation', 'start_date'), + 'storage_parents' => array('extra', 'start_date'), ), - 'year_end' => array( - 'form_parents' => array('validation', 'year_end'), - 'storage_parents' => array('extra', 'year_end'), + 'end_date' => array( + 'form_parents' => array('validation', 'end_date'), + 'storage_parents' => array('extra', 'end_date'), ), 'year_textfield' => array( 'form_parents' => array('display', 'year_textfield'), @@ -204,6 +204,22 @@ function _form_builder_webform_form_builder_map_file() { ); } +/** + * Implements _form_builder_webform_form_builder_load_component(). + */ +function _form_builder_webform_form_builder_load_file($form_element) { + if (isset($form_element['#upload_validators'])) { + // Extension list and size comes from #upload_validators on load. + $form_element['#webform_file_extensions']['types'] = empty($form_element['#upload_validators']['file_validate_extensions'][0]) ? array() : explode(' ', $form_element['#upload_validators']['file_validate_extensions'][0]); + $form_element['#webform_file_size'] = empty($form_element['#upload_validators']['file_validate_size'][0]) ? '' : format_size($form_element['#upload_validators']['file_validate_size'][0]); + // File directory and scheme come from #upload_location on load. + $form_element['#webform_file_directory'] = preg_replace('/^webform[\/]?/', '', file_uri_target($form_element['#upload_location'])); + $form_element['#webform_file_scheme'] = file_uri_scheme($form_element['#upload_location']); + } + + return $form_element; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -384,6 +400,17 @@ function _form_builder_webform_form_builder_types_hidden() { return $fields; } +/** + * Implements _form_builder_webform_form_builder_load_component(). + */ +function _form_builder_webform_form_builder_load_hidden($form_element) { + // Hidden elements may be #type "value" or "hidden". Set the internal property + // to keep track of hidden fields. + $form_element['#form_builder']['element_type'] = 'hidden'; + + return $form_element; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -399,12 +426,7 @@ function _form_builder_webform_form_builder_preview_alter_hidden($form_element) // Display the title of the hidden field as regular markup. $form_element['#children'] = t('@title - <em>hidden field</em>', array('@title' => $form_element['#title'])); - $form_element['#title_display'] = 'none'; - - // Give the element a wrapper class so that themers can recognize it - // represents a hidden element. - $form_element['#attributes']['class'][] = 'form-builder-preview-hidden-webform-element'; - array_unshift($form_element['#theme_wrappers'], 'container'); + $form_element['#title'] = NULL; return $form_element; } @@ -598,12 +620,30 @@ function _form_builder_webform_form_builder_load_pagebreak($form_element) { // Pagebreak components are rendered as hidden elements by webform, but // hidden elements do not have a #title property. So we have to convert the // rendered value to be used as the #title instead. - if ($form_element['#type'] == 'pagebreak') { - $form_element['#title'] = $form_element['#value']; - } + $form_element['#title'] = $form_element['#value']; + $form_element['#form_builder']['element_type'] = 'pagebreak'; + return $form_element; } +/** + * Implements _form_builder_webform_form_builder_save_component(). + */ +function _form_builder_webform_form_builder_save_pagebreak($component, $form_element) { + // Ensure pagebreaks are saved at the root level. + if ($component['pid'] !== 0) { + drupal_set_message(t('Page breaks may not be nested inside fieldsets. Each pagebreak has been moved outside of fieldsets.'), 'status', FALSE); + + $form_cache = form_builder_cache_load('webform', $form_element['#form_builder']['form_id']); + $parent = form_builder_get_element($form_cache, $form_element['#form_builder']['parent_id']); + + $component['weight'] = $parent['#weight'] + 1; + $component['pid'] = 0; + } + + return $component; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -746,7 +786,7 @@ function _form_builder_webform_form_builder_save_select($component, $form_elemen */ function form_builder_webform_property_select_options_form(&$form_state, $form_type, $element) { // Use the default options form, but enhance to allow Webform tokens. - $form = form_builder_property_options_form($form_state, $form_type, $element); + $form = form_builder_property_options_form($form_state, $form_type, $element, 'options'); $form['options']['#default_value_pattern'] = '^%.+\[.+\]$'; return $form; } @@ -885,7 +925,7 @@ function _form_builder_webform_form_builder_map_time() { 'storage_parents' => array('extra', 'timezone'), ), 'hourformat' => array( - 'storage_parents' => array('display', 'hourformat'), + 'form_parents' => array('display', 'hourformat'), 'storage_parents' => array('extra', 'hourformat'), ), ), @@ -916,8 +956,9 @@ function _form_builder_webform_mapped_form(&$form_state, $form_type, $element, $ function _form_builder_webform_save_mapped_component($component, $element) { if ($map = _form_builder_webform_property_map($component['type'])) { foreach ($map['properties'] as $property => $property_map) { - if (isset($element['#' . $property]) && isset($property_map['storage_parents'])) { - _form_builder_webform_save_mapped_component_value($component, $element['#' . $property], $property_map['storage_parents']); + if (isset($property_map['storage_parents'])) { + $property_value = isset($element['#' . $property]) ? $element['#' . $property] : NULL; + _form_builder_webform_save_mapped_component_value($component, $property_value, $property_map['storage_parents']); } } } @@ -967,6 +1008,9 @@ function _form_builder_webform_default($component_type, $merge_extras = array()) // Call the loading function to make sure that the default element gets the // same treatment as an existing one. $default_element = _form_builder_webform_set_mapped_type($default_element); + if ($element = form_builder_webform_component_invoke($component_type, 'form_builder_load', $default_element)) { + $default_element = $element; + } return $default_element; } @@ -1016,11 +1060,12 @@ function _form_builder_webform_build_edit_form($component_type, $element, $prope // component. $defaults_function = '_webform_defaults_' . $component_type; $component = isset($element['#webform_component']) ? $element['#webform_component'] : $defaults_function(); + $nid = isset($component['nid']) ? $component['nid'] : NULL; // The most up-to-date configuration data stored by Form Builder for the // part of the component we are editing is also stored in the passed-in // element, and should always take precedence. - if (isset($element["#$property"])) { + if (array_key_exists("#$property", $element)) { drupal_array_set_nested_value($component, $component_nested_keys, $element["#$property"]); } @@ -1028,7 +1073,10 @@ function _form_builder_webform_build_edit_form($component_type, $element, $prope // the component, and obtain the slice of it that we want. $empty_form = array(); $empty_form_state = form_state_defaults(); - $node = (object) array('nid' => NULL); + + // The full node is needed here so that the "private" option can be access + // checked. + $node = !isset($nid) ? (object) array('nid' => NULL) : node_load($nid); $form = webform_component_edit_form($empty_form, $empty_form_state, $node, $component); $form = drupal_array_get_nested_value($form, $form_nested_keys); diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.info b/sites/all/modules/form_builder/modules/webform/form_builder_webform.info index 3f48fec72ecdfb47099ed7c2f1686d786b46da17..a6856e38a2594157a1bcdf8b34c06eca519a9334 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.info +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = form_builder dependencies[] = webform -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.module b/sites/all/modules/form_builder/modules/webform/form_builder_webform.module index a0d356e2aa41e5af45f960136939790041e6d682..2a7c83856dccb00911a579312486fd2c35967bca 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.module +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.module @@ -52,11 +52,14 @@ function form_builder_webform_save_form($form, &$form_state, $nid) { '#type' => 'value', '#value' => $nid, ); - $form['save'] = array( + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['save'] = array( '#type' => 'submit', '#value' => t('Save'), ); - $form['cancel'] = array( + $form['actions']['cancel'] = array( '#type' => 'submit', '#value' => t('Cancel'), '#submit' => array('form_builder_webform_cancel'), @@ -81,22 +84,47 @@ function form_builder_webform_save_node($node) { $form_cache = form_builder_cache_load('webform', $node->nid); $element_ids = form_builder_preview_prepare($form_cache, 'webform', $node->nid); - // Save modified or created components. - foreach ($element_ids as $id) { - form_builder_webform_save_component($node, $id, $form_cache); - } - - // Delete components that have been removed. + // Remove components if deleted and calculate the highest in-use CID. + $max_cid = 0; foreach ($node->webform['components'] as $component) { $element_id = 'cid_' . $component['cid']; + $cid = $component['cid']; + + // Max CID is used in the creation of new components, preventing conflicts. + $max_cid = max($max_cid, $cid); + + // Remove components from the $node that have been removed in the UI. if (!in_array($element_id, $element_ids)) { - webform_component_delete($node, $component); + if (isset($node->webform['components'][$cid])) { + unset($node->webform['components'][$cid]); + } + } + } + + // Update any new/updated components in the node record. + foreach ($element_ids as $element_id) { + $component = form_builder_webform_get_component($node, $element_id, $form_cache); + if ($component) { + if (empty($component['cid'])) { + $cid = ++$max_cid; + $component['cid'] = $cid; + // Reassign the component ID to the form so that future elements that + // may depend on this one set their parent ID (pid) properly. + $element = form_builder_get_element($form_cache, $element_id); + $element['#webform_component']['cid'] = $cid; + form_builder_set_element($form_cache, $element); + } + else { + $cid = $component['cid']; + } + $node->webform['components'][$cid] = $component; } } - // Reset the node cache, since $node->webform['components'] has changed. - // @todo: Decide if this belongs in the above Webform API functions instead? - entity_get_controller('node')->resetCache(array($node->nid)); + // Save the node itself to update components and allow other modules to + // respond to any changes. The Form Builder cache is intentionally left in + // place so other modules can check it for changes also. + node_save($node); // Remove the cached form_builder form. form_builder_cache_delete('webform', $node->nid); @@ -105,7 +133,7 @@ function form_builder_webform_save_node($node) { /** * Save the contents of a form component into Webform's database tables. */ -function form_builder_webform_save_component($node, $element_id, $form) { +function form_builder_webform_get_component($node, $element_id, $form) { module_load_include('inc', 'form_builder_webform', 'form_builder_webform.components'); $element = form_builder_get_element($form, $element_id); @@ -140,21 +168,9 @@ function form_builder_webform_save_component($node, $element_id, $form) { $component['pid'] = 0; // Set the parent ID for the component if it is nested inside another component. - // This must be done this way so that children of newly added components get a - // proper pid when the cid of the new component has just been generated. $parent = form_builder_get_element($form, $element['#form_builder']['parent_id']); - if ($parent) { - // If the parent is new, the database must be queried for the cid of the parent. - if (isset($parent['#webform_component']['is_new']) && $parent['#webform_component']['is_new']) { - $results = db_query('SELECT cid, form_key FROM {webform_component} WHERE form_key = :key AND nid = :nid', array(':key' => $element['#form_builder']['parent_id'], ':nid' => $node->nid)); - foreach($results as $result) { - $component['pid'] = $result->cid; - } - } - // If the parent is already stored in the webform, grab its cid value. - elseif (isset($parent['#webform_component']['cid'])) { - $component['pid'] = $parent['#webform_component']['cid']; - } + if ($parent && isset($parent['#webform_component']['cid'])) { + $component['pid'] = $parent['#webform_component']['cid']; } // Set the component's value. If the form element doesn't have a default, @@ -176,12 +192,7 @@ function form_builder_webform_save_component($node, $element_id, $form) { $component = $saved_component; } - if (!isset($component['cid'])) { - webform_component_insert($component); - } - elseif ($component != $node->webform['components'][$component['cid']]) { - webform_component_update($component); - } + return $component; } /** @@ -272,32 +283,11 @@ function form_builder_webform_form_builder_load($form_builder_type, $form_builde $nid = $form_builder_id; $node = node_load($nid); - // Since webform_client_form() has special handling for the 'pagebreak' - // component that we do not want to occur here (here we want to display the - // entire webform on a single page, without page breaks), we temporarily - // change the component to 'pagebreak_clone' and then change it back - // afterwards. See _webform_render_pagebreak_clone(). - // @todo: Replace this with something cleaner, if webform eventually allows - // us to do that. - $pagebreak_component_keys = array(); - foreach ($node->webform['components'] as &$component) { - if ($component['type'] == 'pagebreak') { - $component['type'] = 'pagebreak_clone'; - $pagebreak_component_keys[] = $component['form_key']; - } - } - // Get the unfiltered version of the client form. $form = array(); $form_state = array(); $form = webform_client_form($form, $form_state, $node, array(), TRUE, FALSE); - // Change 'pagebreak' components back, as described above. - foreach ($pagebreak_component_keys as $key) { - $form['submitted'][$key]['#type'] = 'pagebreak'; - $form['submitted'][$key]['#webform_component']['type'] = 'pagebreak'; - } - // Perform final processing of the form, and return it. $form += array('submitted' => array()); form_builder_webform_load_process($form['submitted'], $node); @@ -340,6 +330,7 @@ function form_builder_webform_load_process(&$form, $node, $pid = 0) { */ function form_builder_webform_form_builder_add_element_alter(&$element, $form_type, $form_id) { if ($form_type == 'webform') { + $element['#webform_component']['nid'] = is_numeric($form_id) ? $form_id : NULL; $element['#webform_component']['is_new'] = TRUE; } } @@ -369,7 +360,7 @@ function form_builder_webform_form_builder_preview_alter(&$element, $form_type, } // A #title_display property of 0 (as stored by Webform) means no setting. - if (isset($element['#title_display']) && $element['#title_display'] == 0) { + if (isset($element['#title_display']) && strcmp('0', $element['#title_display']) === 0) { unset($element['#title_display']); } } @@ -454,20 +445,6 @@ function form_builder_webform_component_invoke($component_type, $callback) { } } -/** - * Implements _webform_render_component(). - * - * This "component" is only used as a temporary replacement for the standard - * pagebreak component when the form builder version of the webform is being - * rendered; this occurs when form_builder_webform_form_builder_load() calls - * webform_client_form(). Therefore, we do not need to register it as a regular - * webform component anywhere else (and don't want to, since we don't want it - * to be available elsewhere in the webform UI). - */ -function _webform_render_pagebreak_clone($component, $value = NULL, $filter = TRUE) { - return webform_component_invoke('pagebreak', 'render', $component, $value, $filter); -} - /** * Helper function; Retrieve a component's map and merge in generic properties. */ @@ -501,7 +478,7 @@ function _form_builder_webform_property_map($component_type) { } if (webform_component_feature($component_type, 'private')) { - $map['properties']['private'] = array( + $map['properties']['webform_private'] = array( 'form_parents' => array('display', 'private'), 'storage_parents' => array('extra', 'private'), ); @@ -514,6 +491,8 @@ function _form_builder_webform_property_map($component_type) { // All components support the key property. $map['properties']['key'] = array(); + drupal_alter('form_builder_webform_property_map', $map, $component_type); + $maps[$component_type] = $map; } diff --git a/sites/all/modules/imce/imce.info b/sites/all/modules/imce/imce.info index 6394ad4e73a3918942fc9aa1231ea54733a832f2..60b6ac8cf3a3e21d753740190a340d87bea65525 100644 --- a/sites/all/modules/imce/imce.info +++ b/sites/all/modules/imce/imce.info @@ -4,9 +4,9 @@ core = "7.x" package = "Media" configure = "admin/config/media/imce" -; Information added by drupal.org packaging script on 2011-10-20 -version = "7.x-1.5" +; Information added by drupal.org packaging script on 2013-01-29 +version = "7.x-1.7" core = "7.x" project = "imce" -datestamp = "1319104232" +datestamp = "1359476607" diff --git a/sites/all/modules/imce/imce.install b/sites/all/modules/imce/imce.install index 8d3e420199b77a47da67e786042c9a7c081b3118..688c6b5b137e52b390afa0ca58cd3f3518402517 100644 --- a/sites/all/modules/imce/imce.install +++ b/sites/all/modules/imce/imce.install @@ -11,13 +11,13 @@ function imce_install() { module_load_include('inc', 'imce', 'inc/imce.core.profiles'); imce_install_profiles(); - drupal_set_message(st('!module has been installed.', array('!module' => l(st('IMCE'), 'admin/config/media/imce')))); } /** * Implements hook_uninstall(). */ function imce_uninstall() { + db_delete('file_usage')->condition('module', 'imce')->execute(); variable_del('imce_profiles'); variable_del('imce_roles_profiles'); variable_del('imce_settings_textarea'); diff --git a/sites/all/modules/imce/imce.module b/sites/all/modules/imce/imce.module index fe93a1204a4b0cef71cd755bec7ee1489705f870..f3dc4bf79dc3755c53a252de3f629f878e6f35d6 100644 --- a/sites/all/modules/imce/imce.module +++ b/sites/all/modules/imce/imce.module @@ -15,6 +15,7 @@ function imce_menu() { 'title' => 'File browser', 'page callback' => 'imce', 'access callback' => 'imce_access', + 'access arguments' => array(FALSE, 1), 'file' => 'inc/imce.page.inc', 'type' => MENU_CALLBACK, ); @@ -118,7 +119,7 @@ function imce_textarea($element) { if (!isset($regexp)) { $regexp = FALSE; if (imce_access() && $regexp = str_replace(' ', '', variable_get('imce_settings_textarea', ''))) { - $regexp = '@^' . str_replace(',', '|', implode('.*', array_map('preg_quote', explode('*', $regexp)))) . '$@'; + $regexp = '@^(' . str_replace(',', '|', implode('.*', array_map('preg_quote', explode('*', $regexp)))) . ')$@'; } } if ($regexp && preg_match($regexp, $element['#id'])) { @@ -132,58 +133,39 @@ function imce_textarea($element) { * Returns the configuration profile assigned to a user for a specific file scheme. */ function imce_user_profile($user, $scheme = NULL) { - $profiles = variable_get('imce_profiles', array()); - $swrappers = file_get_stream_wrappers(); - $default_scheme = variable_get('file_default_scheme', 'public'); + static $ups = array(); - //handle user#1 separately - if ($user->uid == 1) { - $scheme = empty($scheme) ? $default_scheme : $scheme; - if (isset($profiles[1]) && isset($swrappers[$scheme])) { - return $profiles[1] + array('scheme' => $scheme); - } - return FALSE; + // Set scheme + if (empty($scheme)) { + $scheme = variable_get('file_default_scheme', 'public'); } - //handle regular users - $roles_profiles = variable_get('imce_roles_profiles', array()); - //store assigned configuration - $conf = array(); - foreach ($roles_profiles as $rid => $role) { - if (isset($user->roles[$rid])) { - $conf = $role; - break; - } + // Return from cache. + if (isset($ups[$scheme][$user->uid])) { + return $ups[$scheme][$user->uid]; } + $ups[$scheme][$user->uid] = FALSE; - //no scheme-profile assignment - if (empty($conf)) { + // Check scheme + $swrappers = file_get_stream_wrappers(); + if (!isset($swrappers[$scheme])) { return FALSE; } - //return the profile for the specified scheme - if (!empty($scheme)) { - $key = $scheme . '_pid'; - if (isset($conf[$key]) && isset($profiles[$conf[$key]]) && isset($swrappers[$scheme])) { - return $profiles[$conf[$key]] + array('scheme' => $scheme); - } - return FALSE; - } + $profiles = variable_get('imce_profiles', array()); + $scinfo = array('scheme' => $scheme); - //no scheme specified. check the default - $scheme = $default_scheme; - $key = $scheme . '_pid'; - if (isset($conf[$key]) && isset($profiles[$conf[$key]]) && isset($swrappers[$scheme])) { - return $profiles[$conf[$key]] + array('scheme' => $scheme); + // Handle user#1 separately + if ($user->uid == 1) { + return $ups[$scheme][$user->uid] = isset($profiles[1]) ? $profiles[1] + $scinfo : FALSE; } - //check if any of the schemes has a profile assigned. - foreach ($conf as $key => $pid) { - if (substr($key, -4) == '_pid' && isset($profiles[$pid])) { - $scheme = substr($key, 0, -4); - if (isset($swrappers[$scheme])) { - return $profiles[$pid] + array('scheme' => $scheme); - } + // Handle regular users. + $roles_profiles = variable_get('imce_roles_profiles', array()); + $sckey = $scheme . '_pid'; + foreach ($roles_profiles as $rid => $conf) { + if (isset($user->roles[$rid]) && isset($conf[$sckey]) && isset($profiles[$conf[$sckey]])) { + return $ups[$scheme][$user->uid] = $profiles[$conf[$sckey]] + $scinfo; } } @@ -194,28 +176,11 @@ function imce_user_profile($user, $scheme = NULL) { * Checks if the user is assigned an imce profile. * A more detailed assignment check is performed before imce loads. */ -function imce_access($user = FALSE) { +function imce_access($user = FALSE, $scheme = NULL) { if ($user === FALSE) { global $user; } - - if ($user->uid == 1) { - return TRUE; - } - - $roles_profiles = variable_get('imce_roles_profiles', array()); - foreach ($roles_profiles as $rid => $role) { - if (isset($user->roles[$rid])) { - foreach ($role as $key => $pid) { - if (substr($key, -4) == '_pid' && $pid) { - return TRUE; - } - } - break; - } - } - - return FALSE; + return imce_user_profile($user, $scheme) ? TRUE : FALSE; } /** @@ -225,7 +190,6 @@ function imce_user_page_access($account, $user = FALSE) { if ($user === FALSE) { global $user; } - return ($user->uid == 1 || $account->uid == $user->uid) && ($profile = imce_user_profile($account)) && $profile['usertab']; } @@ -233,5 +197,5 @@ function imce_user_page_access($account, $user = FALSE) { * Check if the directory name is regular. */ function imce_reg_dir($dirname) { - return $dirname == '.' || (is_string($dirname) && $dirname != '' && !preg_match('@(^\s)|(^/)|(^\./)|(\s$)|(/$)|(/\.$)|(\.\.)|(//)|(\\\\)|(/\./)@', $dirname)); + return $dirname == '.' || is_int($dirname) || (is_string($dirname) && $dirname != '' && !preg_match('@(^\s)|(^/)|(^\./)|(\s$)|(/$)|(/\.$)|(\.\.)|(//)|(\\\\)|(/\./)@', $dirname)); } \ No newline at end of file diff --git a/sites/all/modules/imce/inc/imce.admin.inc b/sites/all/modules/imce/inc/imce.admin.inc index 4ff3077bb9c9ebe1ac21cab47327877f6a00f9a9..ff4961836ff255986b5398cf4e5a4f03779c9597 100644 --- a/sites/all/modules/imce/inc/imce.admin.inc +++ b/sites/all/modules/imce/inc/imce.admin.inc @@ -249,7 +249,7 @@ function imce_profile_form($form, &$form_state, $pid = 0) { ); $form['dimensions'] = array( '#type' => 'textfield', - '#title' => t('Maximum image resolution'), + '#title' => t('Maximum image dimensions'), '#default_value' => $profile['dimensions'], '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. If an <a href="!image-toolkit-link">image toolkit</a> is installed, files exceeding this value will be scaled down to fit.', array('!image-toolkit-link' => url('admin/config/media/image-toolkit'))), '#field_suffix' => '<kbd>' . t('WIDTHxHEIGHT') . '</kbd>', diff --git a/sites/all/modules/imce/inc/imce.page.inc b/sites/all/modules/imce/inc/imce.page.inc index 27b27adaa7ca088f96bb60bdbcaa4b08fe0d87dc..bcb00b2d2e6eb7a43b48329877d59fa31929ff71 100644 --- a/sites/all/modules/imce/inc/imce.page.inc +++ b/sites/all/modules/imce/inc/imce.page.inc @@ -11,6 +11,7 @@ function imce($scheme = NULL) { module_invoke('admin_menu', 'suppress');//suppress admin_menu $jsop = isset($_GET['jsop']) ? $_GET['jsop'] : NULL; + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); print imce_page($GLOBALS['user'], $scheme, $jsop); exit(); } @@ -258,7 +259,7 @@ function imce_fileop_form_validate($form, &$form_state) { return form_error($form['filenames'], t('Please select a file.')); } - //filenames come seperated by colon + //filenames come separated by colon $filenames = explode(':', $form_state['values']['filenames']); $cnt = count($filenames); //check the number of files. @@ -359,7 +360,7 @@ function imce_resize_submit($form, &$form_state) { //check dimensions $width = (int) $form_state['values']['width']; $height = (int) $form_state['values']['height']; - list($maxw, $maxh) = explode('x', $imce['dimensions']); + list($maxw, $maxh) = $imce['dimensions'] ? explode('x', $imce['dimensions']) : array(0, 0); if ($width < 1 || $height < 1 || ($maxw && ($width > $maxw || $height > $maxh))) { drupal_set_message(t('Please specify dimensions within the allowed range that is from 1x1 to @dimensions.', array('@dimensions' => $imce['dimensions'] ? $imce['dimensions'] : t('unlimited'))), 'error'); return; @@ -424,10 +425,6 @@ function imce_delete_filepath($uri) { if (!file_delete($file, TRUE)) { return FALSE; } - // Remove imce usage - if ($is_imce) { - file_usage_delete($file, 'imce'); - } } // Not in db. Probably loaded via ftp. elseif (!file_unmanaged_delete($uri)) { @@ -676,13 +673,25 @@ function imce_validate_quotas($file, &$imce, $add = 0) { } /** - * Check if the file is an image and return info. + * Checks if the file is an image and returns info. + * There are two switchable versions that use image_get_info() and getimagesize() */ -function imce_image_info($file) { - if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @getimagesize($file)) && in_array($info[2], array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG)) ) { - return array('width' => $info[0], 'height' => $info[1], 'type' => $info[2], 'mime' => $info['mime']); +if (variable_get('imce_image_get_info', 0)) { + function imce_image_info($file) { + $mimes = array('image/jpeg' => IMAGETYPE_JPEG, 'image/gif' => IMAGETYPE_GIF, 'image/png' => IMAGETYPE_PNG); + if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @image_get_info($file)) && isset($mimes[$info['mime_type']]) ) { + return array('width' => $info['width'], 'height' => $info['height'], 'type' => $mimes[$info['mime_type']], 'mime' => $info['mime_type']); + } + return FALSE; + } +} +else { + function imce_image_info($file) { + if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @getimagesize($file)) && in_array($info[2], array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG)) ) { + return array('width' => $info[0], 'height' => $info[1], 'type' => $info[2], 'mime' => $info['mime']); + } + return FALSE; } - return FALSE; } /** @@ -838,6 +847,7 @@ function imce_working_directory(&$imce) { //or the whole list. foreach ($imce['directories'] as $dirname => $info) { + $dirname = (string) $dirname; if (imce_check_directory($dirname, $imce)) { if ($sess) { $_SESSION['imce_directory'] = rawurlencode($dirname); diff --git a/sites/all/modules/imce/js/imce.js b/sites/all/modules/imce/js/imce.js index 2a2f9b4011c74df0eddae5583effeaf7e5d3993e..f0380873375d20c531891217ef81059065c90747 100644 --- a/sites/all/modules/imce/js/imce.js +++ b/sites/all/modules/imce/js/imce.js @@ -424,7 +424,7 @@ navCache: function (dir, newdir) { //validate upload form uploadValidate: function (data, form, options) { - var path = data[0].value; + var path = $('#edit-imce').val(); if (!path) return false; if (imce.conf.extensions != '*') { var ext = path.substr(path.lastIndexOf('.') + 1); @@ -492,7 +492,7 @@ fopSettings: function (fop) { //toggle loading state fopLoading: function(fop, state) { - var el = imce.el('edit-'+ fop), func = state ? 'addClass' : 'removeClass' + var el = imce.el('edit-'+ fop), func = state ? 'addClass' : 'removeClass'; if (el) { $(el)[func]('loading').attr('disabled', state); } diff --git a/sites/all/modules/imce/js/imce_extras.js b/sites/all/modules/imce/js/imce_extras.js index 349de2d1edc48d94be6d3204246c907aec100ef5..bccf1c1d3ff2313072dec05dda9556a27efffdc4 100644 --- a/sites/all/modules/imce/js/imce_extras.js +++ b/sites/all/modules/imce/js/imce_extras.js @@ -117,15 +117,10 @@ imce.firstSort = function() { //sort file list according to column index. imce.columnSort = function(cid, dsc) { if (imce.findex.length < 2) return; - if (cid == imce.vars.cid && dsc != imce.vars.dsc) { - imce.findex.reverse(); - } - else { - var func = 'sort'+ (cid == 0 ? 'Str' : 'Num') + (dsc ? 'Dsc' : 'Asc'); - var prop = cid == 2 || cid == 3 ? 'innerHTML' : 'id'; - //sort rows - imce.findex.sort(cid ? function(r1, r2) {return imce[func](r1.cells[cid][prop], r2.cells[cid][prop])} : function(r1, r2) {return imce[func](r1.id, r2.id)}); - } + var func = 'sort'+ (cid == 0 ? 'Str' : 'Num') + (dsc ? 'Dsc' : 'Asc'); + var prop = cid == 2 || cid == 3 ? 'innerHTML' : 'id'; + //sort rows + imce.findex.sort(cid ? function(r1, r2) {return imce[func](r1.cells[cid][prop], r2.cells[cid][prop])} : function(r1, r2) {return imce[func](r1.id, r2.id)}); //insert sorted rows for (var row, i=0; row = imce.findex[i]; i++) { imce.tbody.appendChild(row); diff --git a/sites/all/modules/link/link.css b/sites/all/modules/link/link.css index 7bdec9e18a560a2c7384c1eb377b158c36066d71..1590e7ad51bc27bf461157af85bcfabd6846d0b7 100644 --- a/sites/all/modules/link/link.css +++ b/sites/all/modules/link/link.css @@ -1,8 +1,8 @@ -div.link-field-column { +.link-field-column { float: left; width: 48%; } -div.link-field-column .form-text { +.link-field-column .form-text { width: 95%; } diff --git a/sites/all/modules/link/link.devel_generate.inc b/sites/all/modules/link/link.devel_generate.inc new file mode 100644 index 0000000000000000000000000000000000000000..7be4a0dde2eb763f2e48c08410861783e4463a9f --- /dev/null +++ b/sites/all/modules/link/link.devel_generate.inc @@ -0,0 +1,29 @@ +<?php + +/** + * @file + * Devel Generate support for Link module. + */ + +/** + * Implements hook_devel_generate(). + */ +function link_devel_generate($object, $field, $instance, $bundle) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return devel_generate_multiple('_link_devel_generate', $object, $field, $instance, $bundle); + } + else { + return _link_devel_generate($object, $field, $instance, $bundle); + } +} + +/** + * Callback for hook_devel_generate(). + */ +function _link_devel_generate($object, $field, $instance, $bundle) { + return array( + 'url' => url('<front>', array('absolute' => TRUE)), + 'title' => devel_create_greeking(mt_rand(1, 3), TRUE), + 'attributes' => _link_default_attributes(), + ); +} diff --git a/sites/all/modules/link/link.diff.inc b/sites/all/modules/link/link.diff.inc new file mode 100644 index 0000000000000000000000000000000000000000..9e34123ae4d9fd4324f60fe2919af89c4299bf58 --- /dev/null +++ b/sites/all/modules/link/link.diff.inc @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Provide diff field functions for the Link module. + */ + +/** + * Diff field callback for parsing link fields comparative values. + */ +function link_field_diff_view($items, $context) { + $diff_items = array(); + foreach ($items as $delta => $item) { + if ($item['url'] && $item['title']) { + $diff_items[$delta] = $item['title'] . ' (' . $item['url'] . ')'; + } + else { + $diff_items[$delta] = $item['title'] . $item['url']; + } + } + return $diff_items; +} diff --git a/sites/all/modules/link/link.info b/sites/all/modules/link/link.info index 8a5d0c18ccfbddac667cf29f77072cc5a5eb6aec..57aedb3983ded3bd22e46062992a68aa2273132c 100644 --- a/sites/all/modules/link/link.info +++ b/sites/all/modules/link/link.info @@ -4,7 +4,8 @@ core = 7.x package = Fields files[] = link.module -files[] = link.install +files[] = link.migrate.inc + ; Tests files[] = tests/link.test files[] = tests/link.attribute.test @@ -17,9 +18,9 @@ files[] = tests/link.validate.test files[] = views/link_views_handler_argument_target.inc files[] = views/link_views_handler_filter_protocol.inc -; Information added by drupal.org packaging script on 2011-10-23 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2013-02-09 +version = "7.x-1.1" core = "7.x" project = "link" -datestamp = "1319392535" +datestamp = "1360444361" diff --git a/sites/all/modules/link/link.install b/sites/all/modules/link/link.install index fdfc5d1e2da4ab883317d9fd25a2a1b42be2cbc9..14e745d42158981b34657879dcb846f66f83e62c 100644 --- a/sites/all/modules/link/link.install +++ b/sites/all/modules/link/link.install @@ -18,20 +18,21 @@ function link_field_schema($field) { 'columns' => array( 'url' => array( 'type' => 'varchar', - 'length' => 2048, // Maximum URLs length. + // Maximum URLs length. + 'length' => 2048, 'not null' => FALSE, - 'sortable' => TRUE + 'sortable' => TRUE, ), 'title' => array( 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, - 'sortable' => TRUE + 'sortable' => TRUE, ), 'attributes' => array( 'type' => 'text', 'size' => 'medium', - 'not null' => FALSE + 'not null' => FALSE, ), ), ); @@ -87,8 +88,7 @@ function link_update_7000() { * Renames all displays from foobar to link_foobar */ function link_update_7001() { - // for each field that is a link field, we need to update the display types: - + // Update the display type for each link field type. $result = db_query("SELECT id, field_name, data FROM {field_config} WHERE module = 'link' AND type = 'link_field'"); foreach ($result as $field) { $field_id = $field->id; @@ -102,13 +102,12 @@ function link_update_7001() { $update_instance = FALSE; foreach ($instance_data['display'] as $display_name => $display_data) { if ($display_data['type'] && (0 !== strpos($display_data['type'], 'link_'))) { - $instance_data['display'][$display_name]['type'] = 'link_'. $display_data['type']; + $instance_data['display'][$display_name]['type'] = 'link_' . $display_data['type']; $update_instance = TRUE; } } if ($update_instance) { - // update the database. - $num_updated = db_update('field_config_instance') + db_update('field_config_instance') ->fields(array('data' => serialize($instance_data))) ->condition('id', $instance->id) ->execute(); diff --git a/sites/all/modules/link/link.migrate.inc b/sites/all/modules/link/link.migrate.inc new file mode 100644 index 0000000000000000000000000000000000000000..6388d11e44aa0f7429b7cf34520de7c4bea48796 --- /dev/null +++ b/sites/all/modules/link/link.migrate.inc @@ -0,0 +1,108 @@ +<?php + +/** + * @file + * Support for migrate module. + * + * With Migrate 2.4 or later, you can use the subfield syntax to set the title + * and attributes: + * + * @code + * $this->addFieldMapping('field_my_link', 'source_url'); + * $this->addFieldMapping('field_my_link:title', 'source_title'); + * $this->addFieldMapping('field_my_link:attributes', 'source_attributes'); + * @endcode + * + * With earlier versions of Migrate, you must pass an arguments array: + * + * @code + * $link_args = array( + * 'title' => array('source_field' => 'source_title'), + * 'attributes' => array('source_field' => 'source_attributes'), + * ); + * $this->addFieldMapping('field_my_link', 'source_url') + * ->arguments($link_args); + * @endcode + */ + +/** + * Implements hook_migrate_api(). + */ +function link_migrate_api() { + return array( + 'api' => 2, + 'field handlers' => array('MigrateLinkFieldHandler'), + ); +} + +class MigrateLinkFieldHandler extends MigrateFieldHandler { + public function __construct() { + $this->registerTypes(array('link_field')); + } + + static function arguments($title = NULL, $attributes = NULL, $language = NULL) { + $arguments = array(); + if (!is_null($title)) { + $arguments['title'] = $title; + } + if (!is_null($attributes)) { + $arguments['attributes'] = $attributes; + } + if (!is_null($language)) { + $arguments['language'] = $language; + } + return $arguments; + } + + /** + * Implementation of MigrateFieldHandler::fields(). + * + * @param $type + * The field type. + * @param $instance + * Instance info for the field. + * @param Migration $migration + * The migration context for the parent field. We can look at the mappings + * and determine which subfields are relevant. + * @return array + */ + public function fields($type, $instance, $migration = NULL) { + return array( + 'title' => t('Subfield: The link title attribute'), + 'attributes' => t('Subfield: The attributes for this link'), + 'language' => t('Subfield: The language for the field'), + ); + } + + public function prepare($entity, array $field_info, array $instance, array $values) { + if (isset($values['arguments'])) { + $arguments = $values['arguments']; + unset($values['arguments']); + } + else { + $arguments = array(); + } + + $language = $this->getFieldLanguage($entity, $field_info, $arguments); + $values = array_filter($values); + + foreach ($values as $delta => $value) { + $item = array(); + if (isset($arguments['title'])) { + if (!is_array($arguments['title'])) { + $item['title'] = $arguments['title']; + } + elseif (isset($arguments['title'][$delta])) { + $item['title'] = $arguments['title'][$delta]; + } + } + if (isset($arguments['attributes'])) { + $item['attributes'] = $arguments['attributes']; + } + $item['url'] = $value; + $return[$language][$delta] = $item; + } + + return isset($return) ? $return : NULL; + } +} diff --git a/sites/all/modules/link/link.module b/sites/all/modules/link/link.module index c7af1ffbfbe667e3647581ec3b299a511ace9780..241066d55c7c1797e5908099318735e0f62b507e 100644 --- a/sites/all/modules/link/link.module +++ b/sites/all/modules/link/link.module @@ -35,7 +35,7 @@ function link_field_info() { 'url' => 0, 'title' => 'optional', 'title_value' => '', - 'title_maxlength' => 128, //patch #1307788 from nmc + 'title_maxlength' => 128, 'enable_tokens' => 1, 'display' => array( 'url_cutoff' => 80, @@ -46,7 +46,7 @@ function link_field_info() { 'url' => 0, 'title' => 'optional', 'title_value' => '', - 'title_maxlength' => 128, // patch #1307788 from nmc + 'title_maxlength' => 128, 'enable_tokens' => 1, 'display' => array( 'url_cutoff' => 80, @@ -69,7 +69,7 @@ function link_field_instance_settings_form($field, $instance) { $form = array( '#element_validate' => array('link_field_settings_form_validate'), ); - + $form['validate_url'] = array( '#type' => 'checkbox', '#title' => t('Validate URL'), @@ -97,7 +97,7 @@ function link_field_instance_settings_form($field, $instance) { '#title' => t('Link Title'), '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional', '#options' => $title_options, - '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other node field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'), + '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other entity field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'), ); $form['title_value'] = array( @@ -107,14 +107,14 @@ function link_field_instance_settings_form($field, $instance) { '#description' => t('This title will always be used if “Static Title” is selected above.'), ); - $form['title_maxlength'] = array( // patch #1307788 from nmc + $form['title_maxlength'] = array( '#type' => 'textfield', '#title' => t('Max length of title field'), '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128', '#description' => t('Set a maximum length on the title field (applies only if Link Title is optional or required). The maximum limit is 255 characters.'), '#maxlength' => 3, '#size' => 3, - ); + ); if (module_exists('token')) { // Add token module replacements fields @@ -125,23 +125,19 @@ function link_field_instance_settings_form($field, $instance) { '#title' => t('Placeholder tokens'), '#description' => t("The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values."), ); - $token_type = array( - 'theme' => 'token_tree', - 'token_types' => array($instance['entity_type']), - 'global_types' => TRUE, - 'click_insert' => TRUE, - 'recursion_limit' => 2, - ); + $entity_info = entity_get_info($instance['entity_type']); $form['tokens']['help'] = array( - '#type' => 'markup', - '#markup' => theme('token_tree', $token_type), + '#theme' => 'token_tree', + '#token_types' => array($entity_info['token type']), + '#global_types' => TRUE, + '#click_insert' => TRUE, ); $form['enable_tokens'] = array( '#type' => 'checkbox', '#title' => t('Allow user-entered tokens'), '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1, - '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the node edit form. This does not affect the field settings on this page.'), + '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the entity edit form. This does not affect the field settings on this page.'), ); } @@ -154,7 +150,7 @@ function link_field_instance_settings_form($field, $instance) { '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80', '#description' => t('If the user does not include a title for this link, the URL will be used as the title. When should the link title be trimmed and finished with an elipsis (…)? Leave blank for no limit.'), '#maxlength' => 3, - '#size' => 3, + '#size' => 3, ); $target_options = array( @@ -181,6 +177,18 @@ function link_field_instance_settings_form($field, $instance) { '#field_suffix' => '"', '#size' => 20, ); + $rel_remove_options = array( + 'default' => t('Keep rel as set up above (untouched/default)'), + 'rel_remove_external' => t('Remove rel if given link is external'), + 'rel_remove_internal' => t('Remove rel if given link is internal'), + ); + $form['rel_remove'] = array( + '#type' => 'radios', + '#title' => t('Remove rel attribute automatically'), + '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'], + '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'), + '#options' => $rel_remove_options, + ); $form['attributes']['class'] = array( '#type' => 'textfield', '#title' => t('Additional CSS Class'), @@ -205,29 +213,28 @@ function link_field_instance_settings_form($field, $instance) { } /** - * Validate the field settings form. + * #element_validate handler for link_field_instance_settings_form(). */ function link_field_settings_form_validate($element, &$form_state, $complete_form) { - if ($form_state['values']['instance']['settings']['title'] === 'value' - && empty($form_state['values']['instance']['settings']['title_value'])) { + if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) { form_set_error('title_value', t('A default title must be provided if the title is a static value.')); } - if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) // patch #1307788 from nmc - && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) { + if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) { form_set_error('display', t('URL Display Cutoff value must be numeric.')); } - if (empty($form_state['values']['instance']['settings']['title_maxlength'])) { // patch #1307788 from nmc + if (empty($form_state['values']['instance']['settings']['title_maxlength'])) { form_set_value($element['title_maxlength'], '128', $form_state); - } elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) { + } + elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) { form_set_error('title_maxlength', t('The max length of the link title must be numeric.')); - } elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) { + } + elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) { form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.')); } - } /** - * Implement hook_field_is_empty(). + * Implements hook_field_is_empty(). */ function link_field_is_empty($item, $field) { return empty($item['title']) && empty($item['url']); @@ -251,19 +258,28 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode $optional_field_found = FALSE; if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) { foreach ($items as $delta => $value) { - _link_validate($items[$delta], $delta, $field, $entity, $instance, $optional_field_found); + _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found); } } if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) { - form_set_error($field['field_name'] .'][0][title', t('At least one title or URL must be entered.')); + form_set_error($field['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); + } +} + +/** + * Implements hook_field_insert(). + */ +function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + foreach ($items as $delta => $value) { + _link_process($items[$delta], $delta, $field, $entity); } } /** - * Implements hook_field_presave(). + * Implements hook_field_update(). */ -function link_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { +function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $value) { _link_process($items[$delta], $delta, $field, $entity); } @@ -308,13 +324,13 @@ function link_field_widget_form(&$form, &$form_state, $field, $instance, $langco * Unpacks the item attributes for use. */ function _link_load($field, $item, $instance) { - /*return $item['attributes'] = isset($item['attributes']) ? - unserialize($item['attributes']) : - $instance['settings']['attributes'];*/ if (isset($item['attributes'])) { - return unserialize($item['attributes']); + if (!is_array($item['attributes'])) { + $item['attributes'] = unserialize($item['attributes']); + } + return $item['attributes']; } - else if (isset($instance['settings']['attributes'])) { + elseif (isset($instance['settings']['attributes'])) { return $instance['settings']['attributes']; } else { @@ -329,7 +345,8 @@ function _link_process(&$item, $delta = 0, $field, $entity) { // Trim whitespace from URL. $item['url'] = trim($item['url']); - // if no attributes are set then make sure $item['attributes'] is an empty array - this lets $field['attributes'] override it. + // If no attributes are set then make sure $item['attributes'] is an empty + // array, so $field['attributes'] can override it. if (empty($item['attributes'])) { $item['attributes'] = array(); } @@ -340,8 +357,7 @@ function _link_process(&$item, $delta = 0, $field, $entity) { } // Don't save an invalid default value (e.g. 'http://'). - if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) - && is_object($node)) { + if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) { if (!link_validate_url($item['url'])) { unset($item['url']); } @@ -351,45 +367,33 @@ function _link_process(&$item, $delta = 0, $field, $entity) { /** * Validates that the link field has been entered properly. */ -function _link_validate(&$item, $delta, $field, $node, $instance, &$optional_field_found) { - if ($item['url'] - && !(isset($instance['default_value'][$delta]['url']) - && $item['url'] === $instance['default_value'][$delta]['url'] - && !$instance['required'])) { +function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found) { + if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) { // Validate the link. if (link_validate_url(trim($item['url'])) == FALSE) { - form_set_error($field['field_name'] .']['. $delta .'][url', t('Not a valid URL.')); + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('The value provided for %field is not a valid URL.', array('%field' => $instance['label']))); } // Require a title for the link if necessary. if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) { - form_set_error($field['field_name'] .']['. $delta .'][title', t('Titles are required for all links.')); + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][title', t('Titles are required for all links.')); } } // Require a link if we have a title. - if ($instance['settings']['url'] !== 'optional' - && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 - && strlen(trim($item['url'])) == 0) { - form_set_error($field['field_name'] .']['. $delta .'][url', t('You cannot enter a title without a link url.')); + if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) { + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('You cannot enter a title without a link url.')); } // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link. - if ($instance['settings']['url'] === 'optional' - && $instance['settings']['title'] === 'optional' - && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { $optional_field_found = TRUE; } // Require entire field - if ($instance['settings']['url'] === 'optional' - && $instance['settings']['title'] === 'optional' - && $instance['required'] == 1 - && !$optional_field_found - && isset($instance['id'])) { - form_set_error($instance['field_name'] .'][0][title', - t('At least one title or URL must be entered.')); + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) { + form_set_error($instance['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); } } /** - * Cleanup user-entered values for a link field according to field settings. + * Clean up user-entered values for a link field according to field settings. * * @param $item * A single link item, usually containing url, title, and attributes. @@ -397,67 +401,88 @@ function _link_validate(&$item, $delta, $field, $node, $instance, &$optional_fie * The delta value if this field is one of multiple fields. * @param $field * The CCK field definition. - * @param $node - * The node containing this link. + * @param $entity + * The entity containing this link. */ -function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { +function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Don't try to process empty links. if (empty($item['url']) && empty($item['title'])) { return; } // Replace URL tokens. + $entity_type = $instance['entity_type']; + $entity_info = entity_get_info($entity_type); + $property_id = $entity_info['entity keys']['id']; + $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : ( + $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type + ); if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) { global $user; - // Load the node if necessary for nodes in views. - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $item['url'] = token_replace($item['url'], array('node' => $token_node)); + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded)); } $type = link_validate_url($item['url']); - // If we can't determine the type of url, and we've been told not to validate it, - // then we assume it's a LINK_EXTERNAL type for later processing. #357604 + // If the type of the URL cannot be determined and URL validation is disabled, + // then assume LINK_EXTERNAL for later processing. if ($type == FALSE && $instance['settings']['validate_url'] === 0) { $type = LINK_EXTERNAL; } $url = link_cleanup_url($item['url']); + $url_parts = _link_parse_url($url); - // Separate out the anchor if any. - if (strpos($url, '#') !== FALSE) { - $item['fragment'] = substr($url, strpos($url, '#') + 1); - $url = substr($url, 0, strpos($url, '#')); - } - // Separate out the query string if any. - if (strpos($url, '?') !== FALSE) { - $query = substr($url, strpos($url, '?') + 1); - parse_str($query, $query_array); - $item['query'] = $query_array; - $url = substr($url, 0, strpos($url, '?')); + // We can't check_plain('<front>') because it'll break. + if ($type != LINK_FRONT) { + $url_parts['url'] = check_plain($url_parts['url']); } - $item['url'] = check_plain($url); + if (!empty($url_parts['url'])) { + $item['url'] = url($url_parts['url'], + array( + 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, + 'absolute' => TRUE, + 'html' => TRUE, + ) + ); + } // Create a shortened URL for display. - $display_url = $type == LINK_EMAIL ? - str_replace('mailto:', '', $url) : - url($url, array('query' => isset($item['query']) ? - $item['query'] : - NULL, - 'fragment' => isset($item['fragment']) ? - $item['fragment'] : - NULL, - 'absolute' => TRUE)); + if ($type == LINK_EMAIL) { + $display_url = str_replace('mailto:', '', $url); + } + else { + $display_url = url($url_parts['url'], + array( + 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, + 'absolute' => TRUE, + ) + ); + } if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) { - $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) ."..."; + $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "..."; } $item['display_url'] = $display_url; // Use the title defined at the instance level. if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) { $title = $instance['settings']['title_value']; + if (function_exists('i18n_string_translate')) { + $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value"; + $title = i18n_string_translate($i18n_string_name, $title); + } } // Use the title defined by the user at the widget level. - else if (isset($item['title'])) { + elseif (isset($item['title'])) { $title = $item['title']; } else { @@ -466,10 +491,16 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { // Replace tokens. if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) { - // Load the node if necessary for nodes in views. - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $title = filter_xss(token_replace($title, array('node' => $token_node)), - array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $title = token_replace($title, array($entity_token_type => $entity_loaded)); + $title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); $item['html'] = TRUE; } $item['title'] = empty($title) ? $item['display_url'] : $title; @@ -484,7 +515,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { } // Add default attributes. - if (!is_array($instance['settings']['attributes'])){ + if (!is_array($instance['settings']['attributes'])) { $instance['settings']['attributes'] = _link_default_attributes(); } else { @@ -499,23 +530,35 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) { $item['attributes']['target'] = $instance['settings']['attributes']['target']; } + elseif ($item['attributes']['target'] == LINK_TARGET_USER) { + $item['attributes']['target'] = LINK_TARGET_DEFAULT; + } // Remove the target attribute if the default (no target) is selected. - if (empty($item['attributes']) || $item['attributes']['target'] == LINK_TARGET_DEFAULT) { + if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) { unset($item['attributes']['target']); } - // Remove the rel=nofollow for internal links. - if ($type != LINK_EXTERNAL && strpos($item['attributes']['rel'], 'nofollow') !== FALSE) { - $item['attributes']['rel'] = str_replace('nofollow', '', $item['attributes']); + // Remove rel attribute for internal or external links if selected. + if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') { + if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) || + ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) { + unset($item['attributes']['rel']); + } } // Handle "title" link attribute. if (!empty($item['attributes']['title']) && module_exists('token')) { - // Load the node (necessary for nodes in views). - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $item['attributes']['title'] = filter_xss(token_replace($item['attributes']['title'], array('node' => $token_node)), - array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); + // Load the entity (necessary for entities in views). + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded)); + $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); } // Remove title attribute if it's equal to link text. if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) { @@ -527,14 +570,46 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { $item['attributes'] = array_filter($item['attributes']); // Sets title to trimmed url if one exists - // @TODO: Do we need this? It seems not. + // @todo: Obsolete? /*if(!empty($item['display_url']) && empty($item['title'])) { $item['title'] = $item['display_url']; } elseif(!isset($item['title'])) { $item['title'] = $item['url']; }*/ +} +/** + * Because parse_url doesn't work with relative urls. + * + * @param string $url + * URL to parse. + * + * @return Array + * Array of url pieces - only 'url', 'query', and 'fragment'. + */ +function _link_parse_url($url) { + $url_parts = array(); + // Separate out the anchor, if any. + if (strpos($url, '#') !== FALSE) { + $url_parts['fragment'] = substr($url, strpos($url, '#') + 1); + $url = substr($url, 0, strpos($url, '#')); + } + // Separate out the query string, if any. + if (strpos($url, '?') !== FALSE) { + $query = substr($url, strpos($url, '?') + 1); + parse_str($query, $query_array); + // See http://drupal.org/node/1710578 + foreach ($query_array as $key=> &$value) { + if ($value === '' && FALSE === strpos($query, $key . '=')) { + $value = NULL; + } + } + $url_parts['query'] = $query_array; + $url = substr($url, 0, strpos($url, '?')); + } + $url_parts['url'] = $url; + return $url_parts; } /** @@ -542,15 +617,18 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { */ function link_theme() { return array( - /*'link_field_settings' => array( - 'variables' => array('element' => NULL), - ),*/ 'link_formatter_link_default' => array( 'variables' => array('element' => NULL), ), 'link_formatter_link_plain' => array( 'variables' => array('element' => NULL), ), + 'link_formatter_link_absolute' => array( + 'variables' => array('element' => NULL), + ), + 'link_formatter_link_domain' => array( + 'variables' => array('element' => NULL, 'display' => NULL), + ), 'link_formatter_link_title_plain' => array( 'variables' => array('element' => NULL), ), @@ -561,7 +639,7 @@ function link_theme() { 'variables' => array('element' => NULL), ), 'link_formatter_link_label' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_separate' => array( 'variables' => array('element' => NULL), @@ -573,31 +651,30 @@ function link_theme() { } /** - * FAPI theme for an individual text elements. + * Formats a link field widget. */ function theme_link_field($vars) { - drupal_add_css(drupal_get_path('module', 'link') .'/link.css'); - + drupal_add_css(drupal_get_path('module', 'link') . '/link.css'); $element = $vars['element']; // Prefix single value link fields with the name of the field. if (empty($element['#field']['multiple'])) { if (isset($element['url']) && !isset($element['title'])) { - unset($element['url']['#title']); + $element['url']['#title_display'] = 'invisible'; } } $output = ''; $output .= '<div class="link-field-subrow clearfix">'; if (isset($element['title'])) { - $output .= '<div class="link-field-title link-field-column">'. drupal_render($element['title']) .'</div>'; + $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>'; } - $output .= '<div class="link-field-url'. (isset($element['title']) ? ' link-field-column' : '') .'">'. drupal_render($element['url']) .'</div>'; + $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">'. drupal_render($element['url']) . '</div>'; $output .= '</div>'; if (!empty($element['attributes']['target'])) { - $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['target']) .'</div>'; + $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>'; } if (!empty($element['attributes']['title'])) { - $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['title']) .'</div>'; + $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>'; } return $output; } @@ -607,7 +684,7 @@ function theme_link_field($vars) { */ function link_element_info() { $elements = array(); - $elements['link_field'] = array( + $elements['link_field'] = array( '#input' => TRUE, '#process' => array('link_field_process'), '#theme' => 'link_field', @@ -616,6 +693,9 @@ function link_element_info() { return $elements; } +/** + * Returns the default attributes and their values. + */ function _link_default_attributes() { return array( 'target' => LINK_TARGET_DEFAULT, @@ -625,7 +705,7 @@ function _link_default_attributes() { } /** - * Process the link type element before displaying the field. + * Processes the link type element before displaying the field. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. @@ -645,10 +725,10 @@ function link_field_process($element, $form_state, $complete_form) { if ($settings['title'] !== 'none' && $settings['title'] !== 'value') { $element['title'] = array( '#type' => 'textfield', - '#maxlength' => $settings['title_maxlength'], // patch #1307788 from nmc + '#maxlength' => $settings['title_maxlength'], '#title' => t('Title'), - '#description' => t('The link title is limited to '.$settings['title_maxlength'].' characters maximum.'), // patch #1307788 from nmc - '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, // davereids patch from jan 2011 + '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])), + '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL, ); } @@ -678,15 +758,19 @@ function link_field_process($element, $form_state, $complete_form) { ); } - // To prevent an extra required indicator, disable the required flag on the - // base element since all the sub-fields are already required if desired. - $element['#required'] = FALSE; // davereids patch from jan 2011 + // If the title field is avaliable or there are field accepts multiple values + // then allow the individual field items display the required asterisk if needed. + if (isset($element['title']) || isset($element['_weight'])) { + // To prevent an extra required indicator, disable the required flag on the + // base element since all the sub-fields are already required if desired. + $element['#required'] = FALSE; + } return $element; } /** - * Implementation of hook_field_formatter_info(). + * Implements hook_field_formatter_info(). */ function link_field_formatter_info() { return array( @@ -710,6 +794,19 @@ function link_field_formatter_info() { 'field types' => array('link_field'), 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), + 'link_absolute' => array( + 'label' => t('URL, absolute'), + 'field types' => array('link_field'), + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + 'link_domain' => array( + 'label' => t('Domain, as link'), + 'field types' => array('link_field'), + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'settings' => array( + 'strip_www' => FALSE, + ), + ), 'link_short' => array( 'label' => t('Short, as link with title "Link"'), 'field types' => array('link_field'), @@ -728,6 +825,40 @@ function link_field_formatter_info() { ); } +/** + * Implements hook_field_formatter_settings_form(). + */ +function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $element = array(); + if ($display['type'] == 'link_domain') { + $element['strip_www'] = array( + '#title' => t('Strip www. from domain'), + '#type' => 'checkbox', + '#default_value' => $settings['strip_www'], + ); + } + return $element; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function link_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + if ($display['type'] == 'link_domain') { + if ($display['settings']['strip_www']) { + return t('Strip www. from domain'); + } + else { + return t('Leave www. in domain'); + } + } + return ''; +} + /** * Implements hook_field_formatter_view(). */ @@ -735,25 +866,27 @@ function link_field_formatter_view($entity_type, $entity, $field, $instance, $la $elements = array(); foreach ($items as $delta => $item) { $elements[$delta] = array( - '#markup' => theme('link_formatter_'. $display['type'], array('element' => $item, 'field' => $instance)), + '#theme' => 'link_formatter_' . $display['type'], + '#element' => $item, + '#field' => $instance, + '#display' => $display, ); } return $elements; } /** - * Theme function for 'default' text field formatter. + * Formats a link. */ function theme_link_formatter_link_default($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); - - // Issue #1199806 by ss81: Fixes fatal error when the link URl is equal to page URL + unset($link_options['title']); + unset($link_options['url']); + if (isset($link_options['attributes']['class'])) { $link_options['attributes']['class'] = array($link_options['attributes']['class']); } - + // Display a normal link if both title and URL are available. if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) { return l($vars['element']['title'], $vars['element']['url'], $link_options); @@ -768,89 +901,114 @@ function theme_link_formatter_link_default($vars) { } /** - * Theme function for 'plain' text field formatter. + * Formats a link (or its title) as plain text. */ function theme_link_formatter_link_plain($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); - return empty($vars['element']['url']) ? - check_plain($vars['element']['title']) : - url($vars['element']['url'], $link_options); + if (isset($link_options['title'])) { + unset($link_options['title']); + } + else { + $vars['element']['title'] = ''; + } + unset($link_options['url']); + return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options); +} + +/** + * Formats a link as an absolute URL + */ +function theme_link_formatter_link_absolute($vars) { + $absolute = array('absolute' => TRUE); + return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']); } /** - * Theme function for 'title_plain' text field formatter. + * Formats a link using the URL's domain for it's link text. + */ +function theme_link_formatter_link_domain($vars) { + $link_options = $vars['element']; + unset($link_options['title']); + unset($link_options['url']); + $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST); + if (!empty($vars['display']['settings']['strip_www'])) { + $domain = str_replace('www.', '', $domain); + } + return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : ''; +} + +/** + * Formats a link's title as plain text. */ function theme_link_formatter_link_title_plain($vars) { return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']); } /** - * Theme function for 'url' text field formatter. + * Formats a link using an alternate display URL for its link text. */ function theme_link_formatter_link_url($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'short' text field formatter. + * Formats a link using "Link" as the link text. */ function theme_link_formatter_link_short($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'label' text field formatter. + * Formats a link using the field's label as link text. */ function theme_link_formatter_link_label($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'separate' text field formatter. + * Formats a link as separate title and URL elements. */ - function theme_link_formatter_link_separate($vars) { - $class = empty($vars['element']['attributes']['class']) ? '' : ' '. $vars['element']['attributes']['class']; + $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class']; unset($vars['element']['attributes']['class']); $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']); - - /** - * @TODO static html markup looks not very elegant to me (who takes it off?) - * needs smarter output solution and an optional title/url seperator (digidog) + + /** + * @TODO static html markup looks not very elegant + * needs smarter output solution and an optional title/url seperator */ + $url_parts = _link_parse_url($vars['element']['url']); $output = ''; - $output .= '<div class="link-item '. $class .'">'; + $output .= '<div class="link-item ' . $class . '">'; if (!empty($title)) { - $output .= '<div class="link-title">'. $title .'</div>'; + $output .= '<div class="link-title">' . $title . '</div>'; } - $output .= '<div class="link-url">'. l($vars['element']['url'], $vars['element']['url'], $link_options) .'</div>'; + $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>'; $output .= '</div>'; return $output; } - +/** + * Implements hook_token_list(). + */ function link_token_list($type = 'all') { if ($type === 'field' || $type === 'all') { $tokens = array(); - $tokens['link']['url'] = t("Link URL"); $tokens['link']['title'] = t("Link title"); $tokens['link']['view'] = t("Formatted html link"); - return $tokens; } } @@ -873,18 +1031,21 @@ function link_token_values($type, $object = NULL) { function link_views_api() { return array( 'api' => 2, - 'path' => drupal_get_path('module', 'link') .'/views', + 'path' => drupal_get_path('module', 'link') . '/views', ); } /** * Forms a valid URL if possible from an entered address. - * Trims whitespace and automatically adds an http:// to addresses without a protocol specified + * + * Trims whitespace and automatically adds an http:// to addresses without a + * protocol specified * * @param string $url - * @param string $protocol The protocol to be prepended to the url if one is not specified + * @param string $protocol + * The protocol to be prepended to the url if one is not specified */ -function link_cleanup_url($url, $protocol = "http") { +function link_cleanup_url($url, $protocol = 'http') { $url = trim($url); $type = link_validate_url($url); @@ -893,9 +1054,10 @@ function link_cleanup_url($url, $protocol = "http") { $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url); if (empty($protocol_match)) { // But should there be? Add an automatic http:// if it starts with a domain name. - $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i', $url); + $LINK_DOMAINS = _link_domains(); + $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z]{2}))/i', $url); if (!empty($domain_match)) { - $url = $protocol ."://". $url; + $url = $protocol . "://" . $url; } } } @@ -904,16 +1066,20 @@ function link_cleanup_url($url, $protocol = "http") { } /** - * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard - * for URL formation and all email addresses following the RFC 2368 standard for - * mailto address formation. + * Validates a URL. + * + * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail + * addresses following the RFC 2368 standard for mailto address formation. * * @param string $text - * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with - * the following attributes: protocol, hostname, ip, and port. + * + * @return mixed + * Returns boolean FALSE if the URL is not valid. On success, returns one of + * the LINK_(linktype) constants. */ function link_validate_url($text) { - $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( // @TODO completing letters ... + // @TODO Complete letters. + $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( "æ", // æ "Æ", // Æ "À", // À @@ -948,8 +1114,8 @@ function link_validate_url($text) { "Ö", // Ö "Ô", // Ô "ô", // ô - "Õ", // Õ - "õ", // õ + "Õ", // Õ + "õ", // õ "Œ", // Œ "œ", // œ "ü", // ü @@ -959,7 +1125,7 @@ function link_validate_url($text) { "Û", // Û "û", // û "Ÿ", // Ÿ - "ÿ", // ÿ + "ÿ", // ÿ "Ñ", // Ñ "ñ", // ñ "þ", // þ @@ -973,36 +1139,37 @@ function link_validate_url($text) { "ß", // ß )), ENT_QUOTES, 'UTF-8'); $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal')); + $LINK_DOMAINS = _link_domains(); // Starting a parenthesis group with (?: means that it is grouped, but is not captured - $protocol = '((?:'. implode("|", $allowed_protocols) .'):\/\/)'; - $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w". $LINK_ICHARS ."\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)"; - $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9'. $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*('. LINK_DOMAINS .'|[a-z]{2}))?)'; + $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)'; + $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w" . $LINK_ICHARS . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)"; + $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*(' . $LINK_DOMAINS . '|[a-z]{2}))?)'; $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})'; $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})'; $port = '(?::([0-9]{1,5}))'; // Pattern specific to external links. - $external_pattern = '/^'. $protocol .'?'. $authentication .'?('. $domain .'|'. $ipv4 .'|'. $ipv6 .' |localhost)'. $port .'?'; + $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?'; // Pattern specific to internal links. - $internal_pattern = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]]+)"; - $internal_pattern_file = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]\.]+)$/i"; + $internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\] ]+)"; + $internal_pattern_file = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)]+)$/i"; - $directories = "(?:\/[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'#!():;*@\[\]]*)*"; + $directories = "(?:\/[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'#!():;*@\[\]]*)*"; // Yes, four backslashes == a single backslash. - $query = "(?:\/?\?([?a-z0-9". $LINK_ICHARS ."+_|\-\.~\/\\\\%=&,$'():;*@\[\]{} ]*))"; - $anchor = "(?:#[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'():;*@\[\]\/\?]*)"; + $query = "(?:\/?\?([?a-z0-9" . $LINK_ICHARS . "+_|\-\.~\/\\\\%=&,$'():;*@\[\]{} ]*))"; + $anchor = "(?:#[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)"; // The rest of the path for a standard URL. - $end = $directories .'?'. $query .'?'. $anchor .'?'.'$/i'; + $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i'; - $message_id = '[^@].*@'. $domain; + $message_id = '[^@].*@' . $domain; $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*'; - $news_pattern = '/^news:('. $newsgroup_name .'|'. $message_id .')$/i'; + $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i'; - $user = '[a-zA-Z0-9'. $LINK_ICHARS .'_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+'; - $email_pattern = '/^mailto:'. $user .'@'.'(?:'. $domain .'|'. $ipv4 .'|'. $ipv6 .'|localhost)'. $query .'?$/'; + $user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+'; + $email_pattern = '/^mailto:' . $user . '@'.'(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/'; if (strpos($text, '<front>') === 0) { return LINK_FRONT; @@ -1026,13 +1193,25 @@ function link_validate_url($text) { return FALSE; } +/** + * Returns the list of allowed domains, including domains added by admins via variable_set/$config. + */ +function _link_domains() { + $link_extra_domains = variable_get('link_extra_domains', array()); + return empty($link_extra_domains) ? LINK_DOMAINS : LINK_DOMAINS . '|' . implode('|', $link_extra_domains); +} + /** * Implements hook_migrate_field_alter(). */ function link_content_migrate_field_alter(&$field_value, $instance_value) { if ($field_value['type'] == 'link') { - // need to change the type: + // Adjust the field type. $field_value['type'] = 'link_field'; + // Remove settings that are now on the instance. + foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) { + unset($field_value['settings'][$setting]); + } } } @@ -1042,9 +1221,23 @@ function link_content_migrate_field_alter(&$field_value, $instance_value) { * Widget type also changed to link_field. */ function link_content_migrate_instance_alter(&$instance_value, $field_value) { - if ($instance_value['widget']['module'] == 'link' - && $instance_value['widget']['type'] == 'link') { - $instance_value['widget']['type'] = 'link_field'; + if ($field_value['type'] == 'link') { + // Grab settings that were previously on the field. + foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) { + if (isset($field_value['settings'][$setting])) { + $instance_value['settings'][$setting] = $field_value['settings'][$setting]; + } + } + // Adjust widget type. + if ($instance_value['widget']['type'] == 'link') { + $instance_value['widget']['type'] = 'link_field'; + } + // Adjust formatter types. + foreach ($instance_value['display'] as $context => $settings) { + if (in_array($settings['type'], array('default', 'title_plain', 'url', 'plain', 'short', 'label', 'separate'))) { + $instance_value['display'][$context]['type'] = 'link_' . $settings['type']; + } + } } } @@ -1057,6 +1250,7 @@ function link_field_settings_form() { /** * Additional callback to adapt the property info of link fields. + * * @see entity_metadata_field_entity_property_info(). */ function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) { @@ -1067,7 +1261,7 @@ function link_field_property_info_callback(&$info, $entity_type, $field, $instan $property['setter callback'] = 'entity_metadata_field_verbatim_set'; // Auto-create the field item as soon as a property is set. - $property['auto creation'] = 'link_field_item_create'; + $property['auto creation'] = 'link_field_item_create'; $property['property info'] = link_field_item_property_info(); $property['property info']['url']['required'] = !$instance['settings']['url']; @@ -1075,7 +1269,6 @@ function link_field_property_info_callback(&$info, $entity_type, $field, $instan if ($instance['settings']['title'] == 'none') { unset($property['property info']['title']); } - unset($property['query callback']); } @@ -1104,3 +1297,24 @@ function link_field_item_property_info() { ); return $properties; } + +/** + * Implements hook_field_update_instance(). + */ +function link_field_update_instance($instance, $prior_instance) { + if (function_exists('i18n_string_update') && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) { + $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value"; + i18n_string_update($i18n_string_name, $instance['settings']['title_value']); + } +} + +/** + * Implements hook_i18n_string_list_TEXTGROUP_alter(). + */ +function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) { + if ($type == 'field_instance' && $object && $object['widget']['type'] == 'link_field') { + if (isset($object['settings']['title_value'])) { + $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value']; + } + } +} diff --git a/sites/all/modules/link/tests/link.attribute.test b/sites/all/modules/link/tests/link.attribute.test index 398713a04e640c42fe167c5881ab26ccfb31c230..97ac2a478fc77a6a9aecc08a4606f65c1c925884 100644 --- a/sites/all/modules/link/tests/link.attribute.test +++ b/sites/all/modules/link/tests/link.attribute.test @@ -6,10 +6,9 @@ */ class LinkAttributeCrudTest extends DrupalWebTestCase { - private $zebra; - public $permissions = array( + protected $permissions = array( 'access content', 'administer content types', 'administer nodes', @@ -29,15 +28,14 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { } function setup() { + parent::setup('field_ui', 'link'); $this->zebra = 0; - parent::setup('field_ui', 'link'); // was 'views' - //$this->loginWithPermissions($this->permissions); // Create and login user. - $account = $this->drupalCreateUser(array('administer content types')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types')); + $this->drupalLogin($this->web_user); } - function createLink($url, $title, $attributes = array()) { + protected function createLink($url, $title, $attributes = array()) { return array( 'url' => $url, 'title' => $title, @@ -45,7 +43,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { ); } - private function assertLinkOnNode($field_name, $link_value, $message = '', $group = 'Other') { + protected function assertLinkOnNode($field_name, $link_value, $message = '', $group = 'Other') { $this->zebra++; $zebra_string = ($this->zebra % 2 == 0) ? 'even' : 'odd'; $cssFieldLocator = 'field-'. str_replace('_', '-', $field_name); @@ -59,9 +57,6 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { * that the node is being displayed. */ function testBasic() { - /*$this->acquireContentTypes(1); - variable_set('node_options_'. $this->content_types[0]->name, array('status', 'promote'));*/ - $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); $title = $this->randomName(20); @@ -113,8 +108,8 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { // Now that we have a new content type, create a user that has privileges // on the content type. $permissions = array_merge($this->permissions, array($permission)); - $account = $this->drupalCreateUser($permissions); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); // Go to page. $this->drupalGet('node/add/'. $content_type_machine); @@ -129,33 +124,9 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title))); - /*$field_settings = array( - 'type' => 'link', - 'widget_type' => 'link', - 'type_name' => $this->content_types[0]->name, - 'attributes' => array(), // <-- This is needed or we have an error. - ); - - $field = $this->createField($field_settings, 0); - //$this->pass('<pre>'. print_r($field, TRUE) .'</pre>'); - $field_db_info = content_database_info($field);*/ - - //$this->acquireNodes(2); - /*$node = $this->drupalCreateNode(array('type' => $content_type_machine, - 'promote' => 1)); - $test_nid = $node->nid;*/ - - //$node = node_load($this->nodes[0]->nid); - //$node->promote = 1; // We want this to show on front page for the teaser test. - /*$this->assert('debug', print_r($node, TRUE), 'Debug'); - $node->{$single_field_name}['und'][0] = $this->createLink('http://www.example.com', 'Test Link'); - node_save($node); - $this->assert('debug', print_r($node, TRUE), 'Debug');*/ - - //$this->drupalGet('node/'. $test_nid .'/edit'); $this->drupalGet('node/add/'. $content_type_machine); - // lets add a node: + // Create a node: $edit = array( 'title' => $title, 'field_' . $single_field_name_machine . '[und][0][url]' => 'http://www.example.com/', @@ -167,25 +138,11 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title))); $this->assertText('Display'); - //$this->assertText('http://www.example.com/'); $this->assertLinkByHref('http://www.example.com'); } - private function createNodeType($content_type_machine, $content_type_friendly) { - $this->drupalGet('admin/structure/types'); - - // Create the content type. - $this->clickLink(t('Add content type')); - - $edit = array ( - 'name' => $content_type_friendly, - 'type' => $content_type_machine, - ); - $this->drupalPost(NULL, $edit, t('Save and add fields')); - $this->assertText(t('The content type @name has been added.', array('@name' => $content_type_friendly))); - } - - private function createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine) { + protected function createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine) { + $this->drupalGet('admin/structure/types/manage/' . $content_type_machine . '/fields'); $edit = array ( 'fields[_add_new_field][label]' => $single_field_name_friendly, 'fields[_add_new_field][field_name]' => $single_field_name_machine, @@ -209,7 +166,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertTrue($type_exists, 'The new content type has been created in the database.'); } - function createNodeTypeUser($content_type_machine) { + protected function createNodeTypeUser($content_type_machine) { $permission = 'create ' . $content_type_machine . ' content'; $permission_edit = 'edit ' . $content_type_machine . ' content'; // Reset the permissions cache. @@ -218,12 +175,11 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { // Now that we have a new content type, create a user that has privileges // on the content type. $permissions = array_merge($this->permissions, array($permission)); - $account = $this->drupalCreateUser($permissions); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); } - function createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $title, $url, $node_title = '') { - // Go to page. + protected function createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $title, $url, $node_title = '') { $this->drupalGet('node/add/'. $content_type_machine); if (!$node_title) { @@ -239,48 +195,22 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $edit['field_' . $single_field_name_machine . '[und][0][title]'] = $title; } - // Now we can fill in the second item in the multivalue field and save. $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $node_title))); } + /** + * Test the link_plain formatter and it's output. + */ function testFormatterPlain() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_plain', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); - } - - function testFormatterPlainWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -297,82 +227,41 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); - } - - function testFormatterPlainWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_plain', + + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText($link_url); + $this->assertNoText($link_text); + $this->assertNoLinkByHref($link_url); + } } function testFormatterURL() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_url', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterURLWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -389,81 +278,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterURLWithAnchor() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_url', + + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertNoText($link_text); + $this->assertLinkByHref($link_url); + } } function testFormatterShort() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_short', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterShortWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -481,82 +329,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterShortWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_short', + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText('Link'); + $this->assertNoText($link_text); + $this->assertLinkByHref($link_url); + } } function testFormatterLabel() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_label', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); - } - - function testFormatterLabelWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -574,82 +380,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); - } - - function testFormatterLabelWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_label', + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertNoText($link_text); + $this->assertText($single_field_name_friendly); + $this->assertLinkByHref($link_url); + } } function testFormatterSeparate() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_separate', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink($link_url); - $this->assertLinkByHref($link_url); - } - - function testFormatterSeparateWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -667,51 +431,41 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink('http://www.example.com/'); - $this->assertLinkByHref($link_url); - } - - function testFormatterSeparateWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_separate', + $plain_url = 'http://www.example.com/'; + $link_tests = array( + 'plain' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url, + ), + 'query' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url . '?q=test', + ), + 'fragment' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url . '#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->createNodeTypeUser($content_type_machine); - - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink('http://www.example.com/'); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText($link_text); + $this->assertLink($plain_url); + $this->assertLinkByHref($link_url); + } } function testFormatterPlainTitle() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -737,45 +491,4 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertNoText($link_url); $this->assertNoLinkByHref($link_url); } - - /** - * This test sees that we can create a link field with a defined class, and make sure - * that class displays properly when the link is displayed. - */ - /*function testLinkWithClassOnField() { - $this->acquireContentTypes(1); - $field_settings = array( - 'type' => 'link', - 'widget_type' => 'link', - 'type_name' => $this->content_types[0]->name, - 'attributes' => array( - 'class' => 'test-class', - 'target' => 'default', - 'rel' => FALSE, - ), - ); - - $field = $this->createField($field_settings, 0); - //$this->pass('<pre>'. print_r($field, TRUE) .'</pre>'); - $field_db_info = content_database_info($field); - - $this->acquireNodes(2); - - $node = node_load($this->nodes[0]->nid); - $node->promote = 1; // We want this to show on front page for the teaser test. - $node->{$field['field_name']}[0] = $this->createLink('http://www.example.com', 'Test Link'); - node_save($node); - - // Does this display on the node page? - $this->drupalGet('node/'. $this->nodes[0]->nid); - //$this->outputScreenContents('Link field with class', 'link_'); - $this->assertLinkOnNode($field['field_name'], l('Test Link', 'http://www.example.com', array('attributes' => array('class' => 'test-class')))); - - // Does this display on the front page? - $this->drupalGet('<front>'); - // reset the zebra! - $this->zebra = 0; - $this->assertLinkOnNode($field['field_name'], l('Test Link', 'http://www.example.com', array('attributes' => array('class' => 'test-class')))); - }*/ - } diff --git a/sites/all/modules/link/tests/link.crud.test b/sites/all/modules/link/tests/link.crud.test index cdd7fc5aec4fb7ab27f96a8de19d5ec104cd97cf..54889fd298b504c8203e9de346a8196262df3f7e 100644 --- a/sites/all/modules/link/tests/link.crud.test +++ b/sites/all/modules/link/tests/link.crud.test @@ -16,8 +16,7 @@ class LinkContentCrudTest extends DrupalWebTestCase { } function setUp() { - parent::setUp('field_ui', 'link'); // was views? - //$this->loginWithPermissions(); + parent::setUp('field_ui', 'link'); } /** @@ -30,15 +29,14 @@ class LinkContentCrudTest extends DrupalWebTestCase { $title = $this->randomName(20); // Create and login user. - $account = $this->drupalCreateUser(array('administer content types')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types')); + $this->drupalLogin($this->web_user); $this->drupalGet('admin/structure/types'); // Create the content type. $this->clickLink(t('Add content type')); - $edit = array ( 'name' => $content_type_friendly, 'type' => $content_type_machine, @@ -55,7 +53,6 @@ class LinkContentCrudTest extends DrupalWebTestCase { 'fields[_add_new_field][field_name]' => $single_field_name_machine, 'fields[_add_new_field][type]' => 'link_field', 'fields[_add_new_field][widget_type]' => 'link_field', - ); $this->drupalPost(NULL, $edit, t('Save')); diff --git a/sites/all/modules/link/tests/link.crud_browser.test b/sites/all/modules/link/tests/link.crud_browser.test index 7b01da0ff75e2e2c1d8ba06a0fc5f416dbcf6491..95406018089a663e5584f8a93e6358e7caf7d0c9 100644 --- a/sites/all/modules/link/tests/link.crud_browser.test +++ b/sites/all/modules/link/tests/link.crud_browser.test @@ -42,13 +42,15 @@ class LinkUITest extends DrupalWebTestcase { */ function testLinkCreate() { //libxml_use_internal_errors(true); - $account = $this->drupalCreateUser(array('administer content types', - 'administer nodes', - 'administer filters', - 'access content', - 'create page content', - 'access administration pages')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer nodes', + 'administer filters', + 'access content', + 'create page content', + 'access administration pages' + )); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -125,7 +127,7 @@ class LinkUITest extends DrupalWebTestcase { $input_test_cases[] = $test_case; foreach ($input_test_cases as $input) { - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -135,7 +137,7 @@ class LinkUITest extends DrupalWebTestcase { ); $this->drupalPost(NULL, $edit, t('Save')); if ($input['type'] == self::LINK_INPUT_TYPE_BAD_URL) { - $this->assertRaw(t('Not a valid URL.'), 'Not a valid URL: ' . $input['href']); + $this->assertRaw(t('The value provided for %field is not a valid URL.', array('%field' => $name)), 'Not a valid URL: ' . $input['href']); continue; } else { @@ -167,122 +169,13 @@ class LinkUITest extends DrupalWebTestcase { //libxml_use_internal_errors(FALSE); } - /** - * Creates a link field for the "page" type and creates a page with a link. - * Just like the above test, only here we're turning off the validation on the field. - */ - /*function testLinkCreate_NoValidation() { - //libxml_use_internal_errors(true); - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); - - // create field - $name = strtolower($this->randomName()); - $edit = array( - 'fields[_add_new_field][label]' => $name, - 'fields[_add_new_field][field_name]' => $name, - 'fields[_add_new_field][type]' => 'link_field', - 'fields[_add_new_field][widget_type]' => 'link_field', - ); - $this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save')); - $this->drupalPost(NULL, array(), t('Save field settings')); - $this->drupalPost(NULL, array('validate_url' => FALSE), t('Save settings')); - - // Is field created? - $this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added'); - _content_type_info(TRUE); - $fields = content_fields(); - $this->assertTRUE(0 === $fields['field_'. $name]['validate_url'], 'Make sure validation is off.'); - - // create page form - $this->drupalGet('node/add/page'); - $field_name = 'field_' . $name; - $this->assertField($field_name . '[0][title]', 'Title found'); - $this->assertField($field_name . '[0][url]', 'URL found'); - - $input_test_cases = array( - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName(), - 'msg' => 'Link found', - 'type' => self::LINK_INPUT_TYPE_GOOD - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '<script>alert("hi");</script>', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '<script src="http://devil.site.com"></script>', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '" onmouseover="alert(\'hi\')', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '\' onmouseover="alert(\'hi\')', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'javascript:alert("http://example.com/' . $this->randomName() . '")', - 'label' => $this->randomName(), - 'msg' => 'js url', - 'type' => self::LINK_INPUT_TYPE_BAD_URL - ), - ); - foreach ($input_test_cases as $input) { - $this->drupalLogin($account); - $this->drupalGet('node/add/page'); - - $edit = array( - 'title' => $input['label'], - $field_name . '[0][title]' => $input['label'], - $field_name . '[0][url]' => $input['href'], - ); - $this->drupalPost(NULL, $edit, t('Save')); - if ($input['type'] == self::LINK_INPUT_TYPE_BAD_URL) { - //$this->assertRaw(t('Not a valid URL.'), 'Not a valid URL: ' . $input['href']); - $this->assertNoRaw($input['href']); - $this->assertRaw(t('@type %title has been created.', array('@type' => 'Basic Page', '%title' => $edit['title'])), 'Page created: ' . $input['href']); - continue; - } - else { - $this->assertRaw(t('@type %title has been created.', array('@type' => 'Basic Page', '%title' => $edit['title'])), 'Page created: ' . $input['href']); - } - $url = $this->getUrl(); - - // change to anonym user - $this->drupalLogout(); - - $this->drupalGet($url); - //debug($this); - // If simpletest starts using something to override the error system, this will flag - // us and let us know it's broken. - $this->assertFalse(libxml_use_internal_errors(TRUE)); - $path = '//a[@href="'. $input['href'] .'" and text()="'. $input['label'] .'"]'; - //$this->pass(htmlentities($path)); - $elements = $this->xpath($path); - libxml_use_internal_errors(FALSE); - $this->assertIdentical(isset($elements[0]), $input['type'] == self::LINK_INPUT_TYPE_GOOD, $input['msg']); - } - //libxml_use_internal_errors(FALSE); - }*/ - /** * Testing that if you use <strong> in a static title for your link, that the * title actually displays <strong>. */ function testStaticLinkCreate() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -330,8 +223,8 @@ class LinkUITest extends DrupalWebTestcase { * sure they are set to the expected results. */ function testCRUDCreateFieldDefaults() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); diff --git a/sites/all/modules/link/tests/link.test b/sites/all/modules/link/tests/link.test index dfb4dda337f0162710949d454b3b0bd8db00bf67..538e3a6769859e7c7007421b5839a1225e338b0d 100644 --- a/sites/all/modules/link/tests/link.test +++ b/sites/all/modules/link/tests/link.test @@ -6,7 +6,7 @@ */ class LinkBaseTestClass extends DrupalWebTestCase { - public $permissions = array( + protected $permissions = array( 'access content', 'administer content types', 'administer nodes', @@ -17,21 +17,18 @@ class LinkBaseTestClass extends DrupalWebTestCase { 'create page content', ); - public $account; - - function setUp($modules = array()) { - if ($modules) { - parent::setUp($modules); - } - else { - parent::setUp('field_ui', 'link'); - } - $this->account = $this->drupalCreateUser($this->permissions); - $this->drupalLogin($this->account); + function setUp() { + $modules = func_get_args(); + $modules = (isset($modules[0]) && is_array($modules[0]) ? $modules[0] : $modules); + $modules[] = 'field_ui'; + $modules[] = 'link'; + parent::setUp($modules); + + $this->web_user = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->web_user); } - function createLinkField($node_type = 'page', - $settings = array()) { + protected function createLinkField($node_type = 'page', $settings = array()) { $name = strtolower($this->randomName()); $edit = array( 'fields[_add_new_field][label]' => $name, diff --git a/sites/all/modules/link/tests/link.token.test b/sites/all/modules/link/tests/link.token.test index d23f46a78e54a675b4c5943e5831a12a97912551..de3ed3fef587b4fd77f7877e03032c5a593ca5e7 100644 --- a/sites/all/modules/link/tests/link.token.test +++ b/sites/all/modules/link/tests/link.token.test @@ -20,10 +20,7 @@ class LinkTokenTest extends LinkBaseTestClass { } function setUp($modules = array()) { - $modules[] = 'field_ui'; - $modules[] = 'link'; - $modules[] = 'token'; - parent::setUp($modules); + parent::setUp(array('token')); } /** @@ -31,9 +28,6 @@ class LinkTokenTest extends LinkBaseTestClass { * Creates a node with a token in the link title and checks the value. */ function testUserTokenLinkCreate() { - /*$account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account);*/ - // create field $settings = array( 'instance[settings][enable_tokens]' => 1, @@ -52,7 +46,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -92,7 +86,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'href' => 'http://example.com/' . $this->randomName() ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -135,7 +129,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'href' => 'http://example.com/' . $this->randomName() ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -280,8 +274,8 @@ class LinkTokenTest extends LinkBaseTestClass { * Trying to set the url to contain a token. */ function _testUserTokenLinkCreateInURL() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -310,7 +304,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -333,8 +327,8 @@ class LinkTokenTest extends LinkBaseTestClass { * Trying to set the url to contain a token. */ function _testUserTokenLinkCreateInURL2() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -363,7 +357,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -378,6 +372,6 @@ class LinkTokenTest extends LinkBaseTestClass { $this->drupalLogout(); $this->drupalGet($url); - $this->assertRaw(l($input['label'], $input['href'] .'/'. $account->uid)); + $this->assertRaw(l($input['label'], $input['href'] .'/'. $this->web_user->uid)); } } diff --git a/sites/all/modules/link/tests/link.validate.test b/sites/all/modules/link/tests/link.validate.test index affa7e94517f2d584b893d6d702083f8865f561f..0f721fbe181ed9c40118878e0d698d2b67ee2074 100644 --- a/sites/all/modules/link/tests/link.validate.test +++ b/sites/all/modules/link/tests/link.validate.test @@ -7,11 +7,7 @@ class LinkValidateTestCase extends LinkBaseTestClass { - function setUp($modules = array()) { - parent::setUp($modules); - } - - function createLink($url, $title, $attributes = array()) { + protected function createLink($url, $title, $attributes = array()) { return array( 'url' => $url, 'title' => $title, @@ -22,7 +18,7 @@ class LinkValidateTestCase extends LinkBaseTestClass { /** * Takes a url, and sees if it can validate that the url is valid. */ - public function link_test_validate_url($url) { + protected function link_test_validate_url($url) { $field_name = $this->createLinkField(); @@ -66,13 +62,13 @@ class LinkValidateTest extends LinkValidateTestCase { * Test if we're stopped from posting a bad url on default validation. */ function test_link_validate_bad_url_validate_default() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -104,20 +100,20 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText(t('Not a valid URL.')); + $this->assertText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } /** * Test if we're stopped from posting a bad url with validation on. */ function test_link_validate_bad_url_validate_on() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -149,7 +145,7 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText(t('Not a valid URL.')); + $this->assertText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } @@ -157,13 +153,13 @@ class LinkValidateTest extends LinkValidateTestCase { * Test if we can post a bad url if the validation is expressly turned off. */ function test_link_validate_bad_url_validate_off() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -199,8 +195,7 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertNoText(t('Not a valid URL.')); - + $this->assertNoText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } /** @@ -208,13 +203,13 @@ class LinkValidateTest extends LinkValidateTestCase { */ function x_test_link_validate_switching_between_validation_status() { $this->acquireContentTypes(1); - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'access content', 'create '. $this->content_types[0]->type .' content', 'edit any '. $this->content_types[0]->type .' content')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); variable_set('node_options_'. $this->content_types[0]->name, array('status', 'promote')); $field_settings = array( 'type' => 'link', @@ -242,7 +237,7 @@ class LinkValidateTest extends LinkValidateTestCase { $this->drupalPost('node/'. $this->nodes[0]->nid .'/edit', $edit, t('Save')); //$this->pass($this->content); - $this->assertNoText(t('Not a valid URL.')); + $this->assertNoText(t('The value provided for %field is not a valid URL.', array('%field' => $name))); // Make sure we get a new version! $node = node_load($this->nodes[0]->nid, NULL, TRUE); @@ -361,11 +356,6 @@ class LinkValidateSpecificURL extends LinkValidateTestCase { */ class LinkValidateUrlLight extends DrupalWebTestCase { - //function setUp() { - // do we need to include something here? - //module_load_include('inc', 'link'); - //} - public static function getInfo() { return array( 'name' => 'Link Light Validation Tests', @@ -427,19 +417,18 @@ class LinkValidateUrlLight extends DrupalWebTestCase { $this->assertEqual(FALSE, $valid, 'newsgroup names can\'t contain underscores, so it should come back as invalid.'); } - function testValidateInternalLink() { - $valid = link_validate_url('node/5'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test normal internal link.'); - } - - function testValidateInternalLinkWithDot() { - $valid = link_validate_url('rss.xml'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test rss.xml internal link.'); - } - - function testValidateInternalLinkToFile() { - $valid = link_validate_url('files/test.jpg'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test files/test.jpg internal link.'); + function testValidateInternalLinks() { + $links = array( + 'node/5', + 'rss.xml', + 'files/test.jpg', + '/var/www/test', + ); + + foreach ($links as $link) { + $valid = link_validate_url($link); + $this->assertEqual(LINK_INTERNAL, $valid, 'Test ' . $link . ' internal link.'); + } } function testValidateExternalLinks() { @@ -481,15 +470,20 @@ class LinkValidateUrlLight extends DrupalWebTestCase { //$valid2 = valid_url($link, TRUE); //$this->assertEqual(TRUE, $valid2, "Using valid_url() on $link."); } + // Test if we can make a tld valid: + variable_set('link_extra_domains', array('frog')); + $valid = link_validate_url('http://www.example.frog'); + $this->assertEqual(LINK_EXTERNAL, $valid, "Testing that http://www.example.frog is a valid external link if we've added 'frog' to the list of valid domains."); } function testInvalidExternalLinks() { $links = array( 'http://www.ex ample.com/', - '//www.example.com/', 'http://25.0.0/', // bad ip! 'http://4827.0.0.2/', + '//www.example.com/', 'http://www.testß.com/', // ß not allowed in domain names! + 'http://www.example.frog/', // Bad TLD //'http://www.-fudge.com/', // domains can't have sections starting with a dash. ); foreach ($links as $link) { @@ -497,5 +491,4 @@ class LinkValidateUrlLight extends DrupalWebTestCase { $this->assertEqual(FALSE, $valid, 'Testing that '. $link .' is not a valid link.'); } } - } diff --git a/sites/all/modules/link/views/link_views_handler_argument_target.inc b/sites/all/modules/link/views/link_views_handler_argument_target.inc index b0a2a3ee48118e7c18e049f6f7d14beccd9782fc..3a6fdf013b6fefe883402c3da1c159902326cb26 100644 --- a/sites/all/modules/link/views/link_views_handler_argument_target.inc +++ b/sites/all/modules/link/views/link_views_handler_argument_target.inc @@ -93,7 +93,7 @@ class link_views_handler_argument_target extends views_handler_argument { '#default_value' => $this->options['validate_type'], ); - $validate_types = array('none' => t('<Basic validation>')); + $validate_types = array('none' => t('- Basic validation -')); $plugins = views_fetch_plugin_data('argument validator'); foreach ($plugins as $id => $info) { $valid = TRUE; @@ -140,10 +140,10 @@ class link_views_handler_argument_target extends views_handler_argument { * * The argument sent may be found at $this->argument. */ - function query() { + function query($group_by = FALSE) { $this->ensure_my_table(); // Because attributes are stored serialized, our only option is to also // serialize the data we're searching for and use LIKE to find similar data. - $this->query->add_where(0, $this->table_alias .'.'. $this->real_field ." LIKE '%%%s%'", serialize(array('target' => $this->argument))); + $this->query->add_where(0, $this->table_alias . '.' . $this->real_field . " LIKE '%%%s%'", serialize(array('target' => $this->argument))); } } diff --git a/sites/all/modules/link/views/link_views_handler_filter_protocol.inc b/sites/all/modules/link/views/link_views_handler_filter_protocol.inc index f43e345c4b2e5790b33e6cfc83fb5a92ce83cc70..9d194aa94bbc8f9eebf67b8b345bc753ec9a4571 100644 --- a/sites/all/modules/link/views/link_views_handler_filter_protocol.inc +++ b/sites/all/modules/link/views/link_views_handler_filter_protocol.inc @@ -80,27 +80,28 @@ class link_views_handler_filter_protocol extends views_handler_filter_string { $where_conditions = array(); foreach ($protocols as $protocol) { // Simple case, the URL begins with the specified protocol. - $condition = $field .' LIKE \''. $protocol .'%\''; + $condition = $field . ' LIKE \'' . $protocol . '%\''; // More complex case, no protocol specified but is automatically cleaned up // by link_cleanup_url(). RegEx is required for this search operation. if ($protocol == 'http') { + $LINK_DOMAINS = _link_domains(); if ($db_type == 'pgsql') { // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue. // pgSQL requires all slashes to be double escaped in regular expressions. // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP - $condition .= ' OR '. $field .' ~* \''.'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + $condition .= ' OR ' . $field .' ~* \''.'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\''; } else { // mySQL requires backslashes to be double (triple?) escaped within character classes. // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp - $condition .= ' OR '. $field .' REGEXP \''.'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + $condition .= ' OR ' . $field . ' REGEXP \''.'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\''; } } $where_conditions[] = $condition; } - $this->query->add_where($this->options['group'], implode(' '. $this->operator .' ', $where_conditions)); + $this->query->add_where($this->options['group'], implode(' ' . $this->operator . ' ', $where_conditions)); } } diff --git a/sites/all/modules/metatag/CHANGELOG.txt b/sites/all/modules/metatag/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..aeba62f52d1bbcf5a469cfb8d308b3c48b29ad3f --- /dev/null +++ b/sites/all/modules/metatag/CHANGELOG.txt @@ -0,0 +1,180 @@ +Metatag 7.x-1.0-beta5, 2013-03-23 +--------------------------------- +#1844638 by DamienMcKenna: Updated help messages around update 7004, when ran + via Drush it will no longer used Batch API. +#1844764 by Devin Carlson, DamienMcKenna: Fix arg placeholders in t() calls. +#1846516 by Staratel: Incorrect arguments for watchdog(). +#1846516 by DamienMcKenna: Further incorrect arguments for watchdog(). +#1844638 by DamienMcKenna: Correctly used drupal_is_cli() instead of just + php_sapi_name(). +#1846978 by edulterado: Corrected the theme function name used with the + Twitter Cards submodule. +#1307804 by juampy: Support for Select_or_Other for use with the OpenGraph + 'type' field. +#1854522 by DamienMcKenna: Redundant return statements in the MetaTag classes. +#1852600 by DamienMcKenna: Only use the first page argument in the Views and + Panels preprocessors if it is numerical. +#1850014 by plopesc: Not all contexts that may be shown on the admin page will + have a path condition defined. +#1846080 by DamienMcKenna: Only support entities that have the 'metatags' + option specifically enabled. +#1857116 by DamienMcKenna: Purge {metatag} records for a few known unsupported + entities that old versions would have saved. +#1857116 by DamienMcKenna: Don't purge 'file' {metatag} records until #1857334 + is decided. +#1857360 by DamienMcKenna: Purge {metatag} records for nodes, taxonomy terms + and users that were purged but where the APIs of older versions failed to + remove them. +#1857116 by DamienMcKenna: Purge {metatag} records for Profile2. +#1852600 by helmo: Typo in Views integration function. +#1852022 by DamienMcKenna: Don't export the {metatag_config}.cid field. +#1862570 by DamienMcKenna: Purge any empty values that may have been added by + very early releases. +#1862570 by DamienMcKenna: Follow-up to correctly handle the serialized empty + array. +#1864340 by cdoyle, DamienMcKenna: Incorrect output for certain Twitter Card + tags. +#1865170 by DamienMcKenna: Fix metatag_requirements() return array when the + Page Title module is also installed. +#1722564 by DamienMcKenna: Provide a hook_requirements() message and README.txt + note about a possible conflict with the Exclude Node Title module. +#1284756 by damiankloip, sylus, alanburke, lancee: Migrate module integration. +#1865228 by greggles, DamienMcKenna: Added the rel=author link meta tag. +#1866122 by DamienMcKenna: Added the twitter:site:id and twitter:creator:id + meta tags. +#1866980 by makangus: Corrected metatag_features_revert(). +#1862818 by DYdave, DamienMcKenna: Added documentation for + hook_metatag_config_default(). +#1778534 by DamienMcKenna: Added the original-source meta tag. +#1886170 by DamienMcKenna: Typo in the API docs regarding enabling metatag + support in custom entities. +#1871020 by DamienMcKenna: Compatibility problem with Workbench_Moderation. +#1773926 by Dave Reid: Fixed token validation fails on config edit if the + instance context is not an entity type. +#1814736 by plach, Dave Reid: metatag_page_build() did not check if the + global:frontpage metatag configuration is disabled. +#1871852: Fixed metatag_update_7005() did not check if the watchdog table + exists. +#1891082 by bago, Dave Reid: Fixed metatag_config_instance_label() failed to + recurse properly. +#1915284: Fixed metatag_html_head_alter() stopped removing duplicate tags too + soon. Fixed duplicate canonical links when global redirect is enabled. +#1845326 by DamienMcKenna, Peacog: Resolved language handling problems to + correctly identify the langcode to properly work with or without + Entity_Translation. +#1876042 by DamienMcKenna: Rename variables to use $entity_id instead of $id + and $entity_type instead of $type. +#1859136 by splatio, DamienMcKenna, multpix: Feeds integration - allow meta tag + fields to be the target for data imported using the Feeds module. +#1880302 by olli, DamienMcKenna: Resolve problems with Features integration. +#1923030 by krlucas, DamienMcKenna: Only run metatag_entity_update() on + supported entities. +#1844638 by DamienMcKenna, mikeytown2: Remove unnecessary duplicate {metatag} + records, fix language values for all entities. +#1935084 by DamienMcKenna: Remove unnecessary items from metatag_hook_info() + that was causing problems with PHP 5.4. +#1791720 by kbasarab: Added the news_keywords meta tag. +#1934492 by juampy, DamienMcKenna: Added a page for reverting meta tags for + specific entity or bundle. +#1386320: Note a known issue of using custom template files that do not output + the $page['content'] variable. +#1917902 by DamienMcKenna: Ensure strings returned from token replacement of + text fields ([node:summary]) is passed through the appropriate text filters. +#1919070 by DamienMcKenna: Fix any records that may have been corrupted by e.g. + #1871020. +#1861656 by DamienMcKenna, torrance123: Optionally load the global meta tags on + all pages, enabled by default. +#1871798 by mstrelan: Clear the Context plugin cache when metatag_context is + enabled so that the new plugin becomes available. +#1932192 by DamienMcKenna: Only run metatag_entity_view() once per page view. +#1900434 by Dustin Currie, j0rd, DamienMcKenna: Added several new OpenGraph meta + tags, including ones for videos, location and contact information. +#1883118 by DamienMcKenna: Improve the help message on Metatag:Context's Path + field as neither relative nor absolute URLs will work. +#1945114 by SergO, DamienMcKenna: A query from #1919070 was missing the + preproccess wrapper around the table name. +#1908586 by DamienMcKenna: Added a line to README.txt explaining how to + customize the tokens used to generate the meta tags. +#1350610 by DamienMcKenna: metatag_update_7001 needed to drop the primary key + before customizing it. +#1859136 by DamienMcKenna: Fixed scenarios when updating an entity there are two + copies of the data submitted, e.g. Feeds integration. +#1308790 by DamienMcKenna: Documented that [current-user] tokens should not be + used. +#1318294 by DamienMcKenna: Documented how to use Imagecache Token to resize + images that are being used as tokens for meta tags. +#1871534 by DamienMcKenna: Documented how some browser plugins can make the page + title appear to be wrapped with doublequotes though the output doesn't + actually show them. + + +Metatag 7.x-1.0-beta4, 2012-11-17 +--------------------------------- +#1842764 by DamienMcKenna: Work around problems in metatag_entity_load() + stemming from an outdated database schema, leave a message suggesting the + site admin run the database updates. +#1842868 by DamienMcKenna: Changed metatag_update_7003 to automatically assign + the correct language per entity, added update_7004 to fix records updated in + beta3, fixed the language selection for loading meta tags so sites without + translation functionality continue to work correctly. +#1842868 by DamienMcKenna: Changed update 7003 again so it *only* adds the new + field, changed update 7004 so it will update all records using Batch API. +#1843676 by DamienMcKenna: Changed the hook_requirements message to an INFO + message if Page_Title is also installed, will freak people out less. + + +Metatag 7.x-1.0-beta3, 2012-11-16 +--------------------------------- +#1688286 by colan, DamienMcKenna: Support for Entity Translation. +#1835030 by DamienMcKenna: Documentation and hook_requirements note re Drupal + core v7.17. +#1840402 by DamienMcKenna, paperdhc: Corrected use of array_pop(). +#1841404 by mh86: Don't attempt to load meta tags for unsupported entities, and + don't support configuration-only entities. +#1841564 by peximo: Correctly identify the content language being used on the + homepage. +#1841774 by DamienMcKenna: Provide a warning via hook_requirements if the Page + Title module is also enabled, due to the possibilities of complications and + unexpected results. +#1363476 by DamienMcKenna: Workaround to trigger metatag_entity_view() if the + current CTools (Panels, Panelizer, etc) page is an entity display page. +#1842052 by DamienMcKenna: Don't process unsupported entities being displayed + via Views. +#1664322 by nico059, kerasai, miechiel, idflood, DamienMcKenna, alexweber: + Twitter Cards meta tags. +#1842198 by DamienMcKenna: Move the 'advanced' fieldset under the others. +#1840236 by weri, Marty2081: Only revert the requested feature, not all + features. + + +Metatag 7.x-1.0-beta2, 2012-10-30 +--------------------------------- +#1817580 by DamienMcKenna: Removed code that was enabling debug mode on all + Contexts. +#1818240 by DamienMcKenna: Added $instance value to the drupal_alter() call in + metatag_metatags_view(). +#1817984 by DamienMcKenna, alexweber: Documented + hook_metatag_metatags_view_alter(). +#1818252 by DamienMcKenna: There was no caching on the front page's meta tags. +#1818516 by DamienMcKenna: Incorrect variable check in metatag_page_build(). +#1818762 by DamienMcKenna: Updated hook_hook_info(). +#1466292 by DamienMcKenna: Listed hooks in metatag.api.php and everywhere the + hooks are triggered there's a comment to say what the hook is. +#1818984 by DamienMcKenna: Add the $instance value to metatag_context's + triggering of hook_metatag_metatags_view. +#1819000 by DamienMcKenna: Don't load default meta tags if no active contexts + define meta tags. +#1819448 by DamienMcKenna: Error on admin page if any meta tags were disabled. +#1818958 by DamienMcKenna: The $cid_parts array should contain all relevant + entity variables. +#1820362 by DamienMcKenna: $cid_parts should use base_path() instead of '/'. +#1820374 by DamienMcKenna: Front page $cid_parts did not include the full URL. +#1822726 by DamienMcKenna: Ensure the CTools exportables system is loaded. +#1818300 by eugene.ilyin, DamienMcKenna: Improved Features integration. +#1151936 by DamienMcKenna, maximpodorov: Workaround to trigger + metatag_entity_view() if the current Views page is an entity display page. + + +Metatag 7.x-1.0-beta1, 2012-10-19 +--------------------------------- +First mostly-stable release. diff --git a/sites/all/modules/metatag/README.txt b/sites/all/modules/metatag/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..5e2f5a76f6554db5d4f6ca0cf72ab22552c8d99a --- /dev/null +++ b/sites/all/modules/metatag/README.txt @@ -0,0 +1,162 @@ +Metatag +------- +This module allows you to automatically provide structured metadata, aka "meta +tags", about your website and web pages. + +In the context of search engine optimization, providing an extensive set of +meta tags may help improve your site's & pages' ranking, thus may aid with +achieving a more prominent display of your content within search engine +results. Additionally, using meta tags can help control the summary content +that is used within social networks when visitors link to your site, +particularly the Open Graph submodule for use with Facebook (see below). + +This version of the module only works with Drupal 7.15 and newer. + + +Features +------------------------------------------------------------------------------ +The primary features include: + +* The current supported basic meta tags are ABSTRACT, DESCRIPTION, CANONICAL, + COPYRIGHT, GENERATOR, IMAGE_SRC, KEYWORDS, PUBLISHER, ROBOTS, SHORTLINK and + the page's TITLE tag. + +* Multi-lingual support using the Entity Translation module. + +* Per-path control over meta tags using the "Meta tags: Context" submodule + (requires the Context module). + +* The fifteen Dublin Core Basic Element Set 1.1 meta tags may be added by + enabling the "Meta tags: Dublin Core" submodule. + +* The Open Graph Protocol meta tags, as used by Facebook, may be added by + enabling the "Meta tags: Open Graph" submodule. + +* The Twitter Cards meta tags may be added by enabling the "Meta tags: Twitter + Cards" submodule. + +* An API allowing for additional meta tags to be added, beyond what is provided + by this module - see metatag.api.php for full details. + + +Configuration +------------------------------------------------------------------------------ + 1. On the People Permissions administration page ("Administer >> People + >> Permissions") you need to assign: + + - The "Administer meta tags" permission to the roles that are allowed to + access the meta tags admin pages to control the site defaults. + + - The "Edit meta tags" permission to the roles that are allowed to change + meta tags on each individual page (node, term, etc). + + 2. The main admininistrative page controls the site-wide defaults, both global + settings and defaults per entity (node, term, etc), in addition to those + assigned specifically for the front page: + admin/config/search/metatags + + 3. Each supported entity object (nodes, terms, users) will have a set of meta + tag fields available for customization on their respective edit page, these + will inherit their values from the defaults assigned in #2 above. Any + values that are not overridden per object will automatically update should + the defaults be updated. + + 4. As the meta tags are output using Tokens, it may be necessary to customize + the token display for the site's entities (content types, vocabularies, + etc). To do this go to e.g. admin/structure/types/manage/article/display, in + the "Custom Display Settings" section ensure that "Tokens" is checked (save + the form if necessary), then to customize the tokens go to: + admin/structure/types/manage/article/display/token + + +Fine Tuning +------------------------------------------------------------------------------ +* By default Metatag will load the global default values for all pages that do + not have meta tags assigned via the normal entity display or via Metatag + Context. This may be disabled by setting the variable 'metatag_load_all_pages' + to FALSE through one of the following methods: + * Use Drush to set the value: + drush vset metatag_load_all_pages FALSE + * Hardcode the value in the site's settings.php file: + $conf['metatag_load_all_pages'] = FALSE; + To re-enable this option simply set the value to TRUE. + + +Developers +------------------------------------------------------------------------------ +Full API documentation is available in metatag.api.php. + +To enable Metatag support in custom entities, add 'metatag' => TRUE to either +the entity or bundle definition in hook_entity_info(); see metatag.api.php for +further details and example code. + + +Troubleshooting / Known Issues +------------------------------------------------------------------------------ +* When using custom page template files, e.g. page--front.tpl.php, it is + important to ensure that the following code is present in the template file: + <?php render($page['content']); ?> + or + <?php render($page['content']['metatags']); ?> + Without one of these being present the meta tags will not be displayed. +* Versions of Drupal older than v7.17 were missing necessary functionality for + taxonomy term pages to work correctly. +* Using Metatag with values assigned for the page title and the Page Title + module simultaneously can cause conflicts and unexpected results. +* Using the Exclude Node Title module will cause the [node:title] token to be + empty on node pages, so using [current-page:title] will work around the + issue. Note: it isn't possible to "fix" this as it's a by-product of what + Exclude Node Title does - it removes the node title from display. +* When customizing the meta tags for user pages, it is strongly recommended to + not use the [current-user] tokens, these pertain to the person *viewing* the + page and not e.g. the person who authored a page. +* If images being displayed in image tags need to be resized to fit a specific + requirements, use the Imagecache Token module to customize the value. +* Certain browser plugins, e.g. on Chrome, can cause the page title so be + displayed with additional doublequotes, e.g. instead of: + <title>The page title | My cool site</title> + it will show: + <title>"The page title | My cool site"</title> + The solution is to remove the browser plugin - the page's actual output is not + affected, it is just a problem in the browser. + + +Related modules +------------------------------------------------------------------------------ +Some modules are available that extend Metatag with additional functionality: + +* Domain Meta Tags + http://drupal.org/project/domain_meta + Integrates with the Domain Access module, so each site of a multi-domain + install can separately control their meta tags. + +* Select or Other + http://drupal.org/project/select_or_other + Enhances the user experience of the metatag_opengraph submodule by allowing + the creation of custom Open Graph types. + +* Imagecache Token + http://drupal.org/project/imagecache_token + Provide tokens to load fields using an image style preset, for when meta tags + need to fix exact requirements. + + +Credits / Contact +------------------------------------------------------------------------------ +Currently maintained by Dave Reid [1] and Damien McKenna [2]. + +All initial development was sponsored by Acquia [3] and Palantir [4]; +continued development sponsored by Palantir and Mediacurrent [5]. + +The best way to contact the authors is to submit an issue, be it a support +request, a feature request or a bug report, in the project issue queue: + http://drupal.org/project/issues/metatag + + +References +------------------------------------------------------------------------------ +1: http://drupal.org/user/53892 +2: http://drupal.org/user/108450 +3: http://www.acquia.com/ +4: http://www.palantir.net/ +5: http://www.mediacurrent.com/ diff --git a/sites/all/modules/metatag/metatag.admin.inc b/sites/all/modules/metatag/metatag.admin.inc index d7873807b3ff465631179c80fbda711524c0104b..0b4278a4772bcd170f9acef4826dc14142848436 100644 --- a/sites/all/modules/metatag/metatag.admin.inc +++ b/sites/all/modules/metatag/metatag.admin.inc @@ -89,6 +89,10 @@ function metatag_config_overview() { // Add a summary of the configuration's defaults. $summary = array(); foreach ($config->config as $metatag => $data) { + // Skip meta tags that were disabled. + if (empty($metatags[$metatag])) { + continue; + } $summary[] = array( check_plain($metatags[$metatag]['label']) . ':', check_plain(metatag_get_value($metatag, $data, array('raw' => TRUE))), @@ -173,7 +177,6 @@ function metatag_config_overview() { ), '#rows' => $rows, '#empty' => t('No meta tag defaults available yet.'), - '#caption' => '<div class="js-show">' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '</div>', '#attributes' => array( 'class' => array('metatag-config-overview'), ), @@ -270,7 +273,14 @@ function metatag_config_edit_form($form, &$form_state, $config) { $contexts = explode(':', $config->instance); $options['context'] = $contexts[0]; if ($contexts[0] != 'global') { - $options['token types'] = array(token_get_entity_mapping('entity', $contexts[0])); + // The context part of the instance may not map to an entity type, so allow + // the token_get_entity_mapping() function to fallback to the provided type. + if ($token_type = token_get_entity_mapping('entity', $contexts[0], TRUE)) { + $options['token types'] = array($token_type); + } + else { + $options['token types'] = array($contexts[0]); + } } // Ensure that this configuration is properly compared to its parent 'default' @@ -382,3 +392,141 @@ function metatag_config_export_form($config) { ctools_include('export'); return drupal_get_form('ctools_export_form', ctools_export_crud_export('metatag_config', $config), t('Export')); } + +/** + * Form constructor to revert nodes to their default metatags. + * + * @see metatag_bulk_revert_form_submit() + * @ingroup forms + */ +function metatag_bulk_revert_form() { + // Get the list of entity:bundle options + $options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + foreach (array_keys($entity_info['bundles']) as $bundle) { + if (metatag_entity_supports_metatags($entity_type, $bundle)) { + $options[$entity_type . ':' . $bundle] = + $entity_info['label'] . ': ' . $entity_info['bundles'][$bundle]['label']; + } + } + } + + $form['update'] = array( + '#type' => 'checkboxes', + '#required' => TRUE, + '#title' => t('Select the entities to revert'), + '#options' => $options, + '#default_value' => array(), + '#description' => t('All meta tags will be removed for all content of the selected entities.'), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Revert'), + ); + + return $form; +} + +/** + * Form submit handler for metatag reset bulk revert form. + * + * @see metatag_batch_revert_form() + * @see metatag_bulk_revert_batch_finished() + */ +function metatag_bulk_revert_form_submit($form, &$form_state) { + $batch = array( + 'title' => t('Bulk updating metatags'), + 'operations' => array(), + 'finished' => 'metatag_bulk_revert_batch_finished', + 'file' => drupal_get_path('module', 'metatag') . '/metatag.admin.inc', + ); + + // Set a batch operation per entity:bundle. + foreach (array_filter($form_state['values']['update']) as $option) { + list($entity_type, $bundle) = explode(':', $option); + $batch['operations'][] = array('metatag_bulk_revert_batch_operation', array($entity_type, $bundle)); + } + + batch_set($batch); +} + +/** + * Batch callback: delete custom metatags for selected bundles. + */ +function metatag_bulk_revert_batch_operation($entity_type, $bundle, &$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + // Query the selected entity table. + $entity_info = entity_get_info($entity_type); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type) + ->propertyCondition($entity_info['entity keys']['id'], $context['sandbox']['current'], '>') + ->propertyOrderBy($entity_info['entity keys']['id']); + if ($entity_type != 'user') { + /** + * Entities which do not define a bundle such as User fail returning no results. + * @see http://drupal.org/node/1054168#comment-5266208 + */ + $query->entityCondition('bundle', $bundle); + } + // Get the total amount of entities to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->count()->execute(); + $query->count = FALSE; + + // If there are no bundles to revert, stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + // Process 25 entities per iteration. + $query->range(0, 25); + $result = $query->execute(); + $ids = !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array(); + foreach ($ids as $id) { + $metatags = metatag_metatags_load($entity_type, $id); + if (!empty($metatags)) { + db_delete('metatag')->condition('entity_type', $entity_type) + ->condition('entity_id', $id) + ->execute(); + metatag_metatags_cache_clear($entity_type, $id); + $context['results'][] = t('Reverted metatags for @bundle with id @id.', array( + '@bundle' => $entity_type . ': ' . $bundle, + '@id' => $id, + )); + } + } + + $context['sandbox']['count'] += count($ids); + $context['sandbox']['current'] = max($ids); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Batch finished callback. + */ +function metatag_bulk_revert_batch_finished($success, $results, $operations) { + if ($success) { + if (!count($results)) { + drupal_set_message(t('No metatags were reverted.')); + } + else { + $message = theme('item_list', array('items' => $results)); + drupal_set_message($message); + } + } + else { + $error_operation = reset($operations); + drupal_set_message(t('An error occurred while processing @operation with arguments : @args', + array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE)))); + } +} diff --git a/sites/all/modules/metatag/metatag.admin.js b/sites/all/modules/metatag/metatag.admin.js index f278226f686bdce97874cec2d972ca200885b988..c213f721d0587385fa1a7cc3203ee05554ca46d5 100644 --- a/sites/all/modules/metatag/metatag.admin.js +++ b/sites/all/modules/metatag/metatag.admin.js @@ -5,8 +5,16 @@ Drupal.behaviors.metatagUIConfigListing = { // Hide elements to be visible if JavaScript is enabled. $('.js-show').show(); + // Make the leaf arrow clickable. + $('.metatag-config-label').hover(function(){ + $(this).css({'cursor':'pointer'}); + }) + .click(function(){ + $(this).find('a.toggle-details', context).trigger('click'); + }); + // Show or hide the summary - $('table.metatag-config-overview a.toggle-details', context).click(function() { + $('table.metatag-config-overview a.toggle-details', context).click(function(event) { $(this).parent('div').siblings('div.metatag-config-details').each(function() { if ($(this).hasClass('js-hide')) { $(this).slideDown('slow').removeClass('js-hide'); @@ -23,6 +31,10 @@ Drupal.behaviors.metatagUIConfigListing = { else { $(this).parent('div').removeClass('expanded').addClass('collapsed'); } + + // This event may be triggered by a parent element click - so we don't + // want the click to bubble up otherwise we get recursive click events. + event.stopPropagation(); }); } } diff --git a/sites/all/modules/metatag/metatag.api.php b/sites/all/modules/metatag/metatag.api.php index b1245a0355d86abd6b95987b8e96c16fe8400f33..b2d97ddf8df0e1300cd1297015b3e303b9d18cab 100644 --- a/sites/all/modules/metatag/metatag.api.php +++ b/sites/all/modules/metatag/metatag.api.php @@ -3,3 +3,217 @@ * @file * API documentation for the Metatag module. */ + +/** + * To enable Metatag support in custom entities, add 'metatags' => TRUE to the + * entity definition in hook_entity_info(), e.g.: + * + * /** + * * Implements hook_entity_info(). + * * + * * Taken from the Examples module. + * * / + * function entity_example_entity_info() { + * $info['entity_example_basic'] = array( + * 'label' => t('Example Basic Entity'), + * 'controller class' => 'EntityExampleBasicController', + * 'base table' => 'entity_example_basic', + * 'uri callback' => 'entity_example_basic_uri', + * 'fieldable' => TRUE, + * 'metatags' => TRUE, + * 'entity keys' => array( + * 'id' => 'basic_id' , // The 'id' (basic_id here) is the unique id. + * 'bundle' => 'bundle_type' // Bundle will be determined by the 'bundle_type' field + * ), + * 'bundle keys' => array( + * 'bundle' => 'bundle_type', + * ), + * 'static cache' => TRUE, + * 'bundles' => array( + * 'first_example_bundle' => array( + * 'label' => 'First example bundle', + * 'admin' => array( + * 'path' => 'admin/structure/entity_example_basic/manage', + * 'access arguments' => array('administer entity_example_basic entities'), + * ), + * ), + * ), + * 'view modes' => array( + * 'tweaky' => array( + * 'label' => t('Tweaky'), + * 'custom settings' => FALSE, + * ), + * ) + * ); + * + * return $info; + * } + * + * The definition of each bundle may be handled separately, thus support may be + * disabled for the entity as a whole but enabled for individual bundles. This + * is handled via the 'metatags' value on the bundle definition, e.g.: + * + * 'bundles' => array( + * 'first_example_bundle' => array( + * 'label' => 'First example bundle', + * 'metatags' => TRUE, + * 'admin' => array( + * 'path' => 'admin/structure/entity_example_basic/manage', + * 'access arguments' => array('administer entity_example_basic entities'), + * ), + * ), + * ), + */ + +/** + * Provides a default configuration for Metatag intances. + * + * This hook allows modules to provide their own Metatag instances which can + * either be used as-is or as a "starter" for users to build from. + * + * This hook should be placed in MODULENAME.metatag.inc and it will be auto- + * loaded. MODULENAME.metatag.inc *must* be in the same directory as the + * .module file which *must* also contain an implementation of + * hook_ctools_plugin_api, preferably with the same code as found in + * metatag_ctools_plugin_api(). + * + * The $config->disabled boolean attribute indicates whether the Metatag + * instance should be enabled (FALSE) or disabled (TRUE) by default. + * + * @return + * An associative array containing the structures of Metatag instances, as + * generated from the Export tab, keyed by the Metatag config name. + * + * @see metatag_metatag_config_default() + * @see metatag_ctools_plugin_api() + */ +function hook_metatag_config_default() { + $configs = array(); + + $config = new stdClass(); + $config->instance = 'config1'; + $config->api_version = 1; + $config->disabled = FALSE; + $config->config = array( + 'title' => array('value' => '[current-page:title] | [site:name]'), + 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), + ); + $configs[$config->instance] = $config; + + $config = new stdClass(); + $config->instance = 'config2'; + $config->api_version = 1; + $config->disabled = FALSE; + $config->config = array( + 'title' => array('value' => '[user:name] | [site:name]'), + ); + $configs[$config->instance] = $config; + + return $configs; +} + +/** + * + */ +function hook_metatag_config_default_alter(&$config) { +} + +/** + * + */ +function hook_metatag_config_delete($entity_type, $entity_ids) { +} + +/** + * + */ +function hook_metatag_config_insert($config) { +} + +/** + * + */ +function hook_metatag_config_instance_info() { + return array(); +} + +/** + * + */ +function hook_metatag_config_instance_info_alter(&$info) { +} + +/** + * + */ +function hook_metatag_config_load() { +} + +/** + * + */ +function hook_metatag_config_load_presave() { +} + +/** + * + */ +function hook_metatag_config_presave($config) { +} + +/** + * + */ +function hook_metatag_config_update($config) { +} + +/** + * + */ +function hook_metatag_info() { + return array(); +} + +/** + * + */ +function hook_metatag_info_alter(&$info) { +} + +/** + * + */ +function hook_metatag_load_entity_from_path_alter(&$path, $result) { +} + +/** + * Alter metatags before being cached. + * + * This hook is invoked prior to the meta tags for a given page are cached. + * + * @param array $output + * All of the meta tags to be output for this page in their raw format. This + * is a heavily nested array. + * @param string $instance + * An identifier for the current page's page type, typically a combination + * of the entity name and bundle name, e.g. "node:story". + */ +function hook_metatag_metatags_view_alter(&$output, $instance) { + if (isset($output['description']['#attached']['drupal_add_html_head'][0][0]['#value'])) { + $output['description']['#attached']['drupal_add_html_head'][0][0]['#value'] = 'O rly?'; + } +} + +/** + * + */ +function hook_metatag_page_cache_cid_parts_alter(&$cid_parts) { +} + +/** + * + */ +function hook_metatag_presave(&$metatags, $entity_type, $entity_id) { +} diff --git a/sites/all/modules/metatag/metatag.features.inc b/sites/all/modules/metatag/metatag.features.inc new file mode 100644 index 0000000000000000000000000000000000000000..5f8ab5440c3410294d91ba43cdb29159a7d2b07e --- /dev/null +++ b/sites/all/modules/metatag/metatag.features.inc @@ -0,0 +1,87 @@ +<?php +/** + * @file + * Features integration for the Metatag module. + */ + +/** + * Implements hook_features_export(). + */ +function metatag_features_export($data, &$export, $module_name = '', $type = 'metatag') { + $pipe = array(); + + foreach ($data as $name) { + if (metatag_config_load($name)) { + $export['features'][$type][$name] = $name; + } + } + + $export['dependencies']['metatag'] = 'metatag'; + + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function metatag_features_export_render($module_name, $data, $export = NULL) { + $code = array(); + $code[] = ' $config = array();'; + $code[] = ''; + + foreach ($data as $key => $name) { + if (is_object($name)) { + $name = $name->instance; + } + if ($config = metatag_config_load($name)) { + $export = new stdClass(); + $export->instance = $config->instance; + $export->config = $config->config; + $export = features_var_export($export, ' '); + $key = features_var_export($name); + $code[] = " // Exported Metatag config instance: {$name}."; + $code[] = " \$config[{$key}] = {$export};"; + $code[] = ""; + } + } + $code[] = ' return $config;'; + $code = implode("\n", $code); + return array('metatag_export_default' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function metatag_features_revert($module) { + if ($feature_conf = features_get_default('metatag', $module)) { + foreach (array_keys($feature_conf) as $config) { + if ($conf = metatag_config_load($config)) { + db_delete('metatag_config')->condition('instance', $config)->execute(); + } + unset($feature_conf[$config]['cid']); + $object = new stdClass(); + $object->cid = NULL; + $object->instance = $config; + $object->config = $feature_conf[$config]['config']; + metatag_config_save($object); + } + } +} + +/** + * Implements hook_features_export_options(). + */ +function metatag_features_export_options() { + $instances = metatag_config_instance_info(); + foreach ($instances as $key => $instance) { + $options[$key] = $key; + }; + return $options; +} + +/** + * Implements hook_features_rebuild(). + */ +function metatag_features_rebuild($module) { + metatag_features_revert($module); +} diff --git a/sites/all/modules/metatag/metatag.feeds.inc b/sites/all/modules/metatag/metatag.feeds.inc new file mode 100644 index 0000000000000000000000000000000000000000..3ee0acfa010fe5b7a7fa0d795c1dc1b78b16bc72 --- /dev/null +++ b/sites/all/modules/metatag/metatag.feeds.inc @@ -0,0 +1,37 @@ +<?php +/** + * @file + * Feeds mapping implementation for the Metatag module. + */ + +/** + * Implements hook_feeds_processor_targets_alter(). + */ +function metatag_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { + if (metatag_entity_supports_metatags($entity_type)) { + $info = metatag_get_info(); + foreach ($info['tags'] as $name => $tag) { + $targets['meta_' . $name] = array( + 'name' => 'Meta tag: ' . check_plain($tag['label']), + 'callback' => 'metatag_feeds_set_target', + 'description' => $tag['description'], + ); + } + } +} + +/** + * Callback function to set value of a metatag tag. + */ +function metatag_feeds_set_target($source, $entity, $target, $value) { + // Don't do anything if we weren't given any data. + if (empty($value)) { + return; + } + + // Strip the prefix that was added above. + $name = str_replace('meta_', '', $target); + + // Assign the value. + $entity->metatags[$name]['value'] = $value; +} diff --git a/sites/all/modules/metatag/metatag.inc b/sites/all/modules/metatag/metatag.inc index f079d2971e999cb78a3efc71a0a99daccf712970..d2f9aac94799508a2dfd5d1f1f128dc363f9d139 100644 --- a/sites/all/modules/metatag/metatag.inc +++ b/sites/all/modules/metatag/metatag.inc @@ -65,7 +65,6 @@ class DrupalDefaultMetaTag implements DrupalMetaTagInterface { return array( '#attached' => array('drupal_add_html_head' => array(array($element, $element['#id']))), ); - return $element; } } @@ -88,7 +87,7 @@ class DrupalTextMetaTag extends DrupalDefaultMetaTag { '#default_value' => isset($this->data['value']) ? $this->data['value'] : '', '#element_validate' => array('token_element_validate'), '#token_types' => $options['token types'], - '#maxlength' => 255, + '#maxlength' => 1024, ); return $form; @@ -98,7 +97,7 @@ class DrupalTextMetaTag extends DrupalDefaultMetaTag { $options += array( 'token data' => array(), 'clear' => TRUE, - 'sanitize' => FALSE, + 'sanitize' => TRUE, 'raw' => FALSE, ); @@ -142,7 +141,6 @@ class DrupalLinkMetaTag extends DrupalTextMetaTag { return array( '#attached' => array('drupal_add_html_head' => array(array($element, $element['#id']))), ); - return $element; } } diff --git a/sites/all/modules/metatag/metatag.info b/sites/all/modules/metatag/metatag.info index 0addb2e115d62b86a279c9850e6af4659a6a788d..4a61acd75db34ac577d8726a5c34d289eb67928f 100644 --- a/sites/all/modules/metatag/metatag.info +++ b/sites/all/modules/metatag/metatag.info @@ -2,16 +2,23 @@ name = Meta tags description = "Adds support and an API to implement meta tags." package = Meta tags core = 7.x -dependencies[] = token + +# This requires Drupal 7.15 or newer. +dependencies[] = system (>=7.15) + +# CTools and Token are also required. dependencies[] = ctools +dependencies[] = token + configure = admin/config/search/metatags files[] = metatag.inc +files[] = metatag.migrate.inc files[] = metatag.test -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag.install b/sites/all/modules/metatag/metatag.install index 47518f91205c34086fc9f3b5437dfa2740c35762..f1ee370dffb12affacce441e767938fbb3b5da69 100644 --- a/sites/all/modules/metatag/metatag.install +++ b/sites/all/modules/metatag/metatag.install @@ -32,6 +32,7 @@ function metatag_schema() { 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The primary identifier for a metatag configuration set.', + 'no export' => TRUE, ), 'instance' => array( 'type' => 'varchar', @@ -78,8 +79,15 @@ function metatag_schema() { 'not null' => TRUE, 'serialize' => TRUE, ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language of the tag.', + ), ), - 'primary key' => array('entity_type', 'entity_id'), + 'primary key' => array('entity_type', 'entity_id', 'language'), ); $schema['cache_metatag'] = drupal_get_schema_unprocessed('system', 'cache'); @@ -88,6 +96,103 @@ function metatag_schema() { return $schema; } +/** + * Implements hook_requirements(). + */ +function metatag_requirements($phase) { + $requirements = array(); + // Ensure translations don't break during installation. + $t = get_t(); + + if ($phase == 'runtime') { + // Work out the release of D7 that is currently running. + list($major, $minor) = explode('.', VERSION); + // Strip off any suffixes on the version string, e.g. "17-dev". + if (strpos('-', $minor)) { + list($minor, $suffix) = explode('-', $minor); + } + + // Releases of Drupal older than 7.15 did not have entity_language(), which + // is now required. + if ($minor < 15) { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_WARNING, + 'title' => 'Metatag', + 'value' => $t('Upgrade Drupal core to v7.15 or newer'), + 'description' => $t("This older version of Drupal core is missing functionality necessary for the module's multilingual support, it must be upgraded to at least version 7.15."), + ); + } + // Releases of Drupal older than 7.17 did not trigger hook_entity_view on + // term pages, so recommend updating. + elseif ($minor < 17) { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_WARNING, + 'title' => 'Metatag', + 'value' => $t('Upgrade Drupal core to v7.17 or newer'), + 'description' => $t('Your older version of Drupal core is missing functionality necessary for taxonomy term pages to work correctly, it is strongly recommended to upgrade to the latest release.'), + ); + } + // Everything's OK. + else { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_OK, + 'title' => 'Metatag', + 'value' => $t('Drupal core is compatible'), + 'description' => $t('Older versions of Drupal core were missing functionality necessary for taxonomy term pages to work correctly, but this version <em>will</em> work correctly.'), + ); + } + + // Add a note if Page Title is also installed. + if (module_exists('page_title')) { + $requirements['metatag_page_title'] = array( + 'severity' => REQUIREMENT_INFO, + 'title' => 'Metatag', + 'value' => $t('Possible conflicts with Page Title module'), + 'description' => $t('The Metatag module is able to customize page titles so running the Page Title module simultaneously can lead to complications.'), + ); + } + + // Add a note if Page Title is also installed. + if (module_exists('exclude_node_title')) { + $requirements['metatag_exclude_node_title'] = array( + 'severity' => REQUIREMENT_INFO, + 'title' => 'Metatag', + 'value' => $t('Possible conflicts with Exclude Node Title module'), + 'description' => $t('The Metatag module\'s default setitngs for content types (nodes) uses [node:title] for the page title. Unfortunately, Exclude Node Title hides this so the page title ends up blank. It is recommended to <a href="!config">change the "title" field\'s default value</a> to "[current-page:title]" instead of "[node:title]" for any content types affected by Exclude Node Title.', array('!config' => 'admin/config/search/metatags')), + ); + } + } + + return $requirements; +} + +/** + * Implements hook_install(). + */ +// function metatag_install() { +// } + +/** + * Implements hook_uninstall(). + */ +function metatag_uninstall() { + // This variable is created via hook_enable. + variable_del('metatag_schema_installed'); +} + +/** + * Implements hook_enable(). + */ +function metatag_enable() { + variable_set('metatag_schema_installed', TRUE); +} + +/** + * Implements hook_disable(). + */ +// function metatag_disable() { +// } + /** * Disable the deprecated metatag_ui module which has been merged into metatag. */ @@ -99,14 +204,528 @@ function metatag_update_7000() { } /** - * Fix the {metatag_config}.cid column cannot be NULL. + * Fix the "{metatag_config}.cid column cannot be NULL" error. */ function metatag_update_7001() { - $field = array( + $table_name = 'metatag_config'; + $field_name = 'cid'; + $field_spec = array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The primary identifier for a metatag configuration set.', ); - db_change_field('metatag_config', 'cid', 'cid', $field); + $keys = array('primary key' => array($field_name)); + + // Before making any changes, drop the existing primary key. + db_drop_primary_key($table_name); + + // Rejig the field, and turn on the primary key again. + db_change_field($table_name, $field_name, $field_name, $field_spec, $keys); +} + +/** + * Disable the deprecated metatag_ui module which has been merged into metatag, + * again. + */ +function metatag_update_7002() { + if (module_exists('metatag_ui')) { + module_disable(array('metatag_ui'), FALSE); + drupal_uninstall_modules(array('metatag_ui'), FALSE); + drupal_set_message(t('The deprecated Metatag UI module has been disabled.')); + } +} + +/** + * Add the {metatag}.language field. + */ +function metatag_update_7003() { + // Set the target table and field name. + $table_name = 'metatag'; + $field_name = 'language'; + + // Don't add the new field if it already exists. + if (!db_field_exists($table_name, $field_name)) { + // Describe the new field. + $field_spec = array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language of the tag', + ); + + // Add it and update the primary key. + db_add_field($table_name, $field_name, $field_spec); + db_drop_primary_key($table_name); + db_add_primary_key($table_name, array('entity_type', 'entity_id', 'language')); + } +} + +/** + * Replaced by updates 7009, 7010, 7011, 7012 and 7013. + */ +function metatag_update_7004() { + // Do nothing. +} + +/** + * Removing wrong metatag watchdog entries that break the admin/reports/dblog + * page. + */ +function metatag_update_7005() { + if (db_table_exists('watchdog')) { + db_delete('watchdog') + ->condition('type', 'metatag') + ->condition('variables', serialize('info')) + ->execute(); + } +} + +/** + * Remove {metatag} records that were added by old versions of the module for + * entities that don't actually support meta tags. A more complete version of + * this will be added later on after it's (hopefully) guaranteed that all + * modules have updated to the correct API usage. + */ +function metatag_update_7006() { + $entity_types = array( + // Core. + 'comment', + 'menu_link', + 'taxonomy_vocabulary', + // Some contrib entities. + 'mailchimp_list', + 'profile2', + 'profile2_type', + 'redirect', + 'rules_config', + 'wysiwyg_profile', + ); + foreach ($entity_types as $entity_type) { + $num_deleted = db_delete('metatag') + ->condition('entity_type', $entity_type) + ->execute(); + if ($num_deleted > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for the @type entity type, it does not support meta tags.', array('@count' => $num_deleted, '@type' => $entity_type))); + } + } +} + +/** + * Remove {metatag} records for objects that have been deleted; older versions + * of Metatag may have failed to purge these. + */ +function metatag_update_7007() { + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {node} n + ON m.entity_id=n.nid + WHERE m.entity_type='node' + AND n.nid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for nodes that had been purged.', array('@count' => $result->rowCount()))); + } + + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {users} u + ON m.entity_id=u.uid + WHERE m.entity_type='user' + AND u.uid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for users that had been purged.', array('@count' => $result->rowCount()))); + } + + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {taxonomy_term_data} t + ON m.entity_id=t.tid + WHERE m.entity_type='taxonomy_term' + AND t.tid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for taxonomy terms that had been purged.', array('@count' => $result->rowCount()))); + } +} + +/** + * Remove any empty records that may be hanging around from old releases. + */ +function metatag_update_7008() { + $result = db_query("DELETE m FROM {metatag} m WHERE m.data IS NULL or m.data = '' OR m.data = :empty", array(':empty' => serialize(array()))); + if ($result->rowCount() > 0) { + drupal_set_message(t('Purged @count empty meta tag record(s).', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for taxonomy terms. + */ +function metatag_update_7009() { + // Remove duplicates. + _metatag_remove_dupes('taxonomy_term'); + + // The taxonomy term entity doesn't support a 'language' option, so reset it + // to LANGUAGE_NONE. + $result = db_query("UPDATE {metatag} SET language = :language WHERE entity_type='taxonomy_term'", array(':language' => LANGUAGE_NONE)); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count taxonomy terms.', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for users. + */ +function metatag_update_7010() { + // Remove duplicates. + _metatag_remove_dupes('user'); + + // Update User values. + $result = db_query("UPDATE {metatag} SET language = :language WHERE entity_type='user'", array(':language' => LANGUAGE_NONE)); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count user records.', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for nodes. + */ +function metatag_update_7011() { + // Only proceed if Entity_Translation is not enabled as it allows each node + // record to have multiple languages available. + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, so node meta tags will not be updated, to avoid accidental dataloss.")); + return; + } + + // Remove duplicates. + _metatag_remove_dupes('node'); + + // Update Node values. + $result = db_query("UPDATE {metatag} AS m INNER JOIN {node} n ON m.entity_id=n.nid AND m.entity_type='node' SET m.language = n.language"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count nodes.', array('@count' => $result->rowCount()))); + } +} + +/** + * Remove duplicate {metatag} records for non-core entities. + */ +function metatag_update_7012() { + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, duplicate meta tags will not be removed for custom entities, to avoid accidental dataloss.")); + return; + } + + $records = db_select('metatag', 'm') + ->fields('m', array('entity_type')) + ->condition('m.entity_type', array('node', 'taxonomy_term', 'user'), 'NOT IN') + ->orderBy('m.entity_type', 'ASC') + ->orderBy('m.entity_id', 'ASC') + ->distinct() + ->execute(); + + $entity_types = array(); + foreach ($records as $record) { + $entity_types[] = $record->entity_type; + // Remove duplicates. + _metatag_remove_dupes($record->entity_type); + } + + if (empty($entity_types)) { + drupal_set_message(t('There were no other records to fix.')); + } +} + +/** + * Fix the {metatag} language value for all non-core entity records. This might + * take a while, depending on how much data needs to be converted. + */ +function metatag_update_7013(&$sandbox) { + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, meta tags will not be updated for custom entities, to avoid accidental dataloss.")); + return; + } + + // Use the sandbox at your convenience to store the information needed + // to track progression between successive calls to the function. + if (!isset($sandbox['progress'])) { + // The count of records visited so far. + $sandbox['progress'] = 0; + + // Because the {metatag} table uses multiple primary keys, there's no easy + // way to do this, so we're going to cache all record keys and manually + // step through them. + $records = db_select('metatag', 'm') + ->fields('m', array('entity_type', 'entity_id')) + ->condition('m.entity_type', array('node', 'taxonomy_term', 'user'), 'NOT IN') + ->orderBy('m.entity_type', 'ASC') + ->orderBy('m.entity_id', 'ASC') + ->execute(); + $sandbox['records'] = array(); + foreach ($records as $record) { + $sandbox['records'][] = $record; + } + + // If there's no data, don't bother with the extra work. + if (empty($sandbox['records'])) { + watchdog('metatag', 'Update 7013: No meta tag records need updating.', array(), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: No meta tag records need updating.')); + } + return t('No meta tag records need updating.'); + } + + // Total records that must be visited. + $sandbox['max'] = count($sandbox['records']); + + // A place to store messages during the run. + $sandbox['messages'] = array(); + + // An initial record of the number of records to be updated. + watchdog('metatag', 'Update 7013: !count records to update.', array('!count' => $sandbox['max']), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: !count records to update.', array('!count' => $sandbox['max']))); + } + + // Last record processed. + $sandbox['current_record'] = -1; + } + + // Process records by groups of 10 (arbitrary value). + // When a group is processed, the batch update engine determines whether it + // should continue processing in the same request or provide progress + // feedback to the user and wait for the next request. + $limit = 10; + + // The for loop will run as normal when ran via update.php, but when ran via + // Drush it'll just run 'til it's finished. + $increment = 1; + if (drupal_is_cli()) { + $increment = 0; + } + + // Set default values. + for ($ctr = 0; $ctr < $limit; $ctr += $increment) { + $sandbox['current_record']++; + if (empty($sandbox['records'][$sandbox['current_record']])) { + break; + } + + // Shortcuts for later. + $entity_type = $sandbox['records'][$sandbox['current_record']]->entity_type; + $entity_id = $sandbox['records'][$sandbox['current_record']]->entity_id; + + // Load the entity. + $entities = entity_load($entity_type, array($entity_id)); + if (!empty($entities)) { + $entity = array_pop($entities); + + // Make sure that the entity has a language set. + if (!empty($entity)) { + // If there's a (non-empty) language value, use it. + $new_language = entity_language($entity_type, $entity); + if (empty($new_language)) { + $new_language = LANGUAGE_NONE; + } + // Update the 'language' value. + db_update('metatag') + ->fields(array('language' => $new_language)) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + } + } + + // Update our progress information. + $sandbox['progress']++; + } + + // Set the "finished" status, to tell batch engine whether this function + // needs to run again. If you set a float, this will indicate the progress of + // the batch so the progress bar will update. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + // Clear all caches so the fixed data will be reloaded. + cache_clear_all('*', 'cache_metatag', TRUE); + + // A final log of the number of records that were converted. + watchdog('metatag', 'Update 7013: !count records were updated in total.', array('!count' => $sandbox['progress']), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: !count records were updated.', array('!count' => $sandbox['progress']))); + } + + // hook_update_N() may optionally return a string which will be displayed + // to the user. + return t('!count records were updated in total.', array('!count' => $sandbox['progress'])); + } +} + +/** + * Remove duplicate records for a given entity. + * + * It should be OK to run this without doing a separate batch process as there + * shouldn't be many records that have this problem. Hopefully. + * + * @param $entity_type + * The name of an entity type to check for. + */ +function _metatag_remove_dupes($entity_type) { + $purge_count = 0; + + // First step: fix the records. There should not be multiple records for the + // same entity_id with different languages. + $dupe_records = db_query("SELECT m.entity_id, count(m.language) AS the_count + FROM {metatag} m + WHERE + m.entity_type = :type + GROUP BY m.entity_id + HAVING the_count > 1", array(':type' => $entity_type)); + + if (!empty($dupe_records)) { + foreach ($dupe_records as $record) { + $entity_id = $record->entity_id; + $langs = db_query("SELECT m.entity_id, m.language, m.data FROM {metatag} m WHERE m.entity_type = :type AND m.entity_id = :id", array(':type' => $entity_type, ':id' => $entity_id))->fetchAll(); + + // Work out which language record to remove. Will need to store this as + // an array incase there are multiple records to purge. + $langs_to_remove = array(); + + // Check for duplicate records. + // Outer loop starts from the beginning. + for ($outer = 0; $outer < count($langs); $outer++) { + // This record may have been removed already. + if (isset($langs[$outer])) { + // Inner loop starts from the end. + for ($inner = count($langs) - 1; $inner > 0; $inner--) { + // Work out if the outer loop's data is the same as the inner + // loop's. + if (isset($langs[$inner]) && $langs[$outer]->data == $langs[$inner]->data) { + // Remove the second record. + $langs_to_remove[] = $langs[$inner]->language; + unset($langs[$inner]); + } + } + } + } + + // Only one record left. + if (count($langs) == 1) { + // This is how it should be, this record is fine. + } + // More than one record, work out which one to keep. + elseif (count($langs) > 1) { + // Work out the entity's language. + $entity = entity_load($entity_type, $entity_id); + $entity_language = entity_language($entity_type, $entity); + if (empty($language)) { + $entity_language = LANGUAGE_NONE; + } + + // Work out if the entity's language record exists. + $lang_pos = NULL; + foreach ($langs as $key => $record) { + if ($record->language == $entity_language) { + $lang_pos = $key; + break; + } + } + // If the language record exists, delete the others. + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != $entity_language) { + $langs_to_remove[] = $record->language; + } + } + } + // Otherwise look for a record for the site's default language. + else { + foreach ($langs as $key => $record) { + if ($record->language == $GLOBALS['language']->language) { + $lang_pos = $key; + break; + } + } + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != $GLOBALS['language']->language) { + $langs_to_remove[] = $record->language; + } + } + } + // Finally check for LANGUAGE_NONE. + else { + foreach ($langs as $key => $record) { + if ($record->language == LANGUAGE_NONE) { + $lang_pos = $key; + break; + } + } + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != LANGUAGE_NONE) { + $langs_to_remove[] = $record->language; + } + } + } + } + } + } + + // Purge the redundant records. + if (!empty($langs_to_remove)) { + $purge_count += db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $langs_to_remove) + ->execute(); + } + } + } + + if (empty($purge_count)) { + drupal_set_message(t('No duplicate :entity_type records were found (this is a good thing).', array(':entity_type' => $entity_type))); + watchdog('metatag', 'No duplicate :entity_type records were found (this is a good thing).', array(':entity_type' => $entity_type)); + } + else { + drupal_set_message(t('Purged :count duplicate :entity_type record(s).', array(':count' => $purge_count, ':entity_type' => $entity_type))); + watchdog('metatag', 'Purged :count duplicate :entity_type record(s).', array(':count' => $purge_count, ':entity_type' => $entity_type)); + return; + } +} + +/** + * Fix {metatag} records that may have been corrupted by #1871020. + */ +function metatag_update_7014() { + $records = db_query("SELECT * + FROM {metatag} m + WHERE + m.`data` LIKE :nolang + OR m.`data` LIKE :lang + OR m.`data` LIKE :und", + array( + ':nolang' => 'a:1:{s:0:"";a:%:{s:%;a:%:{%;}}}', + ':lang' => 'a:1:{s:2:"__";a:%:{s:%;a:%:{%;}}}', + ':und' => 'a:1:{s:3:"___";a:%:{s:%;a:%:{%;}}}', + )); + + // Nothing to fix. + if ($records->rowCount() == 0) { + drupal_set_message(t('No corrupt records to fix, this is good news :-)')); + } + + // Fix the faulty records. + else { + foreach ($records as $record) { + // Extract the data and get the first element of the array, this should be + // valid data. + $record->data = reset(unserialize($record->data)); + + // Update the record. + drupal_write_record('metatag', $record, array('entity_type', 'entity_id', 'language')); + } + drupal_set_message(t('Fixed @count corrupt meta tag record(s).', array('@count' => $records->rowCount()))); + } } diff --git a/sites/all/modules/metatag/metatag.metatag.inc b/sites/all/modules/metatag/metatag.metatag.inc index 51f0c99e5521d7ff4e32b388411b7fdc33ac18ea..bd892556305708268244a9df8a487924a3743555 100644 --- a/sites/all/modules/metatag/metatag.metatag.inc +++ b/sites/all/modules/metatag/metatag.metatag.inc @@ -13,6 +13,8 @@ function metatag_metatag_config_default() { $config->config = array( 'title' => array('value' => '[current-page:title] | [site:name]'), 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), ); $configs[$config->instance] = $config; @@ -23,6 +25,7 @@ function metatag_metatag_config_default() { $config->config = array( 'title' => array('value' => variable_get('site_slogan') ? '[site:name] | [site:slogan]' : '[site:name]'), 'canonical' => array('value' => '[site:url]'), + 'shortlink' => array('value' => '[site:url]'), ); $configs[$config->instance] = $config; @@ -97,13 +100,30 @@ function metatag_metatag_info() { $info['groups']['advanced'] = array( 'label' => t('Advanced'), 'form' => array( - '#weight' => 90, + '#weight' => 100, ), ); + $info['tags']['title'] = array( + 'label' => t('Page title'), + 'description' => t("The text to display in the title bar of a visitor's web browser when they view this page. This meta tag may also be used as the title of the page when a visitor bookmarks or favorites this page."), + 'class' => 'DrupalTitleMetaTag', + ); + $info['tags']['description'] = array( 'label' => t('Description'), - 'description' => t("A brief and concise summary of the page's content, preferrably 150 characters or less. The description meta tag may be used by search engines to display a snippet about the page in search results."), + 'description' => t("A brief and concise summary of the page's content, preferably 150 characters or less. The description meta tag may be used by search engines to display a snippet about the page in search results."), + 'class' => 'DrupalTextMetaTag', + 'form' => array( + '#type' => 'textarea', + '#rows' => 2, + '#wysiwyg' => FALSE, + ), + ); + + $info['tags']['abstract'] = array( + 'label' => t('Abstract'), + 'description' => t("A brief and concise summary of the page's content, preferably 150 characters or less. The abstract meta tag may be used by search engines for archiving purposes."), 'class' => 'DrupalTextMetaTag', 'form' => array( '#type' => 'textarea', @@ -111,16 +131,12 @@ function metatag_metatag_info() { '#wysiwyg' => FALSE, ), ); + $info['tags']['keywords'] = array( 'label' => t('Keywords'), - 'description' => t("A comma-separated list of keywords about the page. This meta tag is <em>not</em> supported by most search engines."), + 'description' => t("A comma-separated list of keywords about the page. This meta tag is <em>not</em> used by most search engines."), 'class' => 'DrupalTextMetaTag', ); - $info['tags']['title'] = array( - 'label' => t('Title'), - 'description' => t("The text to display in the title bar of a visitor's web browser when they view this page. This meta tag may also be used as the title of the page when a visitor bookmarks or favorites this page."), - 'class' => 'DrupalTitleMetaTag', - ); // More advanced meta tags. $info['tags']['robots'] = array( @@ -129,15 +145,26 @@ function metatag_metatag_info() { 'class' => 'DrupalListMetaTag', 'form' => array( '#options' => array( + 'index' => t('Allow search engines to index this page (assumed).'), + 'follow' => t('Allow search engines to follow links on this page (assumed).'), 'noindex' => t('Prevent search engines from indexing this page.'), 'nofollow' => t('Prevent search engines from following links on this page.'), 'noarchive' => t('Prevent a cached copy of this page from being available in the search results.'), 'nosnippet' => t('Prevents a description from appearing below the page in the search results, as well as prevents caching of the page.'), 'noodp' => t('Blocks the <a href="@odp-url">Open Directory Project</a> description of the page from being used in the description that appears below the page in the search results.', array('@odp-url' => 'http://www.dmoz.org/')), + 'noydir' => t('Prevents Yahoo! from listing this page in the <a href="@ydir">Yahoo! Directory</a>.', array('@ydir' => 'http://dir.yahoo.com/')), ), ), 'group' => 'advanced', ); + + $info['tags']['news_keywords'] = array( + 'label' => t('Google News Keywords'), + 'description' => t('A comma-separated list of keywords about the page. This meta tag is used as an indicator in <a href="@google_news">Google News</a>.', array('@google_news' => 'http://support.google.com/news/publisher/bin/answer.py?hl=en&answer=68297')), + 'class' => 'DrupalTextMetaTag', + 'group' => 'advanced', + ); + $info['tags']['generator'] = array( 'label' => t('Generator'), 'description' => t("Describes the name and version number of the software or publishing tool used to create the page."), @@ -146,6 +173,7 @@ function metatag_metatag_info() { 'context' => array('global'), 'group' => 'advanced', ); + $info['tags']['copyright'] = array( 'label' => t('Copyright'), 'description' => t("Details a copyright, trademark, patent, or other information that pertains to intellectual property about this page. Note that this will not automatically protect your site's content or your intellectual property."), @@ -153,19 +181,27 @@ function metatag_metatag_info() { 'group' => 'advanced', ); - // Link tags. + $info['tags']['image_src'] = array( + 'label' => t('Image'), + 'description' => t("An image associated with this page, for use as a thumbnail in social networks and other services."), + 'class' => 'DrupalLinkMetaTag', + 'group' => 'advanced', + ); + $info['tags']['canonical'] = array( 'label' => t('Canonical URL'), 'description' => t("Tells search engines where the preferred location or URL is for this page to help eliminate self-created duplicate content for search engines."), 'class' => 'DrupalLinkMetaTag', 'group' => 'advanced', ); + $info['tags']['shortlink'] = array( 'label' => t('Shortlink URL'), 'description' => '', 'class' => 'DrupalLinkMetaTag', 'group' => 'advanced', ); + $info['tags']['publisher'] = array( 'label' => t('Publisher URL'), 'description' => '', @@ -173,5 +209,20 @@ function metatag_metatag_info() { 'group' => 'advanced', ); + $info['tags']['author'] = array( + 'label' => t('Author URL'), + 'description' => "Used by some search engines to confirm authorship of the content on a page. Should be either the full URL for the author's Google+ profile page or a local page with information about the author.", + 'class' => 'DrupalLinkMetaTag', + 'group' => 'advanced', + ); + + $info['tags']['original-source'] = array( + 'label' => t('Original Source'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'advanced', + 'description' => "Used to indicate the URL that broke the story, and can link to either an internal URL or an external source. If the full URL is not known it is acceptable to use a partial URL or just the domain name.", + ); + return $info; } diff --git a/sites/all/modules/metatag/metatag.migrate.inc b/sites/all/modules/metatag/metatag.migrate.inc new file mode 100644 index 0000000000000000000000000000000000000000..b189ef001f48f4803be31ef0200bca5d269aebf0 --- /dev/null +++ b/sites/all/modules/metatag/metatag.migrate.inc @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Metatag support for migrate. + */ + +/** + * Implements hook_migrate_api(). + */ +function metatag_migrate_api() { + return array('api' => 2); +} + +/** + * Metatags destination handler. + */ +class MigrateMetatagHandler extends MigrateDestinationHandler { + + public function __construct() { + $this->registerTypes(array('node', 'user', 'taxonomy_term')); + } + + /** + * Implements MigrateDestinationHandler::fields(). + */ + public function fields() { + $fields = array(); + $elements = metatag_get_info(); + + foreach ($elements['tags'] as $value) { + $metatag_field = 'metatag_' . $value['name']; + $fields[$metatag_field] = $value['description']; + } + + return $fields; + } + + /** + * Implements MigrateDestinationHandler::prepare(). + */ + public function prepare($entity, stdClass $row) { + $elements = metatag_get_info(); + + foreach ($elements['tags'] as $value) { + $metatag_field = 'metatag_' . $value['name']; + if (isset($entity->$metatag_field)) { + $entity->metatags[$value['name']]['value'] = $entity->$metatag_field; + unset($entity->$metatag_field); + } + } + } +} diff --git a/sites/all/modules/metatag/metatag.module b/sites/all/modules/metatag/metatag.module index 9899f5e4e96c08864f818981d2b99839cdc65d18..6f0a4933e2e78b9eac94cfd6318b91b6899d4a0b 100644 --- a/sites/all/modules/metatag/metatag.module +++ b/sites/all/modules/metatag/metatag.module @@ -2,9 +2,24 @@ /** * @todo Add revisionable support for metatag data. - * @todo Add multilingual support for metatag data - is this even needed? */ +/** + * Implements hook_help(). + */ +function metatag_help($path, $arg) { + if ($path == 'admin/config/search/metatags') { + return '<p>' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '</p>'; + } + elseif ($path == 'admin/help#metatag') { + return '<p>' . t('The Meta tags module provides a options to let each page have customized meta data added to the "meta" tags in the HEAD section of the document.') . '</p>'; + } + elseif ($path == 'admin/config/search/metatags/bulk-revert') { + return '<p>' . t('This form <strong>will wipe out</strong> all custom meta tags for the selected entities, reverting them to the default configuration assigned at the <a href="@url">Defaults tab</a>. For example, if the meta tags are changed for an article they will be removed if the "Node: Article" checkbox is selected.', array('@url' => url('admin/config/search/metatags'))) . '</p>'; + } + +} + /** * Implements hook_theme(). */ @@ -43,38 +58,34 @@ function metatag_ctools_plugin_api($owner, $api) { */ function metatag_hook_info() { $hooks = array( - 'metatag_info', - 'metatag_info_alter', + 'metatag_config_default', + 'metatag_config_default_alter', + 'metatag_config_delete', + 'metatag_config_insert', 'metatag_config_instance_info', 'metatag_config_instance_info_alter', 'metatag_config_load', - 'metatag_config_presave', - 'metatag_config_insert', + 'metatag_config_load_presave', 'metatag_config_update', - 'metatag_config_delete', - 'metatag_load', - 'metatag_insert', - 'metatag_update', - 'metatag_delete', - 'metatag_alter', - 'metatag_config_default', - 'metatag_config_default_alter', - 'metatag_api', + 'metatag_info', + 'metatag_info_alter', ); return array_fill_keys($hooks, array('group' => 'metatag')); } /** - * Implements hook_permisson(). + * Implements hook_permission(). */ function metatag_permission() { $permissions['administer meta tags'] = array( 'title' => t('Administer meta tags.'), 'restrict access' => TRUE, + 'description' => t('Control the main settings pages and modify per-object meta tags.'), ); $permissions['edit meta tags'] = array( - 'title' => t('Edit meta tags.'), + 'title' => t('Edit meta tags'), + 'description' => t('Modify meta tags on individual entity records (nodes, terms, users, etc).'), ); return $permissions; } @@ -158,6 +169,15 @@ function metatag_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => 10, ); + $items['admin/config/search/metatags/bulk-revert'] = array( + 'title' => 'Bulk revert', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_bulk_revert_form'), + 'access arguments' => array('administer meta tags'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 30, + 'file' => 'metatag.admin.inc', + ); return $items; } @@ -179,18 +199,18 @@ function metatag_flush_caches() { * The levels of defaults is arranged by splitting the $instance variable by * the colon character, and always using a 'global' instance at the end. */ -function metatag_config_load_with_defaults($instance) { +function metatag_config_load_with_defaults($instance, $include_global = TRUE) { $defaults = &drupal_static(__FUNCTION__, array()); - // Check to see if - if (!isset($defaults[$instance])) { - $cid = "config:{$instance}"; + // Statically cache defaults since they can include multiple levels. + $cid = "config:{$instance}" . ($include_global ? ':withglobal' : ':withoutglobal'); + if (!isset($defaults[$cid])) { if ($cache = cache_get($cid, 'cache_metatag')) { - $defaults[$instance] = $cache->data; + $defaults[$cid] = $cache->data; } else { - $defaults[$instance] = array(); - $instances = metatag_config_get_parent_instances($instance); + $defaults[$cid] = array(); + $instances = metatag_config_get_parent_instances($instance, $include_global); $configs = metatag_config_load_multiple($instances); foreach ($instances as $key) { // Ignore disabled configurations. @@ -199,14 +219,14 @@ function metatag_config_load_with_defaults($instance) { } // Add config to the defaults array. - $defaults[$instance] += $configs[$key]->config; + $defaults[$cid] += $configs[$key]->config; } - cache_set($cid, $defaults[$instance], 'cache_metatag'); + cache_set($cid, $defaults[$cid], 'cache_metatag'); } } - return $defaults[$instance]; + return $defaults[$cid]; } /** @@ -231,15 +251,22 @@ function metatag_config_load_multiple(array $instances) { function metatag_config_save($config) { $config->is_new = empty($config->cid); - // Allow modules to alter the configuration before it is saved. + // Allow modules to alter the configuration before it is saved using + // hook_metatag_config_presave(). module_invoke_all('metatag_config_presave', $config); if ($config->is_new) { drupal_write_record('metatag_config', $config); + + // Allow modules to act upon the record insertion using + // hook_metatag_config_insert(). module_invoke_all('metatag_config_insert', $config); } else { drupal_write_record('metatag_config', $config, array('cid')); + + // Allow modules to act upon the record update using + // hook_metatag_config_insert(). module_invoke_all('metatag_config_update', $config); } @@ -267,62 +294,134 @@ function metatag_config_delete($instance) { function metatag_config_cache_clear() { cache_clear_all('*', 'cache_metatag', TRUE); drupal_static_reset('metatag_config_load_with_defaults'); + drupal_static_reset('metatag_entity_has_metatags'); + drupal_static_reset('metatag_entity_supports_metatags'); ctools_include('export'); ctools_export_load_object_reset('metatag_config'); } -function metatag_metatags_load($type, $id) { - $metatags = metatag_metatags_load_multiple($type, array($id)); +/** + * Load an entity's tags. + * + * @param $entity_type + * The entity type to load + * @param $entity_id + * The ID of the entity to load + * @return + * An array of tag data keyed by language. + */ +function metatag_metatags_load($entity_type, $entity_id) { + $metatags = metatag_metatags_load_multiple($entity_type, array($entity_id)); return !empty($metatags) ? reset($metatags) : array(); } -function metatag_metatags_load_multiple($type, array $ids) { +/** + * Load tags for multiple entities. + * + * @param $entity_type + * The entity type to load + * @param $entity_ids + * The list of entity IDs + * @return + * An array of tag data, keyed by ID. + */ +function metatag_metatags_load_multiple($entity_type, array $entity_ids) { // Double check entity IDs are numeric thanks to Entity API module. - $ids = array_filter($ids, 'is_numeric'); - if (empty($ids)) { + $entity_ids = array_filter($entity_ids, 'is_numeric'); + if (empty($entity_ids)) { return array(); } // Also need to check if the metatag table exists since this condition could // fire before the table has been installed yet. - if (!db_table_exists('metatag')) { - return array(); + if (!variable_get('metatag_schema_installed', FALSE)) { + if (db_table_exists('metatag')) { + variable_set('metatag_schema_installed', TRUE); + } + else { + watchdog('metatag', 'The system tried to load metatag data before the schema was fully loaded.', array(), WATCHDOG_WARNING); + return array(); + } + } + + // Get all translations of tag data for this entity. + $result = db_query("SELECT entity_id, data, language FROM {metatag} WHERE (entity_type = :type) AND (entity_id IN (:ids))", array( + ':type' => $entity_type, + ':ids' => $entity_ids, + )); + + // Marshal it into an array keyed by entity ID. Each value is an array of + // translations keyed by language code. + $metatags = array(); + while ($record = $result->fetchObject()) { + $metatags[$record->entity_id][$record->language] = unserialize($record->data); } - $metatags = db_query("SELECT entity_id, data FROM {metatag} WHERE entity_type = :type AND entity_id IN (:ids)", array( - ':type' => $type, - ':ids' => $ids, - ))->fetchAllKeyed(); - $metatags = array_map('unserialize', $metatags); return $metatags; } -function metatag_metatags_save($type, $id, $metatags) { - // Check that $id is numeric because of Entity API and string IDs. - if (!is_numeric($id)) { +/** + * Save an entity's tags. + * + * @param $entity_type + * The entity type to load + * @param $entity_id + * The entity's ID + * @param $metatags + * All of the tag information + * @param $language + * The language of the translation set + */ +function metatag_metatags_save($entity_type, $entity_id, $metatags, $language) { + // If no language assigned, use the has-no-language language. + if (!$language) { + $language = LANGUAGE_NONE; + } + + // Check that $entity_id is numeric because of Entity API and string IDs. + if (!is_numeric($entity_id)) { return; } - // Allow other modules to alter the metatags prior to saving. + // Certain modules, e.g. Workbench Moderation, will cause the data to be in + // an unsupported format; the problem needs to be resolved elsewhere so this + // can only be considered a temporary fix. + // TODO: Solve the core problem, which will probably entail something + // similar to http://drupal.org/node/1876034. + if (isset($metatags[$language])) { + // There are certain occasions when the old data and the new data are + // *both* added to the $metatags array, in this case throw away the language + // data. + $lang_data = $metatags[$language]; + unset($metatags[$language]); + if (empty($metatags)) { + $metatags = $lang_data; + } + } + + // Allow other modules to alter the meta tags prior to saving using + // hook_metatag_presave(). foreach (module_implements('metatag_presave') as $module) { $function = "{$module}_metatag_presave"; - $function($metatags, $type, $id); + $function($metatags, $entity_type, $entity_id, $language); } if (empty($metatags)) { // If the data array is empty, there is no data to actually save, so // just delete the record from the database. db_delete('metatag') - ->condition('entity_type', $type) - ->condition('entity_id', $id) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $language) ->execute(); } else { // Otherwise save the data for this entity. db_merge('metatag') ->key(array( - 'entity_type' => $type, - 'entity_id' => $id, + 'entity_type' => $entity_type, + 'entity_id' => $entity_id, + 'language' => $language, )) ->fields(array( 'data' => serialize($metatags), @@ -331,30 +430,61 @@ function metatag_metatags_save($type, $id, $metatags) { } // Clear cached data. - metatag_metatags_cache_clear($type, $id); + metatag_metatags_cache_clear($entity_type, $entity_id); } -function metatag_metatags_delete($type, $id) { - return metatag_metatags_delete_multiple($type, array($id)); +/** + * Delete an entity's tags. + * + * @param $entity_type + * The entity type + * @param $entity_id + * The entity's ID + * @param $langcode + * The language ID of the entry to delete. If left blank, all language + * entries for this entity will be deleted. + */ +function metatag_metatags_delete($entity_type, $entity_id, $langcode = NULL) { + return metatag_metatags_delete_multiple($entity_type, array($entity_id), $langcode); } -function metatag_metatags_delete_multiple($type, array $ids) { +/** + * Delete multiple entities' tags. + * + * @param $entity_type + * The entity type + * @param $entity_ids + * The list of IDs + * @param $langcode + * The language ID of the entities to delete. If left blank, all language + * entries for the enities will be deleted. + */ +function metatag_metatags_delete_multiple($entity_type, array $entity_ids, $langcode = NULL) { // Double check entity IDs are numeric thanks to Entity API module. - $ids = array_filter(array_keys($ids, 'is_numeric')); + $entity_ids = array_filter($entity_ids, 'is_numeric'); - if ($metatags = metatag_metatags_load_multiple($type, $ids)) { + if ($metatags = metatag_metatags_load_multiple($entity_type, $entity_ids)) { $transaction = db_transaction(); try { - // Let other modules know about the metatags being deleted. - module_invoke_all('metatag_metatags_delete', $type, $ids); + // Let other modules know about the records being deleted using + // hook_metatag_metatags_delete(). + module_invoke_all('metatag_metatags_delete', $entity_type, $entity_ids, $langcode); + + // Set the entity to delete. + $query = db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_ids, 'IN'); + + // Specify a language if there is one. + if ($langcode) { + $query->condition('language', $langcode); + } - db_delete('metatag') - ->condition('entity_type', $type) - ->condition('entity_id', $ids, 'IN') - ->execute(); + // Perform the deletion(s). + $query->execute(); // Clear cached data. - metatag_metatags_cache_clear($type, $ids); + metatag_metatags_cache_clear($entity_type, $entity_ids); } catch (Exception $e) { $transaction->rollback(); @@ -364,14 +494,14 @@ function metatag_metatags_delete_multiple($type, array $ids) { } } -function metatag_metatags_cache_clear($type, $id = NULL) { - if (empty($id)) { - cache_clear_all("output:$type", 'cache_metatag', TRUE); +function metatag_metatags_cache_clear($entity_type, $entity_id = NULL) { + if (empty($entity_id)) { + cache_clear_all("output:$entity_type", 'cache_metatag', TRUE); } else { - $ids = (array) $id; - foreach ($ids as $id) { - cache_clear_all("output:$type:$id", 'cache_metatag', TRUE); + $entity_ids = (array) $entity_id; + foreach ($entity_ids as $entity_id) { + cache_clear_all("output:$entity_type:$entity_id", 'cache_metatag', TRUE); } } } @@ -379,10 +509,23 @@ function metatag_metatags_cache_clear($type, $id = NULL) { /** * Implements hook_entity_load(). */ -function metatag_entity_load($entities, $type) { - $metatags = metatag_metatags_load_multiple($type, array_keys($entities)); - foreach ($entities as $id => $entity) { - $entities[$id]->metatags = isset($metatags[$id]) ? $metatags[$id] : array(); +function metatag_entity_load($entities, $entity_type) { + // Wrap this in a try-catch block to work around occasions when the schema + // hasn't been updated yet. + try { + if (metatag_entity_supports_metatags($entity_type)) { + $metatags = metatag_metatags_load_multiple($entity_type, array_keys($entities)); + foreach ($entities as $entity_id => $entity) { + $entities[$entity_id]->metatags = isset($metatags[$entity_id]) ? $metatags[$entity_id] : array(); + } + } + } + catch (Exception $e) { + watchdog('metatag', 'Error loading meta tag data, do the <a href="@update">database updates</a> need to be run? The error occurred when loading record(s) %ids for the %type entity type. The error message was: %error', array('@update' => base_path() . 'update.php', '%ids' => implode(', ', array_keys($entities)), '%type' => $entity_type, '%error' => $e->getMessage()), WATCHDOG_CRITICAL); + // Don't display the same message twice for Drush. + if (php_sapi_name() != 'cli') { + drupal_set_message(t('Error loading meta tag data, do the <a href="@update">database updates</a> need to be run?', array('@update' => base_path() . 'update.php')), 'error'); + } } } @@ -391,8 +534,12 @@ function metatag_entity_load($entities, $type) { */ function metatag_entity_insert($entity, $entity_type) { if (isset($entity->metatags)) { - list($id) = entity_extract_ids($entity_type, $entity); - metatag_metatags_save($entity_type, $id, $entity->metatags); + list($entity_id) = entity_extract_ids($entity_type, $entity); + + // Determine the entity's language. + $language = metatag_entity_get_language($entity_type, $entity); + + metatag_metatags_save($entity_type, $entity_id, $entity->metatags, $language); } } @@ -400,14 +547,38 @@ function metatag_entity_insert($entity, $entity_type) { * Implements hook_entity_update(). */ function metatag_entity_update($entity, $entity_type) { - list($id) = entity_extract_ids($entity_type, $entity); + if (!metatag_entity_supports_metatags($entity_type)) { + return; + } + + list($entity_id) = entity_extract_ids($entity_type, $entity); if (isset($entity->metatags)) { - metatag_metatags_save($entity_type, $id, $entity->metatags); + // Determine the entity's language. + $new_language = metatag_entity_get_language($entity_type, $entity); + + // Determine the language for this entity object. + if (isset($entity->original)) { + $old_language = metatag_entity_get_language($entity_type, $entity->original); + + // If the language has changed then remove the old one. When a new + // translation is being saved using Entity Translation both values will + // be the same, so this is safe to do. + if ($old_language != $new_language) { + db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $old_language) + ->execute(); + } + } + + // Save the record. + metatag_metatags_save($entity_type, $entity_id, $entity->metatags, $new_language); } else { // Still ensure the meta tag output is cached. - metatag_metatags_cache_clear($entity_type, $id); + metatag_metatags_cache_clear($entity_type, $entity_id); } } @@ -415,8 +586,8 @@ function metatag_entity_update($entity, $entity_type) { * Implements hook_entity_delete(). */ function metatag_entity_delete($entity, $entity_type) { - list($id) = entity_extract_ids($entity_type, $entity); - metatag_metatags_delete($entity_type, $id); + list($entity_id) = entity_extract_ids($entity_type, $entity); + metatag_metatags_delete($entity_type, $entity_id); } /** @@ -427,52 +598,117 @@ function metatag_field_attach_delete_revision($entity_type, $entity) { } /** - * Implements hook_field_attach_view_alter(). + * Implements hook_taxonomy_term_view_alter(). */ -function metatag_field_attach_view_alter(&$output, $context) { - $entity_type = $context['entity_type']; - $entity = $context['entity']; - list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); +function metatag_taxonomy_term_view_alter(&$build, &$entity_type) { + // This is only needed if hook_entity_view has not been added to core. + // @see http://drupal.org/node/1067120 + if (isset($build['#term']) && !function_exists('taxonomy_term_view_multiple')) { + $entity = taxonomy_term_load($build['#term']->tid); + metatag_entity_view($entity, $entity_type, 'full', NULL); + } +} + +/** + * Implements hook_entity_view(). + */ +function metatag_entity_view($entity, $entity_type, $view_mode, $langcode) { + // Only run this function once per page load. + static $i_will_say_this_only_once = FALSE; + + // Only proceed if this entity object is the page being viewed. + if (_metatag_entity_is_page($entity_type, $entity)) { + // Only run this function once per page load. + if ($i_will_say_this_only_once) { + return; + } + $i_will_say_this_only_once = TRUE; + + // If this entity object isn't allowed meta tags, skip it. + if (!metatag_entity_has_metatags($entity_type, $entity)) { + return; + } + + // Obbtain some details of the entity that are needed elsewhere. + list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); + $instance = "{$entity_type}:{$bundle}"; - if (metatag_entity_supports_metatags($entity_type, $bundle) && $context['view_mode'] == 'full' && _metatag_entity_is_page($entity_type, $entity)) { + // Determine the language this entity actually uses. + $entity_language = metatag_entity_get_language($entity_type, $entity); + + // The requested language is different to the entity's language, look for + // a language elsewhere. + if ($entity_language != $langcode) { + // If no language was defined for the entity then use that for the + if ($entity_language == LANGUAGE_NONE) { + $langcode = LANGUAGE_NONE; + } + else { + $enabled_languages = field_content_languages(); + foreach (field_language($entity_type, $entity) as $field => $lang) { + // Only accept actual language values that are properly enabled. + if ($lang != LANGUAGE_NONE && in_array($lang, $enabled_languages)) { + $langcode = $lang; + } + } + } + } + + // All applicable pieces for this current page. $cid_parts = array( - //'view_mode' => $context['view_mode'], - 'langcode' => $context['language'], - 'url' => $GLOBALS['base_url'] . '/' . current_path(), + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'entity_id' => $entity_id, + 'view_mode' => $view_mode, + 'langcode' => $langcode, + 'url' => $GLOBALS['base_url'] . base_path() . current_path(), ); - $cid = "output:{$entity_type}:{$entity_id}:" . hash('sha256', serialize($cid_parts)); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to alter the page parts using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$entity_type}:{$entity_id}:{$langcode}:" . hash('sha256', serialize($cid_parts)); if ($cache = cache_get($cid, 'cache_metatag')) { - $output['metatags'] = $cache->data; + $output = $cache->data; } else { + // Separate the meta tags. $metatags = isset($entity->metatags) ? $entity->metatags : array(); - $instance = "{$entity_type}:{$bundle}"; - // Build options for meta tag rendering. The context variable already - // contains entity type, entity, view mode, language, etc. - $options = $context; + // Build options for meta tag rendering. + $options = array( + 'entity' => $entity, + 'entity_type' => $entity_type, + 'view_mode' => $view_mode, + ); // Ensure we actually pass a language object rather than language code. $languages = language_list(); - if (isset($context['language']) && isset($languages[$context['language']])) { - $options['language'] = $languages[$context['language']]; + if (isset($languages[$langcode])) { + $options['language'] = $languages[$langcode]; } - // Reload the entity object from cache as it may have been altered by Panels. + // Reload the entity object from cache as it may have been altered. $token_type = token_get_entity_mapping('entity', $entity_type); $entities = entity_load($entity_type, array($entity_id)); $options['token data'][$token_type] = $entities[$entity_id]; $options['entity'] = $entities[$entity_id]; // Render the metatags and save to the cache. - $output['metatags'] = metatag_metatags_view($instance, $metatags, $options); - cache_set($cid, $output['metatags'], 'cache_metatag'); + $output = metatag_metatags_view($instance, $metatags, $options); + cache_set($cid, $output, 'cache_metatag'); } - // We have to add a '#field_type' property otherwise - // rdf_field_attach_view_alter() freaks out. - $output['metatags']['#field_type'] = NULL; + // We need to register the term's metatags, so we can later fetch them. + // @see metatag_page_build(). + metatag_page_set_metatags($instance, $output); } } @@ -480,9 +716,10 @@ function metatag_field_attach_view_alter(&$output, $context) { * Build a renderable array of meta tag output. * * @param string $instance - * The configuration instance key of the metatags to use, e.g. "node:article". + * The configuration instance key of the meta tags to use, e.g. + * "node:article". * @param array $metatags - * An arary of metatag data. + * An array of meta tag data. * @param array $options * (optional) An array of options including the following keys and values: * - language: A language object. @@ -491,7 +728,6 @@ function metatag_field_attach_view_alter(&$output, $context) { */ function metatag_metatags_view($instance, array $metatags = array(), array $options = array()) { $output = array(); - $metatags += metatag_config_load_with_defaults($instance); // Convert language codes to a language object. if (isset($options['language']) && is_string($options['language'])) { @@ -499,30 +735,58 @@ function metatag_metatags_view($instance, array $metatags = array(), array $opti $options['language'] = isset($languages[$options['language']]) ? $languages[$options['language']] : NULL; } + // If there are any tags, determine the translation to display. + if (!empty($metatags)) { + // Get the display language; default to the entity's language. + if (isset($options['language']) && isset($options['language']->language) && isset($metatags[$options['language']->language])) { + $metatags = $metatags[$options['language']->language]; + } + // If no language requested, use the no-language value. + elseif (!empty($metatags[LANGUAGE_NONE])) { + $metatags = $metatags[LANGUAGE_NONE]; + } + else { + $metatags = array(); + } + } + + // Add any default tags to the mix. + $metatags += metatag_config_load_with_defaults($instance); + foreach ($metatags as $metatag => $data) { if ($metatag_instance = metatag_get_instance($metatag, $data)) { $output[$metatag] = $metatag_instance->getElement($options); } } - drupal_alter('metatag_metatags_view', $output); + // Allow the output meta tags to be modified using + // hook_metatag_metatags_view_alter(). + drupal_alter('metatag_metatags_view', $output, $instance); return $output; } function metatag_metatags_values($instance, array $metatags = array(), array $options = array()) { $values = array(); - $metatags += metatag_config_load_with_defaults($instance); - // Convert language codes to a language object. - if (isset($options['language']) && is_string($options['language'])) { - $languages = language_list(); - $options['language'] = isset($languages[$options['language']]) ? $languages[$options['language']] : NULL; + // Apply defaults to the data for each language. + foreach ($metatags as $language => $metatag) { + $metatags[$language] += metatag_config_load_with_defaults($instance); } - foreach ($metatags as $metatag => $data) { - if ($metatag_instance = metatag_get_instance($metatag, $data)) { - $values[$metatag] = $metatag_instance->getValue($options); + // Generate output only if we have a valid language. + $language = $options['language']; + if (isset($language) && is_string($language) && isset($metatags[$language])) { + + // Convert language codes to a language object. + $languages = language_list(); + $options['language'] = isset($languages[$language]) ? $languages[$language] : NULL; + + // Get output elements. + foreach ($metatags[$language] as $metatag => $data) { + if ($metatag_instance = metatag_get_instance($metatag, $data)) { + $values[$metatag] = $metatag_instance->getValue($options); + } } } @@ -537,7 +801,7 @@ function metatag_metatags_values($instance, array $metatags = array(), array $op * @param string $instance * The configuration instance key of the metatags to use, e.g. "node:article". * @param array $metatags - * An arary of metatag data. + * An array of metatag data. * @param array $options * (optional) An array of options including the following keys and values: * - token types: An array of token types to be passed to theme_token_tree(). @@ -557,11 +821,12 @@ function metatag_metatags_form(array &$form, $instance, array $metatags = array( $form['metatags'] = array( '#type' => 'fieldset', '#title' => t('Meta tags'), + '#multilingual' => TRUE, '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, '#access' => user_access('edit meta tags') || user_access('administer meta tags'), - '#weight' => 10, + '#weight' => 40, '#attributes' => array( 'class' => array('metatags-form'), ), @@ -628,7 +893,8 @@ function metatag_metatags_form(array &$form, $instance, array $metatags = array( $form['metatags']['tokens'] = array( '#theme' => 'token_tree', '#token_types' => $options['token types'], - '#weight' => 100, + '#weight' => 999, + '#dialog' => TRUE, ); // Add a submit handler to compare the submitted values against the deafult @@ -657,7 +923,7 @@ function metatag_field_extra_fields() { $extra[$entity_type][$bundle]['form']['metatags'] = array( 'label' => t('Meta tags'), 'description' => t('Meta tag module form elements.'), - 'weight' => 10, + 'weight' => 40, ); } } @@ -665,55 +931,96 @@ function metatag_field_extra_fields() { return $extra; } +/** + * Check if an individual entity has meta tags defined, or has defaults. + * + * @param string $entity_type + * An entity type. + * @param object $entity + * An entity object. + * + * @return boolean + * TRUE or FALSE if the entity should have a form for or process meta tags. + */ +function metatag_entity_has_metatags($entity_type, $entity) { + // If an entity has custom meta tags assigned, then we should return TRUE. + if (!empty($entity->metatags)) { + return TRUE; + } + + // Otherwise, check to see if there exists any enabed configuration for + // either the entity type, or bundle (even if the configuration is empty). + // If no configuration exists, then we should not be displaying the meta tag + // forms or processing meta tags on entity view. + $config_exists = &drupal_static(__FUNCTION__, array()); + list( , , $bundle) = entity_extract_ids($entity_type, $entity); + // Do not pretend to have metatags when the bundle does not support them. + if (!metatag_entity_supports_metatags($entity_type, $bundle)) { + return FALSE; + } + $instance = "{$entity_type}:{$bundle}"; + if (!isset($config_exists[$instance])) { + // Check if the intstance or its parents (excluding global) are enabled. + $config_exists[$instance] = metatag_config_is_enabled($instance, TRUE, FALSE); + } + + return isset($config_exists[$instance]); +} + +/** + * Check whether the requested entity type (and bundle) support metatag. + * + * By default this will be FALSE, support has to be specifically enabled by + * assigning 'metatag' => TRUE within the hook_entity_info() definition for the + * entity. + */ function metatag_entity_supports_metatags($entity_type = NULL, $bundle = NULL) { - $types = &drupal_static(__FUNCTION__); + $entity_types = &drupal_static(__FUNCTION__); - if (!isset($types)) { - $types = array(); + if (!isset($entity_types)) { + $entity_types = array(); foreach (entity_get_info() as $entity_type_key => $entity_info) { - if (!isset($entity_info['metatags'])) { - // By default allow entities that have fields and have paths. - $entity_info['metatags'] = !empty($entity_info['uri callback']) && !empty($entity_info['fieldable']); - } if (empty($entity_info['metatags'])) { - $types[$entity_type_key] = FALSE; + $entity_types[$entity_type_key] = FALSE; continue; } - $types[$entity_type_key] = array(); + $entity_types[$entity_type_key] = array(); foreach ($entity_info['bundles'] as $bundle_key => $bundle_info) { - $types[$entity_type_key][$bundle_key] = !isset($bundle_info['metatags']) || !empty($bundle_info['metatags']); + $entity_types[$entity_type_key][$bundle_key] = !isset($bundle_info['metatags']) || !empty($bundle_info['metatags']); } } } if (isset($entity_type) && isset($bundle)) { - return isset($types[$entity_type][$bundle]) ? $types[$entity_type][$bundle] : FALSE; + return isset($entity_types[$entity_type][$bundle]) ? $entity_types[$entity_type][$bundle] : FALSE; } elseif (isset($entity_type)) { - return isset($types[$entity_type]) ? ($types[$entity_type] !== FALSE) : FALSE; + return isset($entity_types[$entity_type]) ? ($entity_types[$entity_type] !== FALSE) : FALSE; } - return $types; + return $entity_types; } /** * Implements hook_entity_info_alter(). + * + * Enables Metatag support for the core entities. */ function metatag_entity_info_alter(&$info) { $defaults['node'] = array( 'path' => 'node/%node', + 'metatags' => TRUE, ); $defaults['taxonomy_term'] = array( 'path' => 'taxonomy/term/%taxonomy_term', + 'metatags' => TRUE, ); if (module_exists('forum') && ($vid = variable_get('forum_nav_vocabulary', 0)) && $vocabulary = taxonomy_vocabulary_load($vid)) { $defaults['taxonomy_term']['bundles'][$vocabulary->machine_name]['path'] = 'forum/%taxonomy_term'; } $defaults['user'] = array( 'path' => 'user/%user', - ); - $defaults['comment'] = array( - 'metatags' => FALSE, + 'metatags' => TRUE, ); foreach ($defaults as $key => $entity_defaults) { @@ -756,36 +1063,122 @@ function metatag_load_entity_from_path($path) { } } + // Allow other modules to customize the data using + // hook_metatag_load_entity_from_path_alter(). drupal_alter('metatag_load_entity_from_path', $path, $result); + return $result; } +/** + * Add meta tags to be added later with metatag_page_build(). + * + * @param string $instance + * The configuration instance key of the meta tags, e.g. "node:article". + * @param array $metatags + * An array of meta tags from metatag_metatags_view(). + */ +function metatag_page_set_metatags($instance, $metatags) { + $page_metatags = &drupal_static(__FUNCTION__, array()); + $page_metatags[$instance] = $metatags; +} + +/** + * Retrieve the array of met tags to be added with metatag_page_build(). + */ +function metatag_page_get_metatags() { + // @todo Add alter to this result? + return drupal_static('metatag_page_set_metatags', array()); +} + /** * Implements hook_page_build(). */ function metatag_page_build(&$page) { - // For some reason with Overlay enabled we get an empty $page, so just fail - // this case. - if (!isset($page['content'])) { - return; + // Ensure these arrays exist, otherwise several use cases will fail. + if (!isset($page['content']) || !is_array($page['content'])) { + $page['content'] = array(); } + if (!isset($page['content']['metatags']) || !is_array($page['content']['metatags'])) { + $page['content']['metatags'] = array(); + } + + // The front page has special consideration. + $instance = 'global:frontpage'; + if (drupal_is_front_page() && metatag_config_is_enabled($instance)) { + $instance = 'global:frontpage'; - // Load the metatags render array in before any page content so that more - // more specific meta tags in the page content can override these meta tags. - $page['content'] = array('metatags' => array()) + $page['content']; - if (drupal_is_front_page()) { - $page['content']['metatags']['global:frontpage'] = metatag_metatags_view('global:frontpage', array()); + // These two parts are sufficient given that the homepage is unique. + $cid_parts = array( + 'langcode' => $GLOBALS['language_content']->language, + 'url' => $GLOBALS['base_url'] . base_path() . '<front>', + ); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to customize the data using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$instance}:" . hash('sha256', serialize($cid_parts)); + + if ($cache = cache_get($cid, 'cache_metatag')) { + $metatags = $cache->data; + } + else { + $metatags = metatag_metatags_view($instance, array()); + cache_set($cid, $metatags, 'cache_metatag'); + } + + $page['content']['metatags'][$instance] = $metatags; + } + + // Load any meta tags assigned via metatag_page_set_metatags(). Note: this + // must include the necessary defaults. + else { + $page['content']['metatags'] += metatag_page_get_metatags(); } - elseif (!path_is_admin(current_path())) { - // Do not output the global metatags when on an administration path. - $page['content']['metatags']['global'] = metatag_metatags_view('global', array()); + + // If no meta tags were loaded, and this is not an admin path, at least load + // the global defaults. This may be disabled, see README.txt for details. + if (empty($page['content']['metatags']) && variable_get('metatag_load_all_pages', TRUE) && !path_is_admin(current_path())) { + $instance = 'global'; + + // These two parts are sufficient given that the homepage is unique. + $cid_parts = array( + 'langcode' => $GLOBALS['language_content']->language, + 'url' => $GLOBALS['base_url'] . $_SERVER['REQUEST_URI'], + ); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to customize the data using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$instance}:" . hash('sha256', serialize($cid_parts)); + + if ($cache = cache_get($cid, 'cache_metatag')) { + $metatags = $cache->data; + } + else { + $metatags = metatag_metatags_view($instance, array()); + cache_set($cid, $metatags, 'cache_metatag'); + } + $page['content']['metatags'][$instance] = $metatags; } } /** * Returns whether the current page is the page of the passed in entity. * - * @param $type + * @param $entity_type * The entity type; e.g. 'node' or 'user'. * @param $entity * The entity object. @@ -794,9 +1187,9 @@ function metatag_page_build(&$page) { * TRUE if the current page is the page of the specified entity, or FALSE * otherwise. */ -function _metatag_entity_is_page($type, $entity) { - $uri = entity_uri($type, $entity); - return (!empty($uri) && current_path() == $uri['path']); +function _metatag_entity_is_page($entity_type, $entity) { + $uri = entity_uri($entity_type, $entity); + return !empty($uri['path']) && current_path() == $uri['path']; } /** @@ -824,15 +1217,49 @@ function metatag_field_attach_delete_bundle($entity_type, $bundle) { * Implements hook_field_attach_form(). */ function metatag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); - - if (!metatag_entity_supports_metatags($entity_type, $bundle)) { + if (!metatag_entity_has_metatags($entity_type, $entity)) { + return; + } + // Entity_Translation will trigger this hook again, skip it. + if (!empty($form_state['entity_translation']['is_translation'])) { return; } + list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); $instance = "{$entity_type}:{$bundle}"; - $metatags = isset($entity->metatags) ? $entity->metatags : array(); + // Grab the meta tags for display in the form if there are any. + if (!empty($entity->metatags)) { + // Identify the language to use with this entity. + $entity_language = metatag_entity_get_language($entity_type, $entity); + + // If this is a new translation using Entity Translation, load the meta + // tags from the entity's original language. + if (module_exists('entity_translation') && empty($form['#entity_translation_source_form']) && ($handler = entity_translation_entity_form_get_handler($form, $form_state)) && isset($entity->metatags[$handler->getSourceLanguage()])) { + $metatags = $entity->metatags[$handler->getSourceLanguage()]; + } + // Determine from where we should get the tags. + elseif (isset($entity->metatags[$langcode])) { + // Set the tags to the translation set matching that of the form. + $metatags = $entity->metatags[$langcode]; + } + // There is no translation for this entity's tags in the current + // language. Instead, display tags in the language of the entity, the + // source language of translations. The will provide translators with the + // original text to translate. + elseif (isset($entity->metatags[$entity_language])) { + $metatags = $entity->metatags[$entity_language]; + } + // This is a preview so set the tags to the raw submission data. No + // language has been set. + else { + $metatags = $entity->metatags; + } + } + else { + $metatags = array(); + } + $options['token types'] = array(token_get_entity_mapping('entity', $entity_type)); $options['context'] = $entity_type; @@ -877,12 +1304,14 @@ function metatag_get_info($type = NULL, $name = NULL) { if (!isset($info)) { // hook_metatag_info() includes translated strings, so each language is cached // separately. - $cid = 'info:' . $GLOBALS['language']->language; + $cid = 'info:' . LANGUAGE_NONE; if ($cache = cache_get($cid, 'cache_metatag')) { $info = $cache->data; } else { + // Obtain all metatag specs defined in other modules using + // hook_metatag_info(). $info = module_invoke_all('metatag_info'); $info += array('tags' => array(), 'groups' => array()); @@ -895,8 +1324,10 @@ function metatag_get_info($type = NULL, $name = NULL) { ); } - // Let other modules alter the entity info and then cache it. + // Let other modules alter the entity info using + // hook_metatag_info_alter(). drupal_alter('metatag_info', $info); + cache_set($cid, $info, 'cache_metatag'); } } @@ -918,9 +1349,6 @@ function metatag_get_instance($metatag, array $data = array()) { $class = $info['class']; return new $class($info, $data); } - else { - trigger_error("Failed to load class {$info['class']} for metatag $metatag.", E_USER_ERROR); - } } /** @@ -1002,7 +1430,6 @@ function metatag_html_head_alter(&$elements) { foreach (array_keys($elements) as $key) { if (strpos($key, 'drupal_add_html_head_link:' . $name . ':') === 0) { unset($elements[$key]); - break; } } } @@ -1030,8 +1457,14 @@ function metatag_config_instance_info($instance = NULL) { $info = $cache->data; } else { + // Allow modules to act upon the record insertion using + // hook_metatag_config_instance_info(). $info = module_invoke_all('metatag_config_instance_info'); + + // Allow other modules to customize the data using + // hook_metatag_config_instance_info_alter(). drupal_alter('metatag_config_instance_info', $info); + cache_set($cid, $info, 'cache_metatag'); } } @@ -1101,12 +1534,20 @@ function metatag_config_instance_label($instance) { $labels = &drupal_static(__FUNCTION__, array()); if (!isset($labels[$instance])) { - $context = metatag_config_instance_info($instance); - $labels[$instance] = isset($context['label']) ? $context['label'] : t('Unknown'); - $parents = metatag_config_get_parent_instances($instance, FALSE); - array_shift($parents); - if (!empty($parents)) { - $labels[$instance] = metatag_config_instance_label(implode(':', $parents)) . ': ' . $labels[$instance]; + $instance_parts = explode(':', $instance); + $instance_part = array_pop($instance_parts); + if ($context = metatag_config_instance_info($instance)) { + $labels[$instance] = $context['label']; + } + else { + $labels[$instance] = t('Unknown (@instance)', array('@instance' => $instance_part)); + } + // Normally the following would use metatag_config_get_parent_instances() + // but since we already sliced the instance by separator and removed the + // last segment, putting the array back together gives us this instance's + // parent. + if (!empty($instance_parts)) { + $labels[$instance] = metatag_config_instance_label(implode(':', $instance_parts)) . ': ' . $labels[$instance]; } } @@ -1143,3 +1584,145 @@ function metatag_config_access($op, $config = NULL) { return FALSE; } + +/** + * Checks if a metatag configuration record is enabled. + * + * @param string $instance + * The configuration instance machine name. + * + * @return bool + * TRUE if the configuration is enabled, or FALSE otherwise. + */ +function metatag_config_is_enabled($instance, $include_defaults = FALSE, $include_global = TRUE) { + if ($include_defaults) { + return (bool) metatag_config_load_with_defaults($instance, $include_global); + } + else { + $config = metatag_config_load($instance); + return !empty($config) && empty($config->disabled); + } +} + +/** + * Wrapper around entity_language() to use LANGUAGE_NONE if the entity does not + * have a language assigned. + * + * @param $entity_type + * An entity type's machine name. + * @param $entity + * The entity to review; + * + * @return + * A string indicating the language code to be used. + */ +function metatag_entity_get_language($entity_type, $entity) { + // Determine the entity's language. + $langcode = entity_language($entity_type, $entity); + + // If no matching language was found, which will happen for e.g. terms and + // users, it is normally recommended to use the system default language. + // However, as the system default language can change, this could potentially + // cause data loss / confusion problems; as a result use the system "no + // language" value to avoid any potential problems. + if (empty($langcode)) { + $langcode = LANGUAGE_NONE; + } + + return $langcode; +} + +/** + * Implements of hook_features_api(). + */ +function metatag_features_api() { + $components = array( + 'metatag' => array( + 'name' => t('Meta tags'), + 'feature_source' => TRUE, + 'default_hook' => 'metatag_export_default', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'file' => drupal_get_path('module', 'metatag') . '/metatag.features.inc', + ), + ); + return $components; +} + +/** + * Implements hook_views_pre_render(). + */ +function metatag_views_post_render(&$view, &$output, &$cache) { + // Build a shortcut to the current display object. + $display = $view->display[$view->current_display]; + + // Only proceed if this view is a full page, don't process block or other + // Views display objects. + if ($display->display_plugin == 'page') { + // Check if this is an entity display page, if so trigger + // hook_entity_view(). + foreach (entity_get_info() as $entity_name => $entity_type) { + // Entity paths will include an auto-loader that matches the entity's + // name, thus the path will be 'some/path/%entity_name'. + if (isset($entity_type['path']) && ($display->display_options['path'] . $entity_name) == $entity_type['path']) { + // Only proceed if this entity type supports meta tags. + if (metatag_entity_supports_metatags($entity_name)) { + // There must be at least one argument and the first argument must be + // numerical. + if (!empty($view->args) && is_numeric($view->args[0])) { + // Only the first argument is used. + $entities = entity_load($entity_name, array($view->args[0])); + $entity = array_pop($entities); + metatag_entity_view($entity, $entity_name, 'full', NULL); + } + } + } + } + } +} + +/** + * Implements hook_ctools_render_alter(). + * + * Temporary solution to load meta tags on entity pages that are driven by + * CTools display handlers. + */ +function metatag_ctools_render_alter(&$info, $page, $context) { + // Only proceed if this is a full page (don't process individual panes) and + // there's an 'admin path' for the current task. + if ($page && !empty($context['task']['admin path'])) { + // Check if this is an entity display page, if so trigger + // hook_entity_view(). + foreach (entity_get_info() as $entity_name => $entity_type) { + // Entity paths will include an auto-loader that matches the entity's + // name, thus the path will be 'some/path/%entity_name'. + if (isset($entity_type['path']) && $context['task']['admin path'] == $entity_type['path']) { + // Only proceed if this entity type supports meta tags. + if (metatag_entity_supports_metatags($entity_name)) { + // There must be at least one argument and the first argument must be + // numerical. + if (!empty($context['args']) && is_numeric($context['args'][0])) { + // Only the first argument is used. + $entities = entity_load($entity_name, array($context['args'][0])); + $entity = array_pop($entities); + metatag_entity_view($entity, $entity_name, 'full', NULL); + } + } + } + } + } +} + +/** + * Implements hook_entity_translation_delete(). + * + * Required for content translations being handled via Entity_Translation to + * remove the appropriate record when a translation is removed without the + * corresponding entity record also being removed. + */ +function metatag_entity_translation_delete($entity_type, $entity, $langcode) { + // Get the entity's ID. + list($entity_id) = entity_extract_ids($entity_type, $entity); + + // Delete the translation. + metatag_metatags_delete($entity_type, $entity_id, $langcode); +} diff --git a/sites/all/modules/metatag/metatag.test b/sites/all/modules/metatag/metatag.test index 2133504d79d65fbc857b56b17dee228de49548ac..ad5076eca968521647f9c0edd2c6fc325e7394e1 100644 --- a/sites/all/modules/metatag/metatag.test +++ b/sites/all/modules/metatag/metatag.test @@ -26,25 +26,27 @@ class MetaTagsUnitTest extends MetaTagsTestHelper { $defaults = metatag_config_load_with_defaults('test:foo'); $this->assertEqual($defaults, array( 'description' => array('value' => 'Test foo description'), + 'abstract' => array('value' => 'Test foo abstract'), 'title' => array('value' => 'Test altered title'), 'test:foo' => array('value' => 'foobar'), 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), )); } public function testEntitySupport() { - $test_cases[0] = array('type' => 'node', 'expected' => TRUE); $test_cases[1] = array('type' => 'node', 'bundle' => 'article', 'expected' => TRUE); $test_cases[2] = array('type' => 'node', 'bundle' => 'page', 'expected' => TRUE); $test_cases[3] = array('type' => 'node', 'bundle' => 'invalid-bundle', 'expected' => FALSE); $test_cases[4] = array('type' => 'user', 'expected' => TRUE); - $test_cases[5] = array('type' => 'invalid-entity', 'expected' => FALSE); foreach ($test_cases as $test_case) { $test_case += array('bundle' => NULL); - $this->assertMetatagEntitySupportsMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); + $this->assertMetatagEntityHasMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); } variable_set('metatag_test_entity_info_disable', TRUE); + drupal_static_reset('metatag_entity_has_metatags'); drupal_static_reset('metatag_entity_supports_metatags'); entity_info_cache_clear(); @@ -52,19 +54,43 @@ class MetaTagsUnitTest extends MetaTagsTestHelper { $test_cases[4]['expected'] = FALSE; foreach ($test_cases as $test_case) { $test_case += array('bundle' => NULL); - $this->assertMetatagEntitySupportsMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); + $this->assertMetatagEntityHasMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); } } - function assertMetatagEntitySupportsMetatags($type, $bundle, $expected) { + function assertMetatagEntityHasMetatags($entity_type, $bundle, $expected) { + $entity = entity_create_stub_entity($entity_type, array(0, NULL, $bundle)); return $this->assertEqual( - metatag_entity_supports_metatags($type, $bundle), + metatag_entity_has_metatags($entity_type, $entity), $expected, - t("metatag_entity_supports_metatags(:type, :bundle) was :expected", array( - ':type' => var_export($type, TRUE), - ':bundle' => var_export($bundle, TRUE), + t("metatag_entity_has_metatags(:type, :entity) is :expected", array( + ':type' => var_export($entity_type, TRUE), + ':entity' => var_export($entity, TRUE), ':expected' => var_export($expected, TRUE), )) ); } + + /** + * Test the metatag_config_instance_label() function. + */ + public function testConfigLabels() { + $test_cases = array( + 'node' => 'Node', + 'node:article' => 'Node: Article', + 'node:article:c' => 'Node: Article: Unknown (c)', + 'node:b' => 'Node: Unknown (b)', + 'node:b:c' => 'Node: Unknown (b): Unknown (c)', + 'a' => 'Unknown (a)', + 'a:b' => 'Unknown (a): Unknown (b)', + 'a:b:c' => 'Unknown (a): Unknown (b): Unknown (c)', + 'a:b:c:d' => 'Unknown (a): Unknown (b): Unknown (c): Unknown (d)', + ); + + foreach ($test_cases as $input => $expected_output) { + drupal_static_reset('metatag_config_instance_label'); + $actual_output = metatag_config_instance_label($input); + $this->assertEqual($actual_output, $expected_output); + } + } } diff --git a/sites/all/modules/metatag/metatag_context/README.txt b/sites/all/modules/metatag/metatag_context/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..3499605888a233f5906c94f976be17b3c33c5f13 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/README.txt @@ -0,0 +1,21 @@ +Metatag Context +--------------- +This module is provides a Metatag reaction for Context [1], thus allowing meta +tags to be assigned to specific paths and other conditions. + +Configuration can controlled via the normal Context UI module or the new admin +page available at: admin/config/search/metatags/context + + +Credits +------------------------------------------------------------------------------ +This module is based on the Context Metadata [2] module. The initial +development was by Marcin Pajdzik [3] (sponsored by Dennis Publishing [4]). + + +References +------------------------------------------------------------------------------ +1: http://drupal.org/project/context +2: http://drupal.org/project/context_metadata +3: http://drupal.org/user/160555 +4: http://www.dennis.co.uk/ diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc b/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..d776cb20682ac5db000ac75ade3863445592597c --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc @@ -0,0 +1,219 @@ +<?php +/** + * @file + * Admin settings page for Metatag Context. + */ + +/** + * Provides administration overview page for metatags by path settings. + */ +function metatag_context_context_overview() { + $contexts = context_enabled_contexts(TRUE); + $header = array(t('Name'), t('Paths'), t('Operations')); + $rows = array(); + + $caption = t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))); + + foreach ($contexts as $name => $context) { + // Only show context items that are specifically selected to be "Shown on + // metatag admin page". + if (isset($context->reactions['metatag_context_reaction']['metatag_admin']) && $context->reactions['metatag_context_reaction']['metatag_admin']) { + $ops = array( + l('Edit', 'admin/config/search/metatags/context/' . $context->name, array('query' => array('destination' => 'admin/config/search/metatags/context'))), + l('Delete', 'admin/config/search/metatags/context/' . $context->name . '/delete', array('query' => array('destination' => 'admin/config/search/metatags/context'))), + ); + $rows[] = array( + $context->name, + isset($context->conditions['path']) ? implode(', ', $context->conditions['path']['values']) : t('No path condition.'), + implode(' | ', $ops), + ); + } + } + return theme('table', array('header' => $header, 'rows' => $rows, 'caption' => $caption)); +} + +function metatag_context_config_add_form($form, &$form_state) { + $form['name'] = array( + '#title' => 'Name', + '#type' => 'textfield', + '#default_value' => '', + '#description' => 'The unique ID for this metatag path context rule. This must contain only lower case letters, numbers and underscores.', + '#required' => 1, + '#maxlength' => 255, + '#element_validate' => array('metatag_context_edit_name_validate'), + ); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Add and configure'), + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/metatags/context', + ); + return $form; +} + +function metatag_context_edit_name_validate($element, &$form_state) { + // Check for string identifier sanity. + if (!preg_match('!^[a-z0-9_-]+$!', $element['#value'])) { + form_error($element, t('The name can only consist of lowercase letters, underscores, dashes, and numbers.')); + return; + } + + // Ensure the CTools exportables system is loaded. + ctools_include('export'); + + // Check for name collision. + if ($exists = ctools_export_crud_load('context', $element['#value'])) { + form_error($element, t('A context with this name already exists. Please choose another name or delete the existing item before creating a new one.')); + } +} + +function metatag_context_config_add_form_submit($form, &$form_state) { + $context = metatag_context_load_default_context(); + $context->name = $form_state['values']['name']; + context_save($context); + $form_state['redirect'] = 'admin/config/search/metatags/context/' . $context->name; +} + +function metatag_context_config_edit_form($form, &$form_state, $context) { + $form_state['metatag_context']['context'] = $context; + + // Empty form to start with. + $form = array(); + // Don't care about the instance name, the data is being managed by Context + // and not Metatag. + $instance = ""; + $options = array(); + + // Load the METATAG form. + metatag_metatags_form($form, $instance, $context->reactions['metatag_context_reaction']['metatags'], $options); + + $form['paths'] = array( + '#title' => 'Path', + '#description' => t('Set this metatag context when any of the paths above match the page path. Put each path on a separate line. You can use the <code>*</code> character (asterisk) as a wildcard and the <code>~</code> character (tilde) to exclude one or more paths. Use <code><front></code> for the site front page. Only local paths (e.g. "example/page") will work, do not use relative URLs ("/example/page") or absolute URLs ("http://example.com/example/page").'), + '#type' => 'textarea', + '#default_value' => isset($context->conditions['path']['values']) ? html_entity_decode(implode(' ', $context->conditions['path']['values'])) : '', + '#required' => 1, + '#weight' => -100, + ); + + // If other conditions are assigned, mention it. + $conditions = array_keys($context->conditions); + foreach ($conditions as $key => $condition) { + if ($condition == 'path') { + unset($conditions[$key]); + } + } + if (!empty($conditions)) { + $form['other_conditions'] = array( + '#prefix' => '<p><em>', + '#markup' => t('Other conditions have been assigned that must be controlled through the main Context settings page.'), + '#suffix' => '</em></p>', + '#weight' => -99.9, + ); + } + + $form['help'] = array( + '#prefix' => '<hr /><p><em>', + '#markup' => t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))), + '#suffix' => '</em></p>', + '#weight' => -99, + ); + + // Show all tokens. + $form['metatags']['tokens']['#token_types'] = 'all'; + + $form['metatags']['#type'] = 'container'; + unset($form['metatags']['#collapsed']); + unset($form['metatags']['#collapsible']); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + $form['actions']['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel'), + '#submit' => array('metatag_context_config_edit_form_cancel_submit'), + '#limit_validation_errors' => array(), + ); + $form['#submit'][] = 'metatag_context_config_edit_form_submit'; + + return $form; +} + +function metatag_context_config_edit_form_cancel_submit($form, &$form_state) { + context_delete($form_state['metatag_context']['context']); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_config_edit_form_submit($form, &$form_state) { + $context = $form_state['metatag_context']['context']; + $context->reactions['metatag_context_reaction']['metatags'] = array_merge($context->reactions['metatag_context_reaction']['metatags'], $form_state['values']['metatags']); + $paths = explode("\n", str_replace("\r", "", $form_state['values']['paths'])); + $paths = array_combine($paths, $paths); + $context->conditions['path']['values'] = $paths; + context_save($context); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_delete_form($form, &$form_state, $context) { + $form_state['metatag_context']['context'] = $context; + + $form['delete'] = array( + '#value' => 'This action will permanently remove this item from your database.' + ); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/metatags/context', + ); + $form['#submit'][] = 'metatag_context_delete_form_submit'; + + return $form; +} + +function metatag_context_delete_form_submit($form, &$form_state) { + context_delete($form_state['metatag_context']['context']); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_load_default_context() { + $context = new stdClass(); + $context->disabled = FALSE; /* Edit this to true to make a default context disabled initially */ + $context->api_version = 3; + $context->name = 'default_metatag_context'; + $context->description = ''; + $context->tag = 'Metatag'; + $context->metatag = TRUE; + $context->conditions = array( + 'path' => array( + 'values' => array( + ), + ), + ); + $context->reactions = array( + 'metatag_context_reaction' => array( + 'metatags' => array(), + 'metatag_admin' => 1, + ), + ); + $context->condition_mode = 0; + $context->weight = 0; + + // Translatables + // Included for use with string extractors like potx. + t('Metatag'); + return $context; +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.context.inc b/sites/all/modules/metatag/metatag_context/metatag_context.context.inc new file mode 100644 index 0000000000000000000000000000000000000000..70f67232abc208d4677682b4a09ea16794796fd1 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.context.inc @@ -0,0 +1,134 @@ +<?php +/** + * @file + * Context reaction for Metatag. + */ + +class metatag_context_reaction extends context_reaction { + function options_form($context) { + $form = array(); + + // Don't care about the instance name, the data is being managed by + // Context and not Metatag. + $instance = ""; + // Load the previously saved settings. + $data = $this->fetch_from_context($context); + if (!isset($data['metatags'])) { + $data['metatags'] = array(); + } + // No options currently available. + $options = array(); + + $form['help'] = array( + '#prefix' => '<p><em>', + '#markup' => t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))), + '#suffix' => '</em></p>', + ); + + $form['basic_header'] = array( + '#prefix' => '<hr /><h3>', + '#markup' => t('Basic tags'), + '#suffix' => '</h3>', + ); + + // Load the basic Metatag form. + metatag_metatags_form($form, $instance, $data['metatags'], $options); + + // Stop the meta tag fields appearing within a fieldset. + $form['metatags']['#type'] = 'container'; + unset($form['metatags']['#collapsed']); + unset($form['metatags']['#collapsible']); + unset($form['#submit']); + + // Flatten the fieldsets because otherwise the Context module will not save + // them properly. + // TODO: Perhaps it can be done in a better way with #tree and #parents? + foreach (array('advanced', 'dublin-core', 'open-graph') as $fieldset) { + if (isset($form['metatags'][$fieldset])) { + $form['metatags'][$fieldset . '_heading'] = array( + '#prefix' => '<hr /><h3>', + '#markup' => $form['metatags'][$fieldset]['#title'], + '#suffix' => '</h3>', + ); + if (isset($form['metatags'][$fieldset]['#description'])) { + $form['metatags'][$fieldset . '_description'] = array( + '#prefix' => '<p>', + '#markup' => $form['metatags'][$fieldset]['#description'], + '#suffix' => '</p>', + ); + } + foreach ($form['metatags'][$fieldset] as $key => $value) { + if (substr($key, 0, 1) == '#') { + unset ($form['metatags'][$fieldset][$key]); + continue; + } + $form['metatags'][$key] = $value; + unset($form['metatags'][$key]['#parents']); + unset($form['metatags'][$fieldset][$key]); + } + unset($form['metatags'][$fieldset]); + } + } + + // Show all takens. + $form['metatags']['tokens']['#token_types'] = 'all'; + + $form['metatag_admin'] = array( + '#type' => 'checkbox', + '#title' => t('Show on metatag admin page.'), + '#weight' => -98, + '#default_value' => isset($data['metatag_admin']) ? $data['metatag_admin'] : '', + ); + + return $form; + } + + /** + * Output a list of active contexts. + */ + function execute() { + $output = &drupal_static('metatag_context'); + + if (!isset($output)) { + $metatags = array(); + $output = array(); + $contexts = context_active_contexts(); + $options = array(); + $instance_names = array(); + + foreach ($contexts as $context) { + if (!empty($context->reactions['metatag_context_reaction']['metatags'])) { + $metadata_array = $context->reactions['metatag_context_reaction']['metatags']; + foreach ($metadata_array as $key => $data) { + if (!empty($data['value'])) { + $metatags[$key] = $data;//t(check_plain($data['value'])); + } + } + + // Add this context to the list of instances. + $instance_names[] = $context->name; + } + } + + // Only proceed if metatags were assigned. + if (!empty($metatags)) { + $metatags += metatag_config_load_with_defaults(''); + + foreach ($metatags as $metatag => $data) { + if ($metatag_instance = metatag_get_instance($metatag, $data)) { + $output[$metatag] = $metatag_instance->getElement($options); + } + } + + // Compile the identifier for this combination based on the context + // names. + asort($instance_names); + $instance = 'context:' . implode(',', $instance_names); + + // Allow the output meta tags to be modified using + // hook_metatag_metatags_view_alter(). + drupal_alter('metatag_metatags_view', $output, $instance); + } + } + } +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.info b/sites/all/modules/metatag/metatag_context/metatag_context.info new file mode 100644 index 0000000000000000000000000000000000000000..14d32b947fe9aa8c888beafeae26178297b007ea --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.info @@ -0,0 +1,13 @@ +name = Meta tags: Context +description = "Assigned Meta tags using Context definitions, allowing them to be assigned by path and other criteria." +package = Meta tags +core = 7.x +dependencies[] = context +dependencies[] = metatag + +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.install b/sites/all/modules/metatag/metatag_context/metatag_context.install new file mode 100644 index 0000000000000000000000000000000000000000..78415ce60305a4e2101699c3e87e909c00d55725 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.install @@ -0,0 +1,13 @@ +<?php +/** + * @file + * Installation and update hooks for Metatag:Context. + */ + +/** + * Implements hook_enable(). + */ +function metatag_context_enable() { + // Clear the cache so Context and CTools know about this plugin. + cache_clear_all('plugins:context:plugins', 'cache'); +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.module b/sites/all/modules/metatag/metatag_context/metatag_context.module new file mode 100644 index 0000000000000000000000000000000000000000..8be7312f5a59fee29df530ba0f7f293c75a3d786 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.module @@ -0,0 +1,105 @@ +<?php +/** + * @file + * Primary hook implementations for Metatag Context. + */ + +/** + * Implements hook_menu(). + */ +function metatag_context_menu() { + $items['admin/config/search/metatags/context'] = array( + 'title' => 'By path', + 'page callback' => 'metatag_context_context_overview', + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/config/search/metatags/context/add'] = array( + 'title' => 'Add a meta tag by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_config_add_form'), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + 'type' => MENU_LOCAL_ACTION, + ); + $items['admin/config/search/metatags/context/%context'] = array( + 'title' => 'Configure metatags by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_config_edit_form', 5), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + ); + $items['admin/config/search/metatags/context/%context/delete'] = array( + 'title' => 'Delete metatags by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_delete_form', 5), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + ); + + return $items; +} + +/** + * Implements hook_context_plugins(). + */ +function metatag_context_context_plugins() { + return array( + 'metatag_context_reaction' => array( + 'handler' => array( + 'path' => drupal_get_path('module', 'metatag_context'), + 'file' => 'metatag_context.context.inc', + 'class' => 'metatag_context_reaction', + 'parent' => 'context_reaction', + ), + ), + ); +} + +/** + * Implements hook_context_registry(). + */ +function metatag_context_context_registry() { + return array( + 'reactions' => array( + 'metatag_context_reaction' => array( + 'title' => t('Meta Data'), + 'description' => t('Control page meta tags using the Metatag module.'), + 'plugin' => 'metatag_context_reaction', + ), + ), + ); +} + +/** + * Implements hook_context_page_reaction(). + */ +function metatag_context_context_page_reaction() { + if ($plugin = context_get_plugin('reaction', 'metatag_context_reaction')) { + $plugin->execute(); + } +} + +/** + * Implements hook_page_build(). + */ +function metatag_context_page_build(&$page) { + // Load the meta tags that have been generated for this page. + $metatags = drupal_static('metatag_context', array()); + + if (!empty($metatags)) { + $page['content']['metatags']['global'] = $metatags; + } +} + +/** + * Implements hook_preprocess_html(). + */ +function metatag_context_preprocess_html(&$variables) { + $metadata = drupal_static('metatag_context'); + + if (isset($metadata['metadata_title'])) { + $variables['head_title'] = token_replace($metadata['metadata_title']); + } +} diff --git a/sites/all/modules/metatag/metatag_dc/README.txt b/sites/all/modules/metatag/metatag_dc/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..d95921887f4db8384f41bd3268fe1cf9380b242a --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/README.txt @@ -0,0 +1,36 @@ +Metatag: Dublin Core +-------------------- +This module adds the fifteen Dublin Core Metadata Element Set [1] to the +available meta tags, as defined by the Dublin Core Metadata Institute [2]. + +The following tags are provided: +* dcterms.contributor +* dcterms.coverage +* dcterms.creator +* dcterms.date +* dcterms.description +* dcterms.format +* dcterms.identifier +* dcterms.language +* dcterms.publisher +* dcterms.relation +* dcterms.rights +* dcterms.source +* dcterms.subject +* dcterms.title +* dcterms.type + + +Credits +------------------------------------------------------------------------------ +The initial development was by Marty2081 [3] (sponsored by Gemeentemuseum Den +Haag. [4]), with contributions by many in the community [5]. + + +References +------------------------------------------------------------------------------ +1: http://dublincore.org/documents/dces/ +2: http://www.dublincore.org/ +3: http://drupal.org/user/960720 +4: http://www.gemeentemuseum.nl/ +5: http://drupal.org/node/1491616 diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.info b/sites/all/modules/metatag/metatag_dc/metatag_dc.info new file mode 100644 index 0000000000000000000000000000000000000000..7e05d4efc9c33d76fe51bc62e2a62207d63ce468 --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.info @@ -0,0 +1,12 @@ +name = Meta tags: Dublin Core +description = Provides the fifteen <a href="http://dublincore.org/documents/dces/">Dublin Core Metadata Element Set 1.1</a> meta tags from the <a href="http://dublincore.org/">Dublin Core Metadata Institute</a>. +package = Meta tags +core = 7.x +dependencies[] = metatag + +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc b/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc new file mode 100644 index 0000000000000000000000000000000000000000..7f8094d157794ba486746d1d17267fa727a7c62d --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc @@ -0,0 +1,235 @@ +<?php +/** + * @file + * Metatag integration for the metatag_dc module. + */ + +/** + * Implements hook_metatag_config_default_alter(). + */ +function metatag_dc_metatag_config_default_alter(array &$configs) { + foreach ($configs as &$config) { + switch ($config->instance) { + case 'global': + $config->config += array( + 'dcterms.title' => array('value' => '[current-page:title]'), + 'dcterms.type' => array('value' => 'Text'), + 'dcterms.format' => array('value' => 'text/html'), + ); + break; + + case 'global:frontpage': + $config->config += array( + 'dcterms.title' => array('value' => '[site:name]'), + 'dcterms.identifier' => array('value' => '[site:url]'), + ); + break; + + case 'node': + $config->config += array( + 'dcterms.title' => array('value' => '[node:title]'), + 'dcterms.date' => array('value' => '[node:created:custom:Y-m-d\TH:iP]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.language' => array('value' => '[node:language]'), + 'dcterms.creator' => array('value' => '[node:author]'), + ); + break; + + case 'taxonomy_term': + $config->config += array( + 'dcterms.title' => array('value' => '[term:name]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.description' => array('value' => '[term:description]'), + ); + break; + + case 'user': + $config->config += array( + 'dcterms.title' => array('value' => '[user:name]'), + 'dcterms.date' => array('value' => '[user:created:custom:Y-m-d\TH:iP]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.creator' => array('value' => '[user:name]'), + ); + break; + } + } +} + +/** + * Implements hook_metatag_info(). + * Dublin Core Elements taken from http://purl.org/dc/elements/1.1/. + */ +function metatag_dc_metatag_info() { + $info['groups']['dublin-core'] = array( + 'label' => t('Dublin Core'), + 'description' => t('The Dublin Core Metadata Element Set, aka "Dublin Core meta tags", are a set of internationally standardized metadata tags used to describe content to make identification and classification of content easier; the standards are controlled by the <a href="http://dublincore.org/">Dublin Core Metadata Initiative (DCMI)</a>.'), + 'form' => array( + '#weight' => 70, + ), + ); + $info['tags']['dcterms.title'] = array( + 'label' => t('Dublin Core Title'), + 'description' => t('The name given to the resource.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#type' => 'term', + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.creator'] = array( + 'label' => t('Dublin Core Creator'), + 'description' => t('An entity primarily responsible for making the resource. Examples of a Creator include a person, an organization, or a service. Typically, the name of a Creator should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.subject'] = array( + 'label' => t('Dublin Core Subject'), + 'description' => t('The topic of the resource. Typically, the subject will be represented using keywords, key phrases, or classification codes. Recommended best practice is to use a controlled vocabulary. To describe the spatial or temporal topic of the resource, use the Coverage element.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.description'] = array( + 'label' => t('Dublin Core Description'), + 'description' => t('An account of the resource. Description may include but is not limited to: an abstract, a table of contents, a graphical representation, or a free-text account of the resource.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.publisher'] = array( + 'label' => t('Dublin Core Publisher'), + 'description' => t('An entity responsible for making the resource available. Examples of a Publisher include a person, an organization, or a service. Typically, the name of a Publisher should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.contributor'] = array( + 'label' => t('Dublin Core Contributor'), + 'description' => t('An entity responsible for making contributions to the resource. Examples of a Contributor include a person, an organization, or a service. Typically, the name of a Contributor should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.date'] = array( + 'label' => t('Dublin Core Date'), + 'description' => t('A point or period of time associated with an event in the lifecycle of the resource. Date may be used to express temporal information at any level of granularity. Recommended best practice is to use an encoding scheme, such as the W3CDTF profile of ISO 8601 [W3CDTF].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.type'] = array( + 'label' => t('Dublin Core Type'), + 'description' => t('The nature or genre of the resource. Recommended best practice is to use a controlled vocabulary such as the DCMI Type Vocabulary [DCMITYPE]. To describe the file format, physical medium, or dimensions of the resource, use the Format element.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'form' => array( + '#type' => 'select', + '#options' => _metatag_dc_dcmi_type_vocabulary_options(), + '#empty_option' => t('- None -'), + ), + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.format'] = array( + 'label' => t('Dublin Core Format'), + 'description' => t('The file format, physical medium, or dimensions of the resource. Examples of dimensions include size and duration. Recommended best practice is to use a controlled vocabulary such as the list of Internet Media Types [MIME].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.identifier'] = array( + 'label' => t('Dublin Core Identifier'), + 'description' => t('An unambiguous reference to the resource within a given context. Recommended best practice is to identify the resource by means of a string conforming to a formal identification system.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.source'] = array( + 'label' => t('Dublin Core Source'), + 'description' => t('A related resource from which the described resource is derived. The described resource may be derived from the related resource in whole or in part. Recommended best practice is to identify the related resource by means of a string conforming to a formal identification system.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.language'] = array( + 'label' => t('Dublin Core Language'), + 'description' => t('A language of the resource. Recommended best practice is to use a controlled vocabulary such as RFC 4646 [RFC4646].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.relation'] = array( + 'label' => t('Dublin Core Relation'), + 'description' => t('A related resource. Recommended best practice is to identify the related resource by means of a string conforming to a formal identification system.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.coverage'] = array( + 'label' => t('Dublin Core Coverage'), + 'description' => t('The spatial or temporal topic of the resource, the spatial applicability of the resource, or the jurisdiction under which the resource is relevant. Spatial topic and spatial applicability may be a named place or a location specified by its geographic coordinates. Temporal topic may be a named period, date, or date range. A jurisdiction may be a named administrative entity or a geographic place to which the resource applies. Recommended best practice is to use a controlled vocabulary such as the Thesaurus of Geographic Names [TGN]. Where appropriate, named places or time periods can be used in preference to numeric identifiers such as sets of coordinates or date ranges.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.rights'] = array( + 'label' => t('Dublin Core Rights'), + 'description' => t('Information about rights held in and over the resource. Typically, rights information includes a statement about various property rights associated with the resource, including intellectual property rights.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + + return $info; +} + +/** + * Function that returns the DCMI type options. + * Types taken from http://dublincore.org/documents/dcmi-type-vocabulary/. + */ +function _metatag_dc_dcmi_type_vocabulary_options() { + $options = array( + 'Collection', + 'Dataset', + 'Event', + 'Image', + 'InteractiveResource', + 'MovingImage', + 'PhysicalObject', + 'Service', + 'Software', + 'Sound', + 'StillImage', + 'Text', + ); + return drupal_map_assoc($options); +} diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.module b/sites/all/modules/metatag/metatag_dc/metatag_dc.module new file mode 100644 index 0000000000000000000000000000000000000000..6090aff71f7f662f750defa5f404d22b3fac1f24 --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.module @@ -0,0 +1,39 @@ +<?php +/** + * @file + * Metatag integration for the metatag_dc module. + */ + +/** + * Implements hook_ctools_plugin_api(). + */ +function metatag_dc_ctools_plugin_api($owner, $api) { + if ($owner == 'metatag' && $api == 'metatag') { + return array('version' => 1); + } +} + +/** + * Implements hook_theme(). + */ +function metatag_dc_theme() { + $info['metatag_dc'] = array( + 'render element' => 'element', + ); + + return $info; +} + +/** + * Theme callback for a Dublin Core meta tag. + */ +function theme_metatag_dc($variables) { + $element = &$variables['element']; + element_set_attributes($element, array( + '#name' => 'property', + '#schema' => 'schema', + '#value' => 'content') + ); + unset($element['#value']); + return theme('html_tag', $variables); +} diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info index 32df12e2ad61a7a9cc3bfdcc5ac15eb3d96e264f..6b32cfdfccd7c2bcb555691816c7e994944b891f 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info @@ -1,12 +1,12 @@ -name = Open Graph meta tags +name = Meta tags: Open Graph description = Provides support for open graph meta tags. package = Meta tags core = 7.x dependencies[] = metatag -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc index a6d9dc7b0454cff7bb6b611bd8b7d5fcfaaa7870..f6c6a1b2a5bf93f7a352ef685a173a7f76995e9e 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc @@ -16,7 +16,7 @@ function metatag_opengraph_metatag_config_default_alter(array &$configs) { 'og:type' => array('value' => 'article'), 'og:title' => array('value' => '[current-page:title]'), 'og:site_name' => array('value' => '[site:name]'), - 'og:url' => array('value' => '[current-page:url]'), + 'og:url' => array('value' => '[current-page:url:absolute]'), ); break; case 'global:frontpage': @@ -60,6 +60,39 @@ function metatag_opengraph_metatag_config_default_alter(array &$configs) { function metatag_opengraph_metatag_info() { $info['groups']['open-graph'] = array( 'label' => t('Open Graph'), + 'form' => array( + '#weight' => 50, + ), + ); + + $info['tags']['fb:admins'] = array( + 'label' => t('Facebook Admins'), + 'description' => t('A comma-separated list of Facebook user IDs of people who are considered administrators or moderators of this page. Most sites will only need to assign this on the global settings page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['fb:app_id'] = array( + 'label' => t('Facebook Application ID'), + 'description' => t('A comma-separated list of Facebook Platform Application IDs applicable for this site. Most sites will only need to assign this on the global settings page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:site_name'] = array( + 'label' => t('Open Graph site name'), + 'description' => t('A human-readable name for your site, e.g., <em>IMDb</em>.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_opengraph', + ), ); $info['tags']['og:title'] = array( @@ -71,23 +104,41 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); + + $info['tags']['og:description'] = array( + 'label' => t('Open Graph description'), + 'description' => t('A one to two sentence description of your page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:type'] = array( 'label' => t('Open Graph type'), 'description' => t('The type of your object, e.g., <em>movie</em>.'), 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), 'form' => array( '#type' => 'select', '#options' => _metatag_opengraph_type_options(), '#empty_option' => t('- None -'), ), - 'element' => array( - '#theme' => 'metatag_opengraph', - ), ); - //if (module_exists('select_or_other')) { - // $info['tags']['og:type']['form']['#type'] = 'select_or_other'; - //} + + if (module_exists('select_or_other')) { + // Enhance the og:type field to support custom types. + $info['tags']['og:type']['form']['#type'] = 'select_or_other'; + $info['tags']['og:type']['form']['#other'] = t('Other (please type a value)'); + $info['tags']['og:type']['form']['#other_unknown_defaults'] = 'other'; + $info['tags']['og:type']['form']['#theme'] = 'select_or_other'; + $info['tags']['og:type']['form']['#element_validate'] = array('select_or_other_element_validate'); + } + $info['tags']['og:image'] = array( 'label' => t('Open Graph image'), 'description' => t('An image URL which should represent your object within the graph. The image must be at least 50px by 50px and have a maximum aspect ratio of 3:1. We support PNG, JPEG and GIF formats.'), @@ -97,6 +148,7 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); + $info['tags']['og:url'] = array( 'label' => t('Open Graph URL'), 'description' => t('The canonical URL of your object that will be used as its permanent ID in the graph, e.g., <em>http://www.imdb.com/title/tt0117500/</em>.'), @@ -106,25 +158,153 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); - $info['tags']['og:site_name'] = array( - 'label' => t('Open Graph site name'), - 'description' => t('A human-readable name for your site, e.g., <em>IMDb</em>.'), + + $info['tags']['og:latitude'] = array( + 'label' => t('Open Graph Latitude'), + 'description' => '', 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', - 'context' => array('global'), 'element' => array( '#theme' => 'metatag_opengraph', ), ); - $info['tags']['og:description'] = array( - 'label' => t('Open Graph description'), - 'description' => t('A one to two sentence description of your page.'), + $info['tags']['og:longitude'] = array( + 'label' => t('Open Graph Longitude'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:street-address'] = array( + 'label' => t('Open Graph Street Address'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:locality'] = array( + 'label' => t('Open Graph Locality'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:region'] = array( + 'label' => t('Open Graph Region'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:postal-code'] = array( + 'label' => t('Open Graph Postal Code'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:country-name'] = array( + 'label' => t('Open Graph Country Name'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:email'] = array( + 'label' => t('Open Graph Email'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:phone_number'] = array( + 'label' => t('Open Graph Phone Number'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:fax_number'] = array( + 'label' => t('Open Graph Fax Number'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:video'] = array( + 'label' => t('Open Graph Video (URL)'), + 'description' => t('A URL to a video file that complements this object.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:secure_url'] = array( + 'label' => t('Open Graph Video Secure'), + 'description' => t('A URL to a video file that complements this object using the HTTPS protocol.'), 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', 'element' => array( '#theme' => 'metatag_opengraph', ), ); + $info['tags']['og:video:width'] = array( + 'label' => t('Open Graph Video Width'), + 'description' => t('The width of the video.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:height'] = array( + 'label' => t('Open Graph Video Height'), + 'description' => t('The height of the video.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:type'] = array( + 'label' => t('Open Graph Video Type'), + 'description' => t('The type of the video file.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + 'form' => array( + '#type' => 'select', + '#options' => array( + 'application/x-shockwave-flash' => 'Flash - playable directly from the feed', + 'text/html' => 'Separate HTML page', + ), + '#empty_option' => t('- None -'), + ), + ); return $info; } diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module index d94e7d4551efc875270699c4c0483d5eeb5ced01..f4a191dcbfb2ecf4abfbf50092b38d1dd2ec17d5 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module @@ -74,4 +74,7 @@ og:audio:type og:upc og:isbn + +fb:admins +fb:app_id */ diff --git a/sites/all/modules/metatag/metatag_twitter_cards/README.txt b/sites/all/modules/metatag/metatag_twitter_cards/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..d6e6e8290d91c64fb8f5a8a611ed352a3c5970c7 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/README.txt @@ -0,0 +1,44 @@ +Metatag: Twitter Cards +---------------------- +This module adds the fourteen basic Twitter Cards meta tags [1]. The following +tags are provided: + +* twitter:card +* twitter:site +* twitter:creator +* twitter:url +* twitter:title +* twitter:description +* twitter:image +* twitter:image:width +* twitter:image:height +* twitter:player +* twitter:player:width +* twitter:player:height +* twitter:player:stream +* twitter:player:stream:content_type + + +Usage +------------------------------------------------------------------------------ +The Twitter Cards meta tags are configured along with all other meta tags; +on-form help is provided to aid with configuring the meta tags. + +After enabling and configuring the meta tags it is important to first test [2] +the meta tags for compliance with Twitter's standards, and then apply [3] to +have your site's usage approved. + + +Credits +------------------------------------------------------------------------------ +The initial development was by nico059 [4] with contributions by many in the +community [5]. + + +References +------------------------------------------------------------------------------ +1: https://dev.twitter.com/docs/cards +2: https://dev.twitter.com/docs/cards/preview +3: http://drupal.org/user/960720 +4: http://www.gemeentemuseum.nl/ +5: http://drupal.org/node/1664322 diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info new file mode 100644 index 0000000000000000000000000000000000000000..670094651c573d17fdd2898decbc7ba5991c18ff --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info @@ -0,0 +1,11 @@ +name = Meta tags: Twitter Cards +description = "Provides support for Twitter's Card meta tags. NOTE: Only use if the site supports SSL as all URLs *must* be secured via HTTPS." +package = Meta tags +core = 7.x +dependencies[] = metatag +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc new file mode 100644 index 0000000000000000000000000000000000000000..3d705a67141b7e4410897a00ef9bcb6e790e0ea2 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc @@ -0,0 +1,214 @@ +<?php +/** + * @file + * Metatag integration for the metatag Twitter Cards module. + */ + +/** + * Implements hook_metatag_config_default_alter(). + */ +function metatag_twitter_cards_metatag_config_default_alter(array &$configs) { + foreach ($configs as &$config) { + switch ($config->instance) { + case 'global': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:description' => array('value' => '[site:slogan]'), + 'twitter:title' => array('value' => '[site:name]'), + 'twitter:url' => array('value' => '[current-page:url:absolute]'), + ); + break; + + case 'global:frontpage': + $config->config += array( + 'twitter:description' => array('value' => ''), + ); + break; + + case 'node': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:description' => array('value' => '[node:summary]'), + 'twitter:title' => array('value' => '[node:title]'), + ); + break; + + case 'taxonomy_term': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:title'=> array('value' => '[term:name]'), + ); + break; + } + } +} + +/** + * Implements hook_metatag_info(). + */ +function metatag_twitter_cards_metatag_info() { + $info['groups']['twitter-cards'] = array( + 'label' => t('Twitter card'), + 'description' => t('A set of meta tags specially for controlling the summaries displayed when content is shared on <a href="!url">Twitter</a>. <strong>NOTE:</strong> Only use if the site supports SSL as all URLs <em>must</em> be secured via HTTPS.', array('!url' => 'http://twitter.com/')), + 'form' => array( + '#weight' => 60, + ), + ); + + $info['tags']['twitter:card'] = array( + 'label' => t('Twitter card type'), + 'description' => t('Notes: no other fields are required for a <em>Summary</em> card, a <em>Photo</em> card requires the \'image\' field, while a <em>Media player</em> card requires the \'title\', \'description\', \'media player URL\', \'media player width\', \'media player height\' and \'image\' fields.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'form' => array( + '#type' => 'select', + '#options' => array( + 'summary' => t('Summary (default)'), + 'photo' => t('Photo'), + 'player' => t('Media player'), + ), + '#empty_option' => t('- None -'), + ), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:site'] = array( + 'label' => t('Site\'s Twitter account'), + 'description' => t('The @username for the website, which will be displayed in the Card\'s footer; must include the @ symbol.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:site:id'] = array( + 'label' => t('Site\'s Twitter account ID'), + 'description' => t('The numerical Twitter account ID for the website, which will be displayed in the Card\'s footer.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:creator'] = array( + 'label' => t('Creator\'s Twitter account'), + 'description' => t('The @username for the content creator / author for this page, including the @ symbol.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:creator:id'] = array( + 'label' => t('Creator\'s Twitter account ID'), + 'description' => t('The numerical Twitter account ID for the content creator / author for this page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:url'] = array( + 'label' => t('Page URL'), + 'description' => t('The permalink / canonical URL of the current page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:title'] = array( + 'label' => t('Title'), + 'description' => t('The page\'s title, which should be concise; it will be truncated at 70 characters by Twitter. This field is required unless this the \'type\' field is set to "photo".'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:description'] = array( + 'label' => t('Description'), + 'description' => t('A description that concisely summarizes the content of the page, as appropriate for presentation within a Tweet. Do not re-use the title text as the description, or use this field to describe the general services provided by the website. The string will be truncated, by Twitter, at the word to 200 characters.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image'] = array( + 'label' => t('Image URL'), + 'description' => t('The URL to a unique image representing the content of the page. Do not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images larger than 120x120px will be resized and cropped square based on longest dimension. Images smaller than 60x60px will not be shown. If the \'type\' is set to <em>Photo</em> then the image must be at least 280x150px.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image:width'] = array( + 'label' => t('Image width'), + 'description' => t('The width of the image being linked to, in pixels.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image:height'] = array( + 'label' => t('Image height'), + 'description' => t('The height of the image being linked to, in pixels.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player'] = array( + 'label' => t('Media player URL'), + 'description' => t('The full URL for loading a media player. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:width'] = array( + 'label' => t('Media player width'), + 'description' => t('The width of the media player iframe, in pixels. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:height'] = array( + 'label' => t('Media player height'), + 'description' => t('The height of the media player iframe, in pixels. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:stream'] = array( + 'label' => t('MP4 media stream URL'), + 'description' => t('The full URL for an MP4 video (h.264) or audio (AAC) stream, takes precidence over the other media player field.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:stream:content_type'] = array( + 'label' => t('MP4 media stream MIME type'), + 'description' => t('The MIME type for the media contained in the stream URL, as defined by <a href="!url">RFC 4337</a>.', array('!url' => 'http://tools.ietf.org/rfc/rfc4337.txt')), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + return $info; +} diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module new file mode 100644 index 0000000000000000000000000000000000000000..2f835c4f2b9c10ff5cf3098605548055179eff37 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module @@ -0,0 +1,35 @@ +<?php +/** + * @file + * Primary hook implementations for Metatag: Twitter Cards. + */ + +/** + * Implements hook_ctools_plugin_api(). + */ +function metatag_twitter_cards_ctools_plugin_api($owner, $api) { + if ($owner == 'metatag' && $api == 'metatag') { + return array('version' => 1); + } +} + +/** + * Implements hook_theme(). + */ +function metatag_twitter_cards_theme() { + $info['metatag_twitter_cards'] = array( + 'render element' => 'element', + ); + + return $info; +} + +/** + * Theme callback for an twittercard meta tag. + */ +function theme_metatag_twitter_cards($variables) { + $element = &$variables['element']; + element_set_attributes($element, array('#name' => 'property', '#value' => 'content')); + unset($element['#value']); + return theme('html_tag', $variables); +} diff --git a/sites/all/modules/metatag/metatag_ui/metatag_ui.info b/sites/all/modules/metatag/metatag_ui/metatag_ui.info index 801f2c87e7b8b1485f64b21bd7ac899d4328d863..30fcb4bb214463398785feae58fdd6ccfb9b812c 100644 --- a/sites/all/modules/metatag/metatag_ui/metatag_ui.info +++ b/sites/all/modules/metatag/metatag_ui/metatag_ui.info @@ -1,14 +1,14 @@ name = Meta tag UI -description = User interface for the Meta tag API. +description = "DEPRECATED admin interface for the Meta tag API, this functionality has be merged into the main module." package = Meta tags core = 7.x dependencies[] = metatag dependencies[] = ctools hidden = TRUE -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag_ui/metatag_ui.module b/sites/all/modules/metatag/metatag_ui/metatag_ui.module new file mode 100644 index 0000000000000000000000000000000000000000..0942a6b3556b0ccd63d6bd99b76bc5f1d378c569 --- /dev/null +++ b/sites/all/modules/metatag/metatag_ui/metatag_ui.module @@ -0,0 +1,5 @@ +<?php +/** + * @file + * Empty file for the deprecated Metatag UI module. + */ diff --git a/sites/all/modules/metatag/tests/metatag_test.info b/sites/all/modules/metatag/tests/metatag_test.info index 451067fbaa435575422598278653467a376c0c06..57114e4d351c557009af0bb0dfee0dc1c7de8b27 100644 --- a/sites/all/modules/metatag/tests/metatag_test.info +++ b/sites/all/modules/metatag/tests/metatag_test.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = metatag hidden = TRUE -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/tests/metatag_test.metatag.inc b/sites/all/modules/metatag/tests/metatag_test.metatag.inc index bed4abc69c946c2f5afa68dd075e0f10dd02e481..7525f5aa3475f0b4f2d0ecfdae129f3d2aaba243 100644 --- a/sites/all/modules/metatag/tests/metatag_test.metatag.inc +++ b/sites/all/modules/metatag/tests/metatag_test.metatag.inc @@ -21,6 +21,7 @@ function metatag_test_metatag_config_default() { $config->disabled = FALSE; $config->config = array( 'description' => array('value' => 'Test foo description'), + 'abstract' => array('value' => 'Test foo abstract'), 'title' => array('value' => 'Test title'), 'test:foo' => array('value' => 'foobar'), ); diff --git a/sites/all/modules/module_filter/CHANGELOG.txt b/sites/all/modules/module_filter/CHANGELOG.txt index 7de283d3e20d31ed115723a8a14d890076b88626..04f924766035e9c0ac83acf7e7c1b075b3c09af5 100644 --- a/sites/all/modules/module_filter/CHANGELOG.txt +++ b/sites/all/modules/module_filter/CHANGELOG.txt @@ -1,3 +1,21 @@ +Module Filter 7.x-2.x, 2013-01-04 +--------------------------------- +by greenSkin: Fixed issue relating to row coloring when enabling/disabling + modules via switch due to recent fix for jQuery Update module. + + +Module Filter 7.x-2.x, 2012-11-27 +--------------------------------- +by greenSkin: Added functionality to show recently enabled/disabled modules. +#1710230 by littlekoala, greenSkin: Fixed On | Off buttons does not change + state with jquery_update() module active. + + +Module Filter 7.x-2.x, 2012-11-26 +--------------------------------- +by greenSkin: Added description to filter textfield on permissions page. + + Module Filter 7.x-2.x, 2012-11-04 --------------------------------- by greenSkin: Added filter to user permissions page. diff --git a/sites/all/modules/module_filter/js/module_filter_tab.js b/sites/all/modules/module_filter/js/module_filter_tab.js index 2653f977d50f8bbcdfd03dad581e2da2081659e4..b14e84239027878c9822ca001c5e5ce8c5fc64b4 100644 --- a/sites/all/modules/module_filter/js/module_filter_tab.js +++ b/sites/all/modules/module_filter/js/module_filter_tab.js @@ -5,6 +5,40 @@ Drupal.ModuleFilter.tabs = {}; Drupal.ModuleFilter.enabling = {}; Drupal.ModuleFilter.disabling = {}; +Drupal.ModuleFilter.jQueryIsNewer = function() { + if (Drupal.ModuleFilter.jQueryNewer == undefined) { + var v1parts = $.fn.jquery.split('.'); + var v2parts = new Array('1', '4', '4'); + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length == i) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } + + if (v1parts[i] == v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } + else { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } + } + + if (v1parts.length != v2parts.length) { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } + + Drupal.ModuleFilter.jQueryNewer = false; + } + return Drupal.ModuleFilter.jQueryNewer; +}; + Drupal.behaviors.moduleFilterTabs = { attach: function(context) { if (Drupal.settings.moduleFilter.tabs) { @@ -39,6 +73,14 @@ Drupal.behaviors.moduleFilterTabs = { summary += '<span>' + Drupal.t('No modules added within the last week.') + '</span>'; } break; + case 'recent': + name = Drupal.t('Recent'); + title = Drupal.t('Modules enabled/disabled within the last week.'); + if (Drupal.settings.moduleFilter.enabledCounts['recent'].total == 0) { + tabClass += ' disabled'; + summary += '<span>' + Drupal.t('No modules were enabled or disabled within the last week.') + '</span>'; + } + break; default: var $row = $('#' + id + '-package'); name = $.trim($row.text()); @@ -90,6 +132,7 @@ Drupal.behaviors.moduleFilterTabs = { moduleFilter.element.bind('moduleFilter:start', function() { moduleFilter.tabResults = { 'all-tab': { items: {}, count: 0 }, + 'recent-tab': { items: {}, count: 0 }, 'new-tab': { items: {}, count: 0 } }; @@ -113,6 +156,11 @@ Drupal.behaviors.moduleFilterTabs = { // All tab moduleFilter.tabResults['all-tab'].count++; + // Recent tab + if (item.element.hasClass('recent-module')) { + moduleFilter.tabResults['recent-tab'].count++; + } + // New tab if (item.element.hasClass('new-module')) { moduleFilter.tabResults['new-tab'].count++; @@ -123,7 +171,7 @@ Drupal.behaviors.moduleFilterTabs = { } if (Drupal.ModuleFilter.activeTab != undefined && Drupal.ModuleFilter.activeTab.id != 'all-tab') { - if ((Drupal.ModuleFilter.activeTab.id == 'new-tab' && !item.element.hasClass('new-module')) || (Drupal.ModuleFilter.activeTab.id != 'new-tab' && id != Drupal.ModuleFilter.activeTab.id)) { + if ((Drupal.ModuleFilter.activeTab.id == 'recent-tab' && !item.element.hasClass('recent-module')) || (Drupal.ModuleFilter.activeTab.id == 'new-tab' && !item.element.hasClass('new-module')) || (Drupal.ModuleFilter.activeTab.id != 'recent-tab' && Drupal.ModuleFilter.activeTab.id != 'new-tab' && id != Drupal.ModuleFilter.activeTab.id)) { // The item is not in the active tab, so hide it. item.element.addClass('js-hide'); } @@ -183,13 +231,19 @@ Drupal.behaviors.moduleFilterTabs = { $('td.checkbox div.form-item').hide(); $('td.checkbox').each(function(i) { var $cell = $(this); + var $checkbox = $(':checkbox', $cell); var $switch = $('.toggle-enable', $cell); $switch.removeClass('js-hide').click(function() { if (!$(this).hasClass('disabled')) { - $(':checkbox', $cell).click().change(); + if (Drupal.ModuleFilter.jQueryIsNewer()) { + $checkbox.click(); + } + else { + $checkbox.click().change(); + } } }); - $(':checkbox', $cell).change(function() { + $checkbox.click(function() { if (!$switch.hasClass('disabled')) { $switch.toggleClass('off'); } diff --git a/sites/all/modules/module_filter/module_filter.admin.inc b/sites/all/modules/module_filter/module_filter.admin.inc index cd7d9e2da766576bd0bb7ccd60abcf2fe8073561..11a2f6bdd8c9a43923cf35d2d65829820842d827 100644 --- a/sites/all/modules/module_filter/module_filter.admin.inc +++ b/sites/all/modules/module_filter/module_filter.admin.inc @@ -70,6 +70,12 @@ function module_filter_settings() { '#description' => t('This is purely cosmetic (at least for now). Displays a ON/OFF switch rather than a checkbox to enable/disable modules.<br /><strong>Modules will not actually be enabled/disabled until the form is saved.</strong>'), '#default_value' => variable_get('module_filter_use_switch', 1), ); + $form['tabs']['module_filter_track_recent_modules'] = array( + '#type' => 'checkbox', + '#title' => t('Track recently enabled/disabled modules'), + '#description' => t('Adds a "Recent" tab that displays modules that have been enabled or disabled with the last week.'), + '#default_value' => variable_get('module_filter_track_recent_modules', 1), + ); $form['update'] = array( '#type' => 'fieldset', diff --git a/sites/all/modules/module_filter/module_filter.info b/sites/all/modules/module_filter/module_filter.info index f77610b22ff84bbeab914acfe0d66bbba76c540e..970265281a18ac8be5ff18278224bde0fd3a6e66 100644 --- a/sites/all/modules/module_filter/module_filter.info +++ b/sites/all/modules/module_filter/module_filter.info @@ -17,9 +17,9 @@ files[] = js/module_filter_tab.js configure = admin/config/user-interface/modulefilter -; Information added by drupal.org packaging script on 2012-11-05 +; Information added by drupal.org packaging script on 2013-01-05 version = "7.x-2.x-dev" core = "7.x" project = "module_filter" -datestamp = "1352121182" +datestamp = "1357349173" diff --git a/sites/all/modules/module_filter/module_filter.install b/sites/all/modules/module_filter/module_filter.install index ff37531d700bc9da57ca6ece0d9d59e0069b4ee7..6aa3dcf20107baad462eafc78226c7aec30d3e0d 100644 --- a/sites/all/modules/module_filter/module_filter.install +++ b/sites/all/modules/module_filter/module_filter.install @@ -16,6 +16,7 @@ function module_filter_uninstall() { variable_del('module_filter_dynamic_save_position'); variable_del('module_filter_use_url_fragment'); variable_del('module_filter_use_switch'); + variable_del('module_filter_track_recent_modules'); variable_del('module_filter_remember_update_state'); } diff --git a/sites/all/modules/module_filter/module_filter.module b/sites/all/modules/module_filter/module_filter.module index 05d8ab88771aea68b7b728858c4fa234f9b12132..39ca0e7fa122b9e7e5cb5740c8faa98858f4ed4c 100644 --- a/sites/all/modules/module_filter/module_filter.module +++ b/sites/all/modules/module_filter/module_filter.module @@ -83,6 +83,10 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) $form['#theme'] = 'module_filter_system_modules'; $form['#submit'][] = 'module_filter_system_modules_submit_redirect'; + + if (variable_get('module_filter_track_recent_modules', 1)) { + $form['#submit'][] = 'module_filter_system_modules_submit_recent'; + } } /** @@ -91,6 +95,7 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) function module_filter_form_user_admin_permissions_alter(&$form, &$form_state) { $form['module_filter'] = array( '#type' => 'module_filter', + '#description' => t('Filter list by module. Use the query operator "perm" to filter by permission, e.g., perm:access.'), '#attached' => array( 'js' => array( drupal_get_path('module', 'module_filter') . '/js/permissions.js', @@ -163,6 +168,7 @@ function form_process_module_filter($element, &$form_state) { 'dynamicPosition' => (!module_exists('page_actions')) ? variable_get('module_filter_dynamic_save_position', 1) : FALSE, 'useURLFragment' => variable_get('module_filter_use_url_fragment', 1), 'useSwitch' => variable_get('module_filter_use_switch', 1), + 'trackRecent' => variable_get('module_filter_track_recent_modules', 1), 'rememberUpdateState' => variable_get('module_filter_remember_update_state', 0), ) ), @@ -171,6 +177,9 @@ function form_process_module_filter($element, &$form_state) { ) ) ); + if (isset($element['#description'])) { + $element['name']['#description'] = $element['#description']; + } if (variable_get('module_filter_remember_update_state', 0)) { $element['name']['#attached']['js'][] = 'misc/jquery.cookie.js'; } @@ -193,6 +202,20 @@ function module_filter_system_modules_submit_redirect($form, &$form_state) { ); } +function module_filter_system_modules_submit_recent($form, &$form_state) { + $recent_modules = variable_get('module_filter_recent_modules', array()); + + foreach ($form_state['values']['modules'] as $package => $modules) { + foreach ($modules as $key => $module) { + if ($form['modules'][$package][$key]['enable']['#default_value'] != $module['enable']) { + $recent_modules[$key] = REQUEST_TIME; + } + } + } + + variable_set('module_filter_recent_modules', $recent_modules); +} + function module_filter_new_modules() { // Get current list of modules. $files = system_rebuild_module_data(); @@ -222,3 +245,7 @@ function module_filter_get_id($text) { $id = preg_replace('/([^a-z0-9]+)/', '-', $id); return trim($id, '-'); } + +function module_filter_recent_filter($var) { + return (!($var < REQUEST_TIME - 60*60*24*7)); +} diff --git a/sites/all/modules/module_filter/module_filter.theme.inc b/sites/all/modules/module_filter/module_filter.theme.inc index 22f764dc6df884a4953833832cf44819159a44ca..ac7e9d9960a4188df09fe6ff4ee1aeb7e87818ce 100644 --- a/sites/all/modules/module_filter/module_filter.theme.inc +++ b/sites/all/modules/module_filter/module_filter.theme.inc @@ -76,11 +76,22 @@ function theme_module_filter_system_modules_tabs($variables) { t('Version'), t('Description') ); - $package_ids = array('all', 'new'); - $enabled['all'] = $enabled['new'] = array(); + $package_ids = array('all'); + $enabled['all'] = array(); + + if (variable_get('module_filter_track_recent_modules', 1)) { + $recent_modules = array_filter(variable_get('module_filter_recent_modules', array()), 'module_filter_recent_filter'); + // Save the filtered results. + variable_get('module_filter_recent_modules', $recent_modules); + + $package_ids[] = 'recent'; + $enabled['recent'] = array(); + } // Determine what modules are new (within a week). $new_modules = module_filter_new_modules(); + $package_ids[] = 'new'; + $enabled['new'] = array(); $rows = array(); $flip = array('even' => 'odd', 'odd' => 'even'); @@ -99,6 +110,9 @@ function theme_module_filter_system_modules_tabs($variables) { $is_enabled = isset($module['enable']['#default_value']) ? $module['enable']['#default_value'] : ''; $enabled['all'][] = $enabled[$package_id][] = $is_enabled; + if (isset($recent_modules[$key])) { + $enabled['recent'][] = $is_enabled; + } if (isset($new_modules[$key])) { $enabled['new'][] = $is_enabled; } @@ -136,6 +150,9 @@ function theme_module_filter_system_modules_tabs($variables) { $row[] = array('data' => $description, 'class' => array('description')); $class = array(module_filter_get_id($package) . '-tab', 'module', $stripe); + if (isset($recent_modules[$key])) { + $class[] = 'recent-module'; + } if (isset($new_modules[$key])) { $class[] = 'new-module'; } diff --git a/sites/all/modules/options_element/options_element.inc b/sites/all/modules/options_element/options_element.inc index d6a90cc2e0c290ab96162a74c26edfacb2d03930..ed64743503c62a51bfe991da6e8cdd43452e09f0 100644 --- a/sites/all/modules/options_element/options_element.inc +++ b/sites/all/modules/options_element/options_element.inc @@ -133,13 +133,13 @@ function _form_options_expand($element) { '#rows' => 5, '#required' => isset($element['#required']) ? $element['#required'] : FALSE, '#description' => t('List options one option per line.'), - '#attributes' => $element['#disabled'] ? array('readonly' => 'readonly') : array(), + '#attributes' => $element['#options_readonly'] ? array('readonly' => 'readonly') : array(), '#wysiwyg' => FALSE, // Prevent CKeditor from trying to hijack. ); // If validation fails, reload the user's text even if it's not valid. - if (isset($element['#value']['text'])) { - $element['options_field']['#value'] = $element['#value']['text']; + if (isset($element['#value']['options_text'])) { + $element['options_field']['#value'] = $element['#value']['options_text']; } // Most of the time, we'll be converting the options array into the text. else { @@ -195,7 +195,8 @@ function _form_options_expand($element) { * @see form_options_validate() */ function _form_options_validate($element, &$form_state) { - // Convert text to an array of options. + // Even though we already have the converted options in #value['options'], run + // the conversion again to check for duplicates in the user-defined list. $duplicates = array(); $options = form_options_from_text($element['#value']['options_text'], $element['#key_type'], empty($element['#optgroups']), $duplicates); diff --git a/sites/all/modules/options_element/options_element.info b/sites/all/modules/options_element/options_element.info index d370e45105f5455b32b48763475e679a138cea55..9f566f94e64bfe51cc735166859659ee9373019d 100644 --- a/sites/all/modules/options_element/options_element.info +++ b/sites/all/modules/options_element/options_element.info @@ -2,9 +2,9 @@ name = Options element description = A custom form element for entering the options in select lists, radios, or checkboxes. core = 7.x -; Information added by drupal.org packaging script on 2012-03-17 -version = "7.x-1.7" +; Information added by drupal.org packaging script on 2012-09-13 +version = "7.x-1.8" core = "7.x" project = "options_element" -datestamp = "1332018945" +datestamp = "1347551745" diff --git a/sites/all/modules/options_element/options_element.js b/sites/all/modules/options_element/options_element.js index 0d71ae7fc76ea453e2cc77e560369cae7146195b..11ccb6a78820a14573537141870b2bc26e66e4e8 100644 --- a/sites/all/modules/options_element/options_element.js +++ b/sites/all/modules/options_element/options_element.js @@ -37,7 +37,8 @@ Drupal.optionsElement = function(element) { this.keyType = element.className.replace(/^.*?options-key-type-([a-z]+).*?$/, '$1'); this.customKeys = Boolean(element.className.match(/options-key-custom/)); this.identifier = this.manualOptionsElement.id + '-widget'; - this.enabled = $(this.manualOptionsElement).attr('readonly') == ''; + // jQuery 1.6 API change: http://api.jquery.com/prop/ + this.enabled = $.fn.prop ? !$(this.manualOptionsElement).prop('readonly') : !$(this.manualOptionsElement).attr('readonly'); this.defaultValuePattern = $(element).find('input.default-value-pattern').val(); if (this.defaultValuePattern) { diff --git a/sites/all/modules/options_element/options_element.module b/sites/all/modules/options_element/options_element.module index db007de545e2d5b186770925a770bb8bd0730cf7..d777164bca1495c07d6d4257065cc0343483fdba 100644 --- a/sites/all/modules/options_element/options_element.module +++ b/sites/all/modules/options_element/options_element.module @@ -12,7 +12,7 @@ * * The 'options' form element type is useful when collecting a series of * values in a list. The values within the list may optionally have unique - * keys, such as that in a array structure. In addition, a default choice + * keys, such as that in an array structure. In addition, a default choice * (or several default choices) may be selected by the user. * * @code @@ -89,13 +89,13 @@ function options_element_element_info() { '#optgroups' => TRUE, '#multiple' => FALSE, '#options' => array(), + '#options_readonly' => FALSE, '#key_type' => 'mixed', '#key_type_toggle' => NULL, '#key_type_toggled' => FALSE, '#default_value_allowed' => TRUE, '#default_value_pattern' => '', '#element_validate' => array('form_options_validate'), - '#disabled' => FALSE, ); return $type; diff --git a/sites/all/modules/pagepreview/LICENSE.txt b/sites/all/modules/pagepreview/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/pagepreview/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/pagepreview/pagepreview.css b/sites/all/modules/pagepreview/pagepreview.css new file mode 100644 index 0000000000000000000000000000000000000000..a780d28cd2430c3f9d511a705f7601cac3688145 --- /dev/null +++ b/sites/all/modules/pagepreview/pagepreview.css @@ -0,0 +1,6 @@ +iframe.pagepreview-preview { + border: 1px solid #aaa; + width: 100%; + height: 480px; +} + diff --git a/sites/all/modules/pagepreview/pagepreview.info b/sites/all/modules/pagepreview/pagepreview.info index 6243f29e6c646eb03ff89bb3987bd51fc4d76ff8..cce5063dce4a656420aa76be1b758aad7b54f3a8 100644 --- a/sites/all/modules/pagepreview/pagepreview.info +++ b/sites/all/modules/pagepreview/pagepreview.info @@ -1,4 +1,10 @@ -; $Id$ name = Page Preview description = Allows node previews to be viewed as fully-rendered pages. core = 7.x + +; Information added by drupal.org packaging script on 2012-11-06 +version = "7.x-1.x-dev" +core = "7.x" +project = "pagepreview" +datestamp = "1352208035" + diff --git a/sites/all/modules/pagepreview/pagepreview.install b/sites/all/modules/pagepreview/pagepreview.install index 7e614c76410fba2eb92442623e2fa0111464fbb0..8a9fadd6b5306e96cc60f734c93732f9e50c1216 100644 --- a/sites/all/modules/pagepreview/pagepreview.install +++ b/sites/all/modules/pagepreview/pagepreview.install @@ -1,21 +1,17 @@ <?php -// $Id$ - /** - * @file - * Install functions for the Page Preview module. + * Implementation of hook_schema(). */ +function pagepreview_schema() { + $schema['cache_pagepreview'] = drupal_get_schema_unprocessed('system', 'cache'); + + return $schema; +} /** - * Implements hook_install(). - * - * Set module weight to so that pagepreview_init() can run early. + * Create a dedicated cache table. */ -function pagepreview_install() { - db_update('system') - ->fields(array( - 'weight' => -1, - )) - ->condition('name', 'pagepreview', '=') - ->execute(); +function pagepreview_update_7100() { + $schema = pagepreview_schema(); + db_create_table('cache_pagepreview', $schema['cache_pagepreview']); } diff --git a/sites/all/modules/pagepreview/pagepreview.module b/sites/all/modules/pagepreview/pagepreview.module index 5f9a274dbc57e56d6e4c440ccc5b0d37e9b15aa5..1fa69197217e6fe974a114c5961050989bc535e3 100644 --- a/sites/all/modules/pagepreview/pagepreview.module +++ b/sites/all/modules/pagepreview/pagepreview.module @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -13,7 +12,7 @@ function pagepreview_menu() { $items['pagepreview/%'] = array( 'title' => 'Page Preview', 'description' => 'Menu callback for rendering a full-page preview.', - 'page callback' => 'pagepreview_get_preview', + 'page callback' => 'pagepreview_deliver_page', 'page arguments' => array(1), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, @@ -27,7 +26,8 @@ function pagepreview_menu() { * Replaces the default submit callback for the "Preview" button. */ function pagepreview_form_alter(&$form, &$form_state, $form_id) { - if ($form['#id'] == 'page-node-form') { + if (!empty($form['#node_edit_form'])) { + drupal_add_css(drupal_get_path('module', 'pagepreview') . '/pagepreview.css'); if ($form['actions']['preview']) { $form['actions']['preview']['#submit'] = array('pagepreview_node_form_build_preview'); } @@ -42,7 +42,7 @@ function pagepreview_node_form_build_preview($form, &$form_state) { $node->pagepreview = TRUE; // Get the expected URL alias from Pathauto, if applicable. - if (module_exists('pathauto') && $node->path['pathauto']) { + if (module_exists('pathauto') && (!isset($node->path['pathauto']) || $node->path['pathauto'])) { module_load_include('inc', 'pathauto'); $src = "node/" . $node->nid; $alias = pathauto_create_alias('node', 'return', $src, array('node' => $node), $node->type, $node->language); @@ -52,61 +52,31 @@ function pagepreview_node_form_build_preview($form, &$form_state) { } // Cache the temporary node and active the preview area of the node form. - cache_set('pagepreview:' . $form['form_token']['#default_value'], $node, 'cache_page', CACHE_TEMPORARY); + $preview_id = md5(json_encode($form_state['values']) . mt_rand()); + cache_set($preview_id, $node, 'cache_pagepreview', CACHE_TEMPORARY); - $form_state['node_preview'] = '<iframe class="pagepreview-preview" style="width:100%; height: 480px;" src="' . base_path() . 'pagepreview/' . $form['#token'] . '"></iframe>'; + $form_state['node_preview'] = '<iframe class="pagepreview-preview" src="' . base_path() . 'pagepreview/' . $preview_id . '"></iframe>'; $form_state['rebuild'] = TRUE; } -/** - * Implements hook_init(). - * - * We need drupal_is_front_page() to reflect the page being previewed, not the - * path of the pagepreview callback. Since the result of drupal_is_front_page() - * is statically cached, we have to call it first and trick it. - */ -function pagepreview_init() { - // We only want to do this on pagepreview requests. - $path = $_GET['q']; - $parts = explode('/', $path); - if ($parts[0] == 'pagepreview' && isset($parts[1])) { - // Get the cached temporary node. - $form_token = drupal_get_token($parts[1]); - $cache = cache_get('pagepreview:' . $form_token, 'cache_page'); - - // If we can't find a cached node, might as well quit here. - if (!$cache) { - print t('There was a problem generating the preview. Please review the form for error and try again.'); - exit; - } - - // Switch $_GET['q'] to the expected path, call drupal_is_front_page() to - // set the static cache, the switch $_GET['q'] back to the original. - $node = $cache->data; - if ($node->nid) { - $_GET['q'] = 'node/' . $node->nid; - } - drupal_is_front_page(); - $_GET['q'] = $path; - - // Meanwhile, don't allow the preview result to be cached. - $GLOBALS['conf']['cache'] = FALSE; - } -} - /** * Menu callback for "pagepreview/%". * * Directly prints a rendered page based on the cached temporary node. * - * @param $token - * The value of $form['#token']. This is generally the form ID. + * @param string $preview_id + * The cache key for the object to be retrieved. */ -function pagepreview_get_preview($token) { +function pagepreview_deliver_page($preview_id) { // Get the cached temporary node. - $form_token = drupal_get_token($token); - $cache = cache_get('pagepreview:' . $form_token, 'cache_page'); - $node = $cache->data; + $node = pagepreview_cache_get($preview_id); + + // If we don't have a valid node for whatever reason, quit here. + if (!$node) { + drupal_exit(); + } + + // Change the preview's page title. drupal_set_title($node->title); // Overrides $_GET['q'] so that other elements on the page can react to the @@ -117,26 +87,44 @@ function pagepreview_get_preview($token) { elseif (!empty($node->path['alias'])) { $_GET['q'] = trim($node->path['alias'], '/'); } - elseif (!empty($node->path['old_alias'])) { - $_GET['q'] = trim($node->path['old_alias'], '/'); - } - $preview = pagepreview_render_preview($node); + // Allow other modules to alter the node or execute code before building the + // preview. + drupal_alter('pagepreview_node', $node); + + $preview = pagepreview_build_preview($node); // Switch to the anonymous user for page rendering. // TODO: make this configurable. global $user; - $original_user = $user; drupal_save_session(FALSE); $user = user_load(0); // Suppress fancy stuff like admin and admin_menu.module for the preview. module_invoke_all('suppress'); - drupal_deliver_page(drupal_render($preview)); + // Add JS within the preview to prevent clicking through to links. + drupal_add_js(drupal_get_path('module', 'pagepreview') . '/pagepreview.preview.js'); + + // Deliver the page output. + drupal_deliver_page($preview); - $user = $original_user; - drupal_save_session(TRUE); + // Remove the cached preview. + cache_clear_all($preview_id, 'cache_pagepreview'); +} + +/** + * Return a preview object from cache. + * + * @param string $preview_id + * The cache key for the object to be retrieved. + * + * @return object + * The cached object, or FALSE if none was found. + */ +function pagepreview_cache_get($preview_id) { + $cache = cache_get($preview_id, 'cache_pagepreview'); + return ($cache) ? $cache->data : FALSE; } /** @@ -147,7 +135,7 @@ function pagepreview_get_preview($token) { * * @see theme_node_preview() */ -function pagepreview_render_preview($node) { +function pagepreview_build_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. @@ -177,15 +165,26 @@ function pagepreview_render_preview($node) { $node->in_preview = TRUE; // If enabled, allow page_manager.module to handle node rendering. if (module_exists('page_manager')) { - module_load_include('inc', 'page_manager', 'plugins/tasks/node_view'); - $output = page_manager_node_view_page($node); + // Load my task plugin + $task = page_manager_get_task('node_view'); + + // Load the node into a context. + ctools_include('context'); + ctools_include('context-task-handler'); + $contexts = ctools_context_handler_get_task_contexts($task, '', array($node)); + + $output = ctools_context_handler_render($task, '', $contexts, array($node->nid)); + // Page manager is not handeling node_view + if ($output === FALSE) { + $output = node_view($node, 'full'); + } } else { $output = node_view($node, 'full'); } unset($node->in_preview); // Rather than the default t('Preview') allow user to see more acurate rendering - drupal_set_title($node->title, PASS_THROUGH); + drupal_set_title($node->title); } return $output; diff --git a/sites/all/modules/pagepreview/pagepreview.preview.js b/sites/all/modules/pagepreview/pagepreview.preview.js new file mode 100644 index 0000000000000000000000000000000000000000..4f56037dfb6b8d352271956dd336b7cd8329f39a --- /dev/null +++ b/sites/all/modules/pagepreview/pagepreview.preview.js @@ -0,0 +1,19 @@ +(function($){ + // Prevent links, forms, and inputs from unloading the preview page. + $(document).delegate('a', 'click', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('form', 'submit', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('input', 'mousedown', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('input', 'click', function(ev){ + ev.preventDefault(); + return false; + }); +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/pathauto/pathauto.info b/sites/all/modules/pathauto/pathauto.info index 09c3d7cf673cb1f4d5a64f53b9e5ea014d916c77..1488faa0e9a2d59be402cd3d36cc15e450f8d9db 100644 --- a/sites/all/modules/pathauto/pathauto.info +++ b/sites/all/modules/pathauto/pathauto.info @@ -7,9 +7,9 @@ files[] = pathauto.test configure = admin/config/search/path/patterns recommends[] = redirect -; Information added by drupal.org packaging script on 2012-05-13 -version = "7.x-1.1" +; Information added by drupal.org packaging script on 2012-08-09 +version = "7.x-1.2" core = "7.x" project = "pathauto" -datestamp = "1336950382" +datestamp = "1344525185" diff --git a/sites/all/modules/pathauto/pathauto.install b/sites/all/modules/pathauto/pathauto.install index dcca163ec2f58eba4b056b03946b8c145081e882..cd5211cb2086132b3a01d08942e1d77f8c5d499e 100644 --- a/sites/all/modules/pathauto/pathauto.install +++ b/sites/all/modules/pathauto/pathauto.install @@ -33,7 +33,7 @@ function pathauto_install() { * Implements hook_uninstall(). */ function pathauto_uninstall() { - // Delete all the pathauto variables and then clear the variable cache + // Delete all the pathauto variables and then clear the variable cache. db_query("DELETE FROM {variable} WHERE name LIKE 'pathauto_%'"); cache_clear_all('variables', 'cache'); } diff --git a/sites/all/modules/pathauto/pathauto.module b/sites/all/modules/pathauto/pathauto.module index 1875caae7581d890ec8f1245d789fd28ddfc8a97..dffc29282e723918d59a35d432bc56d73283c45c 100644 --- a/sites/all/modules/pathauto/pathauto.module +++ b/sites/all/modules/pathauto/pathauto.module @@ -389,6 +389,34 @@ function pathauto_action_info() { return $info; } +/** + * Returns the language code of the given entity. + * + * Backward compatibility layer to ensure that installations running an older + * version of core where entity_language() is not avilable do not break. + * + * @param string $entity_type + * An entity type. + * @param object $entity + * An entity object. + * @param bool $check_language_property + * A boolean if TRUE, will attempt to fetch the language code from + * $entity->language if the entity_language() function failed or does not + * exist. Default is TRUE. + */ +function pathauto_entity_language($entity_type, $entity, $check_language_property = TRUE) { + $langcode = NULL; + + if (function_exists('entity_language')) { + $langcode = entity_language($entity_type, $entity); + } + elseif ($check_language_property && !empty($entity->language)) { + $langcode = $entity->language; + } + + return !empty($langcode) ? $langcode : LANGUAGE_NONE; +} + if (!function_exists('path_field_extra_fields')) { /** * Implements hook_field_extra_fields() on behalf of path.module. @@ -461,7 +489,8 @@ function pathauto_node_delete($node) { */ function pathauto_form_node_form_alter(&$form, &$form_state) { $node = $form_state['node']; - pathauto_field_attach_form('node', $node, $form, $form_state, $node->language); + $langcode = pathauto_entity_language('node', $node); + pathauto_field_attach_form('node', $node, $form, $form_state, $langcode); } /** @@ -492,9 +521,7 @@ function pathauto_node_update_alias(stdClass $node, $op, array $options = array( return; } - $options += array( - 'language' => !empty($node->language) ? $node->language : LANGUAGE_NONE, - ); + $options += array('language' => pathauto_entity_language('node', $node)); // Skip processing if the node has no pattern. if (!pathauto_pattern_load_by_entity('node', $node->type, $options['language'])) { @@ -574,7 +601,7 @@ function pathauto_taxonomy_term_delete($term) { */ function pathauto_form_taxonomy_form_term_alter(&$form, $form_state) { $term = $form_state['term']; - $langcode = !empty($term->language) ? $term->language : LANGUAGE_NONE; + $langcode = pathauto_entity_language('taxonomy_term', $term); pathauto_field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode); } @@ -594,11 +621,6 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options return; } - $options += array( - 'alias children' => FALSE, - 'language' => !empty($term->language) ? $term->language : LANGUAGE_NONE, - ); - $module = 'taxonomy_term'; if ($term->vid == variable_get('forum_nav_vocabulary', '')) { if (module_exists('forum')) { @@ -615,6 +637,11 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options $term->vocabulary_machine_name = $vocabulary->machine_name; } + $options += array( + 'alias children' => FALSE, + 'language' => pathauto_entity_language('taxonomy_term', $term), + ); + // Skip processing if the term has no pattern. if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) { return; @@ -727,7 +754,9 @@ function pathauto_user_update_alias(stdClass $account, $op, array $options = arr $options += array( 'alias blog' => module_exists('blog'), - 'language' => LANGUAGE_NONE, + // $user->language is not the user entity language, thus we need to skip + // the property fallback check. + 'language' => pathauto_entity_language('user', $account, FALSE), ); // Skip processing if the account has no pattern. @@ -772,8 +801,8 @@ function pathauto_user_update_alias_multiple(array $uids, $op, array $options = /** * Update action wrapper for pathauto_user_update_alias(). */ -function pathauto_user_update_action($user, $context = array()) { - pathauto_user_update_alias($user, 'bulkupdate', array('message' => TRUE)); +function pathauto_user_update_action($account, $context = array()) { + pathauto_user_update_alias($account, 'bulkupdate', array('message' => TRUE)); } /** diff --git a/sites/all/modules/redirect/LICENSE.txt b/sites/all/modules/redirect/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/redirect/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/redirect/README.txt b/sites/all/modules/redirect/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ea2f179aaf1510c8ee12a9b3cb7833109baf8eb --- /dev/null +++ b/sites/all/modules/redirect/README.txt @@ -0,0 +1,3 @@ + +This is the new module home for a unified redirection API (also replaces +path_redirect and globalredirect). diff --git a/sites/all/modules/redirect/redirect.admin.inc b/sites/all/modules/redirect/redirect.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..a6610d5e2f09ae438220c1f73a43fdae4a56fd6b --- /dev/null +++ b/sites/all/modules/redirect/redirect.admin.inc @@ -0,0 +1,922 @@ +<?php + +/** + * @file + * Administrative page callbacks for the redirect module. + */ + +function redirect_list_form($form, &$form_state) { + $form['#operations'] = redirect_get_redirect_operations(); + if (isset($form_state['values']['operation']) && empty($form_state['values']['confirm'])) { + return redirect_list_form_operations_confirm_form($form, $form_state, $form_state['values']['operation'], array_filter($form_state['values']['rids'])); + } + + $destination = drupal_get_destination(); + $default_status_code = variable_get('redirect_default_status_code', 301); + + // Set up the header. + $header = array( + 'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'), + 'redirect' => array('data' => t('To'), 'field' => 'redirect'), + 'status_code' => array('data' => t('Type'), 'field' => 'status_code'), + 'language' => array('data' => t('Language'), 'field' => 'language'), + 'count' => array('data' => t('Count'), 'field' => 'count'), + 'access' => array('data' => t('Last accessed'), 'field' => 'access'), + 'operations' => array('data' => t('Operations')), + ); + + // Do not include the language column if locale is disabled. + if (!module_exists('locale')) { + unset($header['language']); + } + + // Get filter keys and add the filter form. + $keys = func_get_args(); + $keys = array_splice($keys, 2); // Offset the $form and $form_state parameters. + $keys = implode('/', $keys); + $form['redirect_list_filter_form'] = redirect_list_filter_form($keys); + + // Build the 'Update options' form. + $form['operations'] = array( + '#type' => 'fieldset', + '#title' => t('Update options'), + '#prefix' => '<div class="container-inline">', + '#suffix' => '</div>', + '#attributes' => array( + 'class' => array('redirect-list-operations'), + ), + ); + $operations = array(); + foreach ($form['#operations'] as $key => $operation) { + $operations[$key] = $operation['action']; + } + $form['operations']['operation'] = array( + '#type' => 'select', + '#options' => $operations, + '#default_value' => 'delete', + ); + $form['operations']['submit'] = array( + '#type' => 'submit', + '#value' => t('Update'), + '#validate' => array('redirect_list_form_operations_validate'), + '#submit' => array('redirect_list_form_operations_submit'), + ); + + // Building the SQL query and load the redirects. + $query = db_select('redirect', 'r')->extend('TableSort')->extend('PagerDefault'); + $query->addField('r', 'rid'); + $query->condition('r.type', 'redirect'); + $query->orderByHeader($header); + $query->limit(50); + $query->addTag('redirect_list'); + $query->addTag('redirect_access'); + redirect_build_filter_query($query, array('source', 'redirect'), $keys); + $rids = $query->execute()->fetchCol(); + $redirects = redirect_load_multiple($rids); + + $rows = array(); + foreach ($redirects as $rid => $redirect) { + $row = array(); + $redirect->source_options = array_merge($redirect->source_options, array('alias' => TRUE, 'language' => redirect_language_load($redirect->language))); + $source_url = redirect_url($redirect->source, $redirect->source_options); + $redirect_url = redirect_url($redirect->redirect, array_merge($redirect->redirect_options, array('alias' => TRUE))); + drupal_alter('redirect_url', $redirect->source, $redirect->source_options); + drupal_alter('redirect_url', $redirect->redirect, $redirect->redirect_options); + $row['source'] = l($source_url, $redirect->source, $redirect->source_options); + $row['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options); + $row['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code)); + $row['language'] = module_invoke('locale', 'language_name', $redirect->language); + $row['count'] = $redirect->count; + if ($redirect->access) { + $row['access'] = array( + 'data' => t('!interval ago', array('!interval' => format_interval(REQUEST_TIME - $redirect->access))), + 'title' => t('Last accessed on @date', array('@date' => format_date($redirect->access))), + ); + } + else { + $row['access'] = t('Never'); + } + + // Mark redirects that override existing paths with a warning in the table. + if (drupal_valid_path($redirect->source)) { + $row['#attributes']['class'][] = 'warning'; + $row['#attributes']['title'] = t('This redirect overrides an existing internal path.'); + } + + $operations = array(); + if (redirect_access('update', $redirect)) { + $operations['edit'] = array( + 'title' => t('Edit'), + 'href' => 'admin/config/search/redirect/edit/' . $rid, + 'query' => $destination, + ); + } + if (redirect_access('delete', $redirect)) { + $operations['delete'] = array( + 'title' => t('Delete'), + 'href' => 'admin/config/search/redirect/delete/' . $rid, + 'query' => $destination, + ); + } + $row['operations'] = array( + 'data' => array( + '#theme' => 'links', + '#links' => $operations, + '#attributes' => array('class' => array('links', 'inline', 'nowrap')), + ), + ); + + $rows[$rid] = $row; + } + + $form['rids'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $rows, + '#empty' => t('No URL redirects available.'), + '#attributes' => array( + 'class' => array('redirect-list-tableselect'), + ), + '#attached' => array( + 'js' => array( + drupal_get_path('module', 'redirect') . '/redirect.admin.js', + ), + ), + ); + if (redirect_access('create', 'redirect')) { + $form['rids']['#empty'] .= ' ' . l(t('Add URL redirect.'), 'admin/config/search/redirect/add', array('query' => $destination)); + } + $form['pager'] = array('#theme' => 'pager'); + return $form; +} + +/** + * Return a form to filter URL redirects. + * + * @see redirect_list_filter_form_submit() + * + * @ingroup forms + */ +function redirect_list_filter_form($keys = '') { + $form['#attributes'] = array('class' => array('search-form')); + $form['basic'] = array( + '#type' => 'fieldset', + '#title' => t('Filter redirects'), + '#attributes' => array('class' => array('container-inline')), + ); + $form['basic']['filter'] = array( + '#type' => 'textfield', + '#title' => '', + '#default_value' => $keys, + '#maxlength' => 128, + '#size' => 25, + ); + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + '#submit' => array('redirect_list_filter_form_submit'), + ); + if ($keys) { + $form['basic']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + '#submit' => array('redirect_list_filter_form_reset'), + ); + } + return $form; +} + +/** + * Process filter form submission when the Filter button is pressed. + */ +function redirect_list_filter_form_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/redirect/list/' . trim($form_state['values']['filter']); +} + +/** + * Process filter form submission when the Reset button is pressed. + */ +function redirect_list_filter_form_reset($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/redirect'; +} + +/** + * Extends a query object for URL redirect filters. + * + * @param $query + * Query object that should be filtered. + * @param $keys + * The filter string to use. + */ +function redirect_build_filter_query(QueryAlterableInterface $query, array $fields, $keys = '') { + if ($keys && $fields) { + // Replace wildcards with PDO wildcards. + $conditions = db_or(); + $wildcard = '%' . trim(preg_replace('!\*+!', '%', db_like($keys)), '%') . '%'; + foreach ($fields as $field) { + $conditions->condition($field, $wildcard, 'LIKE'); + } + $query->condition($conditions); + } +} + +/** + * Validate redirect_list_form form submissions. + * + * Check if any redirects have been selected to perform the chosen + * 'Update option' on. + */ +function redirect_list_form_operations_validate($form, &$form_state) { + // Error if there are no redirects selected. + if (!is_array($form_state['values']['rids']) || !count(array_filter($form_state['values']['rids']))) { + form_set_error('', t('No redirects selected.')); + } +} + +/** + * Process redirect_list_form form submissions. + * + * Execute the chosen 'Update option' on the selected redirects. + */ +function redirect_list_form_operations_submit($form, &$form_state) { + $operations = $form['#operations']; + $operation = $operations[$form_state['values']['operation']]; + + // Filter out unchecked redirects + $rids = array_filter($form_state['values']['rids']); + + if (!empty($operation['confirm']) && empty($form_state['values']['confirm'])) { + // We need to rebuild the form to go to a second step. For example, to + // show the confirmation form for the deletion of redirects. + $form_state['rebuild'] = TRUE; + } + else { + $function = $operation['callback']; + + // Add in callback arguments if present. + if (isset($operation['callback arguments'])) { + $args = array_merge(array($rids), $operation['callback arguments']); + } + else { + $args = array($rids); + } + call_user_func_array($function, $args); + + $count = count($form_state['values']['rids']); + watchdog('redirect', '@action @count redirects.', array('@action' => $operation['action_past'], '@count' => $count)); + drupal_set_message(format_plural(count($rids), '@action @count redirect.', '@action @count redirects.', array('@action' => $operation['action_past'], '@count' => $count))); + } +} + +function redirect_list_form_operations_confirm_form($form, &$form_state, $operation, $rids) { + $operations = $form['#operations']; + $operation = $operations[$form_state['values']['operation']]; + + $form['rids_list'] = array( + '#theme' => 'item_list', + '#items' => array(), + ); + $form['rids'] = array( + '#type' => 'value', + '#value' => $rids, + ); + + $redirects = redirect_load_multiple($rids); + foreach ($redirects as $rid => $redirect) { + $form['rids_list']['#items'][$rid] = check_plain(redirect_url($redirect->source, $redirect->source_options)); + } + + $form['operation'] = array('#type' => 'hidden', '#value' => $form_state['values']['operation']); + $form['#submit'][] = 'redirect_list_form_operations_submit'; + $confirm_question = format_plural(count($rids), 'Are you sure you want to @action this redirect?', 'Are you sure you want to @action these redirects?', array('@action' => drupal_strtolower($operation['action']))); + + return confirm_form( + $form, + $confirm_question, + 'admin/config/search/redirect', // @todo This does not redirect back to filtered page. + t('This action cannot be undone.'), + $operation['action'], + t('Cancel') + ); +} + +/** + * Form builder to add or edit an URL redirect. + * + * @see redirect_element_validate_source() + * @see redirect_element_validate_redirect() + * @see redirect_edit_form_validate() + * @see redirect_edit_form_submit() + * + * @ingroup forms + */ +function redirect_edit_form($form, &$form_state, $redirect = NULL) { + if (!isset($redirect)) { + $redirect = new stdClass(); + } + + // Merge default values. + redirect_object_prepare($redirect, array( + 'source' => isset($_GET['source']) ? urldecode($_GET['source']) : '', + 'source_options' => isset($_GET['source_options']) ? drupal_get_query_array($_GET['source_options']) : array(), + 'redirect' => isset($_GET['redirect']) ? urldecode($_GET['redirect']) : '', + 'redirect_options' => isset($_GET['redirect_options']) ? drupal_get_query_array($_GET['redirect_options']) : array(), + 'language' => isset($_GET['language']) ? urldecode($_GET['language']) : LANGUAGE_NONE, + )); + + $form['rid'] = array( + '#type' => 'value', + '#value' => $redirect->rid, + ); + $form['type'] = array( + '#type' => 'value', + '#value' => $redirect->type, + ); + $form['hash'] = array( + '#type' => 'value', + '#value' => $redirect->hash, + ); + + $form['source'] = array( + '#type' => 'textfield', + '#title' => t('From'), + '#description' => t("Enter an internal Drupal path or path alias to redirect (e.g. %example1 or %example2). Fragment anchors (e.g. %anchor) are <strong>not</strong> allowed.", array('%example1' => 'node/123', '%example2' => 'taxonomy/term/123', '%anchor' => '#anchor')), + '#maxlength' => 560, + '#default_value' => $redirect->rid || $redirect->source ? redirect_url($redirect->source, $redirect->source_options + array('alter' => FALSE)) : '', + '#required' => TRUE, + '#field_prefix' => $GLOBALS['base_url'] . '/' . (variable_get('clean_url', 0) ? '' : '?q='), + '#element_validate' => array('redirect_element_validate_source'), + ); + $form['source_options'] = array( + '#type' => 'value', + '#value' => $redirect->source_options, + '#tree' => TRUE, + ); + $form['redirect'] = array( + '#type' => 'textfield', + '#title' => t('To'), + '#maxlength' => 560, + '#default_value' => $redirect->rid || $redirect->redirect ? redirect_url($redirect->redirect, $redirect->redirect_options, TRUE) : '', + '#required' => TRUE, + '#description' => t('Enter an internal Drupal path, path alias, or complete external URL (like http://example.com/) to redirect to. Use %front to redirect to the front page.', array('%front' => '<front>')), + '#element_validate' => array('redirect_element_validate_redirect'), + ); + $form['redirect_options'] = array( + '#type' => 'value', + '#value' => $redirect->redirect_options, + '#tree' => TRUE, + ); + + // This will be a hidden value unless locale module is enabled. + $form['language'] = array( + '#type' => 'value', + '#value' => $redirect->language, + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced options'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['advanced']['status_code'] = array( + '#type' => 'select', + '#title' => t('Redirect status'), + '#description' => t('You can find more information about HTTP redirect status codes at <a href="@status-codes">@status-codes</a>.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')), + '#default_value' => $redirect->status_code, + '#options' => array(0 => t('Default (@default)', array('@default' => variable_get('redirect_default_status_code', 301)))) + redirect_status_code_options(), + ); + + $form['override'] = array( + '#type' => 'checkbox', + '#title' => t('I understand the following warnings and would like to proceed with saving this URL redirect'), + '#default_value' => FALSE, + '#access' => FALSE, + '#required' => FALSE, + '#weight' => -100, + '#prefix' => '<div class="messages warning">', + '#suffix' => '</div>', + ); + if (!empty($form_state['storage']['override_messages'])) { + $form['override']['#access'] = TRUE; + //$form['override']['#required'] = TRUE; + $form['override']['#description'] = theme('item_list', array('items' => $form_state['storage']['override_messages'])); + // Reset the messages. + $form_state['storage']['override_messages'] = array(); + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/redirect', + ); + + return $form; +} + +/** + * Element validate handler; validate the source of an URL redirect. + * + * @see redirect_edit_form() + */ +function redirect_element_validate_source($element, &$form_state) { + $value = &$element['#value']; + + // Check that the source contains no URL fragment. + if (strpos($value, '#') !== FALSE) { + form_error($element, t('The source path cannot contain an URL fragment anchor.')); + } + + _redirect_extract_url_options($element, $form_state); + + // Disallow redirections from the frontpage. + if ($value === '<front>') { + form_error($element, t('The source path cannot be the front page.')); + } + + // Cannot create redirects for valid paths. + if (empty($form_state['values']['override'])) { + $menu_item = menu_get_item($value); + if ($menu_item && $menu_item['page_callback'] != 'redirect_redirect' && $value == $menu_item['path']) { + $form_state['storage']['override_messages']['valid-path'] = t('The source path %path is likely a valid path. It is preferred to <a href="@url-alias">create URL aliases</a> for existing paths rather than redirects.', array('%path' => $value, '@url-alias' => url('admin/config/search/path/add'))); + $form_state['rebuild'] = TRUE; + } + } + + return $element; +} + +/** + * Element validate handler; validate the redirect of an URL redirect. + * + * @see redirect_edit_form() + */ +function redirect_element_validate_redirect($element, &$form_state) { + $value = &$element['#value']; + _redirect_extract_url_options($element, $form_state); + $value = &$form_state['values']['redirect']; + + + // Normalize the path. + $value = drupal_get_normal_path($value, $form_state['values']['language']); + + if (!valid_url($value) && !valid_url($value, TRUE) && $value != '<front>' && $value != '' && !file_exists($value)) { + form_error($element, t('The redirect path %value is not valid.', array('%value' => $value))); + } + + return $element; +} + +/** + * Extract the query and fragment parts out of an URL field. + */ +function _redirect_extract_url_options(&$element, &$form_state) { + $value = &$element['#value']; + $type = $element['#name']; + $options = &$form_state['values']["{$type}_options"]; + + $parsed = redirect_parse_url($value); + + if (isset($parsed['fragment'])) { + $options['fragment'] = $parsed['fragment']; + } + else { + unset($options['fragment']); + } + + if (isset($parsed['query'])) { + $options['query'] = $parsed['query']; + } + else { + unset($options['query']); + } + + if (isset($parsed['scheme']) && $parsed['scheme'] == 'https') { + $options['https'] = TRUE; + } + else { + unset($options['https']); + } + + if (!url_is_external($parsed['url'])) { + $parsed['url'] = drupal_get_normal_path($parsed['url'], $form_state['values']['language']); + } + + form_set_value($element, $parsed['url'], $form_state); + return $parsed; +} + +/** + * Form validate handler; validate an URL redirect + * + * @see redirect_edit_form() + */ +function redirect_edit_form_validate($form, &$form_state) { + $redirect = (object) $form_state['values']; + + if (empty($form_state['values']['override'])) { + if ($existing = redirect_load_by_source($redirect->source, $redirect->language)) { + $existing = $existing[1]; + if ($redirect->rid != $existing->rid && $redirect->language == $existing->language) { + // The "from" path should not conflict with another redirect + $form_state['storage']['override_messages']['redirect-conflict'] = t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $redirect->source, '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid))); + $form_state['rebuild'] = TRUE; + } + } + + if ($form['override']['#access']) { + drupal_set_message('Did you read the warnings and click the checkbox?', 'error'); + $form_state['rebuild'] = TRUE; + //form_set_error('override', 'CLICK DA BUTTON!'); + } + } + + redirect_validate($redirect, $form, $form_state); +} + +/** + * Form submit handler; insert or update an URL redirect. + * + * @see redirect_edit_form() + */ +function redirect_edit_form_submit($form, &$form_state) { + form_state_values_clean($form_state); + $redirect = (object) $form_state['values']; + redirect_save($redirect); + drupal_set_message(t('The redirect has been saved.')); + $form_state['redirect'] = 'admin/config/search/redirect'; +} + +/** + * Form builder to delete an URL redirect. + * + * @see redirect_delete_form() + * @see confirm_form() + * + * @ingroup forms + */ +function redirect_delete_form($form, &$form_state, $redirect) { + $form['rid'] = array( + '#type' => 'value', + '#value' => $redirect->rid, + ); + + return confirm_form( + $form, + t('Are you sure you want to delete the URL redirect from %source to %redirect?', array('%source' => $redirect->source, '%redirect' => $redirect->redirect)), + 'admin/config/search/redirect' + ); +} + +/** + * Form submit handler; delete an URL redirect after confirmation. + * + * @see redirect_delete_form() + */ +function redirect_delete_form_submit($form, &$form_state) { + redirect_delete($form_state['values']['rid']); + drupal_set_message(t('The redirect has been deleted.')); + $form_state['redirect'] = 'admin/config/search/redirect'; +} + +/** + * Form builder for redirection settings. + * + * @see system_settings_form() + * @see redirect_settings_form_submit() + * + * @ingroup forms + */ +function redirect_settings_form($form, &$form_state) { + $form['redirect_auto_redirect'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically create redirects when URL aliases are changed.'), + '#default_value' => variable_get('redirect_auto_redirect', TRUE), + '#disabled' => !module_exists('path'), + ); + $form['redirect_passthrough_querystring'] = array( + '#type' => 'checkbox', + '#title' => t('Retain query string through redirect.'), + '#default_value' => variable_get('redirect_passthrough_querystring', 1), + '#description' => t('For example, given a redirect from %source to %redirect, if a user visits %sourcequery they would be redirected to %redirectquery. The query strings in the redirection will always take precedence over the current query string.', array('%source' => 'source-path', '%redirect' => 'node?a=apples', '%sourcequery' => 'source-path?a=alligators&b=bananas', '%redirectquery' => 'node?a=apples&b=bananas')), + ); + $form['redirect_warning'] = array( + '#type' => 'checkbox', + '#title' => t('Display a warning message to users when they are redirected.'), + '#default_value' => variable_get('redirect_warning', FALSE), + '#access' => FALSE, + ); + $form['redirect_default_status_code'] = array( + '#type' => 'select', + '#title' => t('Default redirect status'), + '#description' => t('You can find more information about HTTP redirect status codes at <a href="@status-codes">@status-codes</a>.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')), + '#options' => redirect_status_code_options(), + '#default_value' => variable_get('redirect_default_status_code', 301), + ); + $form['redirect_page_cache'] = array( + '#type' => 'checkbox', + '#title' => t('Allow redirects to be saved into the page cache.'), + '#default_value' => variable_get('redirect_page_cache', 0), + '#description' => t('This feature requires <a href="@performance">Cache pages for anonymous users</a> to be enabled and the %variable variable to be TRUE.', array('@performance' => url('admin/config/development/performance'), '%variable' => "\$conf['page_cache_invoke_hooks']")), + '#disabled' => !variable_get('cache', 0) || !variable_get('page_cache_invoke_hooks', TRUE), + ); + $form['redirect_purge_inactive'] = array( + '#type' => 'select', + '#title' => t('Delete redirects that have not been accessed for'), + '#default_value' => variable_get('redirect_purge_inactive', 0), + '#options' => array(0 => t('Never (do not discard)')) + drupal_map_assoc(array(604800, 1209600, 1814400, 2592000, 5184000, 7776000, 10368000, 15552000, 31536000), 'format_interval'), + '#description' => t('Only redirects managaged by the redirect module itself will be deleted. Redirects managed by other modules will be left alone.'), + '#disabled' => variable_get('redirect_page_cache', 0) && !variable_get('page_cache_invoke_hooks', TRUE), + ); + + $form['globals'] = array( + '#type' => 'fieldset', + '#title' => t('Always enabled redirections'), + ); + $form['globals']['redirect_global_home'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect alternative front page URLs to the root directory.'), + '#default_value' => variable_get('redirect_global_home', 1), + '#description' => t('Includes all aliases for the front page, %root, as well as %node if a default front page is not set.', array('%root' => '/index.php', '%node' => '/node')), + ); + $form['globals']['redirect_global_index'] = array( + '#type' => 'checkbox', + '#title' => t('Remove index.php from all non-front page paths.'), + '#default_value' => variable_get('redirect_global_index', 0), + '#description' => t('Will remove index.php from paths such as %prepend and %append.', array('%prepend' => '/index.php?q=node/1', '%append' => '/page-alias/index.php')), + ); + $form['globals']['redirect_global_clean'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect from non-clean URLs to clean URLs.'), + '#default_value' => variable_get('redirect_global_clean', 1), + '#disabled' => !variable_get('clean_url', 0), + ); + $form['globals']['redirect_global_canonical'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect from non-canonical URLs to the canonical URLs.'), + '#default_value' => variable_get('redirect_global_canonical', 1), + ); + $form['globals']['redirect_global_canonical_front'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect the front page to its canonical URL.'), + '#default_value' => variable_get('redirect_global_canonical_front', 0), + '#description' => t('Add a path to a request for the site root. For example, a request for %rootpath will redirect to %canonicalpath if the default front page is set to %defaultfrontpage.', array('%rootpath' => 'http://example.com/', '%canonicalpath' => 'http://example.com/node/1', '%defaultfrontpage' => 'node/1')), + ); + $form['globals']['redirect_global_deslash'] = array( + '#type' => 'checkbox', + '#title' => t('Remove trailing slashes from paths.'), + '#default_value' => variable_get('redirect_global_deslash', 1), + ); + $form['globals']['redirect_global_add_slash'] = array( + '#type' => 'checkbox', + '#title' => t('Add trailing slashes to paths.'), + '#description' => t('For use with the Trailing Slash module instead of editing the .htaccess file.'), + '#default_value' => variable_get('redirect_global_add_slash', 0), + ); + $form['globals']['redirect_global_admin_paths'] = array( + '#type' => 'checkbox', + '#title' => t('Allow redirections on admin paths.'), + '#default_value' => variable_get('redirect_global_admin_paths', 0), + ); + + $form['submit'] = array( + '#validate' => array('redirect_settings_form_validate'), + '#submit' => array('redirect_settings_form_submit'), + ); + + return system_settings_form($form); +} + +/** + * Form submit handler; clears the page cache. + * + * @see redirect_settings_form() + */ +function redirect_settings_form_submit($form, &$form_state) { + redirect_page_cache_clear(); +} + +/** + * Form validator; makes sure conflicting settings aren't chosen. + * + * @see redirect_settings_form() + */ +function redirect_settings_form_validate($form, &$form_state) { + if ($form_state['values']['redirect_global_deslash'] && $form_state['values']['redirect_global_add_slash']) { + form_set_error('info', t('You can not have both %deslash and %add_slash enabled.', array('%deslash' => rtrim($form['globals']['redirect_global_deslash']['#title'], '.'), '%add_slash' => rtrim($form['globals']['redirect_global_add_slash']['#title'], '.')))); + } +} + +function redirect_404_list($form = NULL) { + $destination = drupal_get_destination(); + + // Get filter keys and add the filter form. + $keys = func_get_args(); + //$keys = array_splice($keys, 2); // Offset the $form and $form_state parameters. + $keys = implode('/', $keys); + $build['redirect_list_404_filter_form'] = drupal_get_form('redirect_list_404_filter_form', $keys); + + $header = array( + array('data' => t('Page'), 'field' => 'message'), + array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'), + array('data' => t('Last accessed'), 'field' => 'timestamp'), + array('data' => t('Operations')), + ); + + $count_query = db_select('watchdog', 'w'); + $count_query->addExpression('COUNT(DISTINCT(w.message))'); + $count_query->leftJoin('redirect', 'r', 'w.message = r.source'); + $count_query->condition('w.type', 'page not found'); + $count_query->isNull('r.rid'); + redirect_build_filter_query($count_query, array('w.message'), $keys); + + $query = db_select('watchdog', 'w')->extend('PagerDefault')->extend('TableSort'); + $query->fields('w', array('message')); + $query->addExpression('COUNT(wid)', 'count'); + $query->addExpression('MAX(timestamp)', 'timestamp'); + $query->leftJoin('redirect', 'r', 'w.message = r.source'); + $query->isNull('r.rid'); + $query->condition('w.type', 'page not found'); + $query->groupBy('w.message'); + $query->orderByHeader($header); + $query->limit(25); + redirect_build_filter_query($query, array('w.message'), $keys); + $query->setCountQuery($count_query); + $results = $query->execute(); + + $rows = array(); + foreach ($results as $result) { + $row = array(); + $row['source'] = l($result->message, $result->message, array('query' => $destination)); + $row['count'] = $result->count; + $row['timestamp'] = format_date($result->timestamp, 'short'); + + $operations = array(); + if (redirect_access('create', 'redirect')) { + $operations['add'] = array( + 'title' => t('Add redirect'), + 'href' => 'admin/config/search/redirect/add/', + 'query' => array('source' => $result->message) + $destination, + ); + } + $row['operations'] = array( + 'data' => array( + '#theme' => 'links', + '#links' => $operations, + '#attributes' => array('class' => array('links', 'inline', 'nowrap')), + ), + ); + + $rows[] = $row; + } + + $build['redirect_404_table'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('No 404 pages without redirects found.'), + ); + $build['redirect_404_pager'] = array('#theme' => 'pager'); + return $build; +} + +/** + * Return a form to filter URL redirects. + * + * @see redirect_list_filter_form_submit() + * + * @ingroup forms + */ +function redirect_list_404_filter_form($form, &$form_state, $keys = '') { + $form['#attributes'] = array('class' => array('search-form')); + $form['basic'] = array( + '#type' => 'fieldset', + '#title' => t('Filter 404s'), + '#attributes' => array('class' => array('container-inline')), + ); + $form['basic']['filter'] = array( + '#type' => 'textfield', + '#title' => '', + '#default_value' => $keys, + '#maxlength' => 128, + '#size' => 25, + ); + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + '#submit' => array('redirect_list_404_filter_form_submit'), + ); + if ($keys) { + $form['basic']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + '#submit' => array('redirect_list_404_filter_form_reset'), + ); + } + return $form; +} + +/** + * Process filter form submission when the Filter button is pressed. + */ +function redirect_list_404_filter_form_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/redirect/404/' . trim($form_state['values']['filter']); +} + +/** + * Process filter form submission when the Reset button is pressed. + */ +function redirect_list_404_filter_form_reset($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/redirect/404'; +} + +function redirect_list_table($redirects, $header) { + $destination = drupal_get_destination(); + $default_status_code = variable_get('redirect_default_status_code', 301); + + // Set up the header. + $header = array_combine($header, $header); + $header = array_intersect_key(array( + 'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'), + 'redirect' => array('data' => t('To'), 'field' => 'redirect'), + 'status_code' => array('data' => t('Type'), 'field' => 'status_code'), + 'language' => array('data' => t('Language'), 'field' => 'language'), + 'count' => array('data' => t('Count'), 'field' => 'count'), + 'access' => array('data' => t('Last accessed'), 'field' => 'access'), + 'operations' => array('data' => t('Operations')), + ), $header); + + // Do not include the language column if locale is disabled. + if (!module_exists('locale')) { + unset($header['language']); + } + + $rows = array(); + foreach ($redirects as $rid => $redirect) { + $row = array(); + $redirect->source_options = array_merge($redirect->source_options, array('alias' => TRUE, 'language' => redirect_language_load($redirect->language))); + $source_url = redirect_url($redirect->source, $redirect->source_options); + $redirect_url = redirect_url($redirect->redirect, array_merge($redirect->redirect_options, array('alias' => TRUE))); + $row['data']['source'] = l($source_url, $redirect->source, $redirect->source_options); + $row['data']['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options); + $row['data']['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code)); + $row['data']['language'] = module_invoke('locale', 'language_name', $redirect->language); + $row['data']['count'] = $redirect->count; + if ($redirect->access) { + $row['data']['access'] = array( + 'data' => t('!interval ago', array('!interval' => format_interval(REQUEST_TIME - $redirect->access))), + 'title' => t('Last accessed on @date', array('@date' => format_date($redirect->access))), + ); + } + else { + $row['data']['access'] = t('Never'); + } + + // Mark redirects that override existing paths with a warning in the table. + if (drupal_valid_path($redirect->source)) { + $row['class'][] = 'warning'; + $row['title'] = t('This redirect overrides an existing internal path.'); + } + + $operations = array(); + if (redirect_access('update', $redirect)) { + $operations['edit'] = array( + 'title' => t('Edit'), + 'href' => 'admin/config/search/redirect/edit/' . $rid, + 'query' => $destination, + ); + } + if (redirect_access('delete', $redirect)) { + $operations['delete'] = array( + 'title' => t('Delete'), + 'href' => 'admin/config/search/redirect/delete/' . $rid, + 'query' => $destination, + ); + } + $row['data']['operations'] = array( + 'data' => array( + '#theme' => 'links', + '#links' => $operations, + '#attributes' => array('class' => array('links', 'inline', 'nowrap')), + ), + ); + + $row['data'] = array_intersect_key($row['data'], $header); + $rows[$rid] = $row; + } + + $build['list'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('No URL redirects available.'), + '#attributes' => array('class' => array('redirect-list')), + ); + + return $build; +} diff --git a/sites/all/modules/redirect/redirect.admin.js b/sites/all/modules/redirect/redirect.admin.js new file mode 100644 index 0000000000000000000000000000000000000000..d169d4befe224243fa2c48c993b9465842d2c980 --- /dev/null +++ b/sites/all/modules/redirect/redirect.admin.js @@ -0,0 +1,28 @@ + +(function ($) { + +Drupal.behaviors.redirectAdmin = { + attach: function (context) { + $('table.redirect-list-tableselect tbody input:checkbox').bind('change', function(context) { + var checked = $('table.redirect-list-tableselect input:checkbox:checked').length; + if (checked) { + $('fieldset.redirect-list-operations').slideDown(); + } + else { + $('fieldset.redirect-list-operations').slideUp(); + } + }); + $('table.redirect-list-tableselect th.select-all input:checkbox').bind('change', function(context) { + var checked = $(this, context).attr('checked'); + if (checked) { + $('fieldset.redirect-list-operations').slideDown(); + } + else { + $('fieldset.redirect-list-operations').slideUp(); + } + }); + $('fieldset.redirect-list-operations').hide(); + } +}; + +})(jQuery); diff --git a/sites/all/modules/redirect/redirect.api.php b/sites/all/modules/redirect/redirect.api.php new file mode 100644 index 0000000000000000000000000000000000000000..f38d7153c57281f696c26a4dd96b4c26ef93e231 --- /dev/null +++ b/sites/all/modules/redirect/redirect.api.php @@ -0,0 +1,295 @@ +<?php + +/** + * @file + * Hooks provided by the Redirect module. + */ + +/** + * @defgroup redirect_api_hooks Redirect API Hooks + * @{ + * During redirect operations (create, update, view, delete, etc.), there are + * several sets of hooks that get invoked to allow modules to modify the + * redirect operation: + * - All-module hooks: Generic hooks for "redirect" operations. These are + * always invoked on all modules. + * - Entity hooks: Generic hooks for "entity" operations. These are always + * invoked on all modules. + * + * Here is a list of the redirect and entity hooks that are invoked, and other + * steps that take place during redirect operations: + * - Creating a new redirect (calling redirect_save() on a new redirect): + * - hook_redirect_presave() (all) + * - Redirect written to the database + * - hook_redirect_insert() (all) + * - hook_entity_insert() (all) + * - Updating an existing redirect (calling redirect_save() on an existing redirect): + * - hook_redirect_presave() (all) + * - Redirect written to the database + * - hook_redirect_update() (all) + * - hook_entity_update() (all) + * - Loading a redirect (calling redirect_load(), redirect_load_multiple(), or + * entity_load() with $entity_type of 'redirect'): + * - Redirect information is read from database. + * - hook_entity_load() (all) + * - hook_redirect_load() (all) + * - Deleting a redirect (calling redirect_delete() or redirect_delete_multiple()): + * - Redirect is loaded (see Loading section above) + * - Redirect information is deleted from database + * - hook_redirect_delete() (all) + * - hook_entity_delete() (all) + * - Preparing a redirect for editing (note that if it's + * an existing redirect, it will already be loaded; see the Loading section + * above): + * - hook_redirect_prepare() (all) + * - Validating a redirect during editing form submit (calling + * redirect_form_validate()): + * - hook_redirect_validate() (all) + * @} + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Act on redirects being loaded from the database. + * + * This hook is invoked during redirect loading, which is handled by + * entity_load(), via classes RedirectController and + * DrupalDefaultEntityController. After the redirect information is read from + * the database or the entity cache, hook_entity_load() is invoked on all + * implementing modules, and then hook_redirect_load() is invoked on all + * implementing modules. + * + * This hook should only be used to add information that is not in the redirect + * table, not to replace information that is in that table (which could + * interfere with the entity cache). For performance reasons, information for + * all available redirects should be loaded in a single query where possible. + * + * The $types parameter allows for your module to have an early return (for + * efficiency) if your module only supports certain redirect types. + * + * @param $redirects + * An array of the redirects being loaded, keyed by rid. + * @param $types + * An array containing the types of the redirects. + * + * @ingroup redirect_api_hooks + */ +function hook_redirect_load(array &$redirects, $types) { + +} + +/** + * Alter the list of redirects matching a certain source. + * + * @param $redirects + * An array of redirect objects. + * @param $source + * The source request path. + * @param $context + * An array with the following key/value pairs: + * - language: The language code of the source request. + * - query: An array of the source request query string. + * + * @see redirect_load_by_source() + * @ingroup redirect_api_hooks + */ +function hook_redirect_load_by_source_alter(array &$redirects, $source, array $context) { + foreach ($redirects as $rid => $redirect) { + if ($redirect->source !== $source) { + // If the redirects to do not exactly match $source (e.g. case + // insensitive matches), then remove them from the results. + unset($redirects[$rid]); + } + } +} + +/** + * Control access to a redirect. + * + * Modules may implement this hook if they want to have a say in whether or not + * a given user has access to perform a given operation on a redirect. + * + * The administrative account (user ID #1) always passes any access check, + * so this hook is not called in that case. Users with the "administer redirects" + * permission may always update and delete redirects through the administrative + * interface. + * + * Note that not all modules will want to influence access on all + * redirect types. If your module does not want to actively grant or + * block access, return REDIRECT_ACCESS_IGNORE or simply return nothing. + * Blindly returning FALSE will break other redirect access modules. + * + * @param $redirect + * The redirect object on which the operation is to be performed, or, if it + * does not yet exist, the type of redirect to be created. + * @param $op + * The operation to be performed. Possible values: + * - "create" + * - "delete" + * - "update" + * @param $account + * A user object representing the user for whom the operation is to be + * performed. + * + * @return + * REDIRECT_ACCESS_ALLOW if the operation is to be allowed; + * REDIRECT_ACCESS_DENY if the operation is to be denied; + * REDIRECT_ACCESSS_IGNORE to not affect this operation at all. + * + * @see redirect_access() + * @ingroup redirect_api_hooks + */ +function hook_redirect_access($op, $redirect, $account) { + $type = is_string($redirect) ? $redirect : $redirect->type; + + if (in_array($type, array('normal', 'special'))) { + if ($op == 'create' && user_access('create ' . $type . ' redirects', $account)) { + return REDIRECT_ACCESS_ALLOW; + } + + if ($op == 'update') { + if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $redirect->uid))) { + return REDIRECT_ACCESS_ALLOW; + } + } + + if ($op == 'delete') { + if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $redirect->uid))) { + return REDIRECT_ACCESS_ALLOW; + } + } + } + + // Returning nothing from this function would have the same effect. + return REDIRECT_ACCESS_IGNORE; +} + +/** + * Act on a redirect object about to be shown on the add/edit form. + * + * This hook is invoked from redirect_object_prepare(). + * + * @param $redirect + * The redirect that is about to be shown on the add/edit form. + * + * @ingroup redirect_api_hooks + */ +function hook_redirect_prepare($redirect) { + +} + +/** + * Perform redirect validation before a redirect is created or updated. + * + * This hook is invoked from redirect_validate(), after a user has has finished + * editing the redirect and is submitting it. It is invoked at the end of all + * the standard validation steps. + * + * To indicate a validation error, use form_set_error(). + * + * Note: Changes made to the $redirect object within your hook implementation + * will have no effect. The preferred method to change a redirect's content is + * to use hook_redirect_presave() instead. If it is really necessary to change + * the redirect at the validate stage, you can use form_set_value(). + * + * @param $redirect + * The redirect being validated. + * @param $form + * The form being used to edit the redirect. + * @param $form_state + * The form state array. + * + * @see redirect_validate() + * @ingroup redirect_api_hooks + */ +function hook_redirect_validate($redirect, $form, $form_state) { + +} + +/** + * Act on a redirect being inserted or updated. + * + * This hook is invoked from redirect_save() before the redirect is saved to + * the database. + * + * @param $redirect + * The redirect that is being inserted or updated. + * + * @see redirect_save() + * @ingroup redirect_api_hooks + */ +function hook_redirect_presave($redirect) { + +} + +/** + * Respond to creation of a new redirect. + * + * This hook is invoked from redirect_save() after the redirect is inserted + * into the redirect table in the database. + * + * @param $redirect + * The redirect that is being created. + * + * @see redirect_save() + * @ingroup redirect_api_hooks + */ +function hook_redirect_insert($redirect) { + +} + +/** + * Respond to updates to a redirect. + * + * This hook is invoked from redirect_save() after the redirect is updated in + * the redirect table in the database. + * + * @param $redirect + * The redirect that is being updated. + * + * @see redirect_save() + * @ingroup redirect_api_hooks + */ +function hook_redirect_update($redirect) { + +} + +/** + * Respond to redirect deletion. + * + * This hook is invoked from redirect_delete_multiple() after the redirect has + * been removed from the redirect table in the database. + * + * @param $redirect + * The redirect that is being deleted. + * + * @see redirect_delete_multiple() + * @ingroup redirect_api_hooks + */ +function hook_redirect_delete($redirect) { + +} + +/** + * Act on a redirect being redirected. + * + * This hook is invoked from redirect_redirect() before the redirect callback + * is invoked. + * + * @param $redirect + * The redirect that is being used for the redirect. + * + * @see redirect_redirect() + * @see drupal_page_is_cacheable() + * @ingroup redirect_api_hooks + */ +function hook_redirect_alter($redirect) { +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/redirect/redirect.drush.inc b/sites/all/modules/redirect/redirect.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..304f89a2f206e8d7d4787e5bdd3345ca2b30b72e --- /dev/null +++ b/sites/all/modules/redirect/redirect.drush.inc @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Drush integration for the redirect module. + */ + +/** + * Implementation of hook_drush_command(). + */ +function redirect_drush_command() { + $items['generate-redirects'] = array( + 'description' => 'Create redirects.', + 'drupal dependencies' => array('devel_generate'), + 'arguments' => array( + 'count' => 'Number of redirects to generate.', + ), + 'options' => array( + 'delete' => 'Delete all redirects before generating new ones.', + ), + ); + + return $items; +} + +/** + * Command callback. Generate a number of redirects. + */ +function drush_redirect_generate_redirects($count = NULL) { + if (drush_generate_is_number($count) == FALSE) { + return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', t('Invalid number of redirects.')); + } + module_load_include('inc', 'redirect', 'redirect.generate'); + drush_generate_include_devel(); + redirect_run_unprogressive_batch('redirect_generate_redirects_batch_info', $count, drush_get_option('delete')); +} + +/** + * Perform an unprogressive batch process for CLI. + */ +function redirect_run_unprogressive_batch() { + $batch = batch_get(); + if (!empty($batch)) { + // If there is already something in the batch, don't run. + return FALSE; + } + + $args = func_get_args(); + $batch_callback = array_shift($args); + + if (!lock_acquire($batch_callback)) { + return FALSE; + } + + // Attempt to increase the execution time. + drupal_set_time_limit(240); + + // Build the batch array. + $batch = call_user_func_array($batch_callback, $args); + batch_set($batch); + + // We need to manually set the progressive variable again. + // @todo Remove when http://drupal.org/node/638712 is fixed. + $batch =& batch_get(); + $batch['progressive'] = FALSE; + + // Run the batch process. + batch_process(); + + lock_release($batch_callback); + return TRUE; +} diff --git a/sites/all/modules/redirect/redirect.generate.inc b/sites/all/modules/redirect/redirect.generate.inc new file mode 100644 index 0000000000000000000000000000000000000000..c18357abf283d8db834c8f509b1d49c80abf4d1a --- /dev/null +++ b/sites/all/modules/redirect/redirect.generate.inc @@ -0,0 +1,182 @@ +<?php + +/** + * @file + * Devel generate integration for the redirect module. + */ + +function redirect_generate_form() { + $form['count'] = array( + '#type' => 'textfield', + '#title' => t('How many URL redirects would you like to generate?'), + '#default_value' => 50, + '#size' => 4, + ); + $form['delete'] = array( + '#type' => 'checkbox', + '#title' => t('Delete all URL redirects before generating new URL redirects.'), + '#default_value' => FALSE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Generate'), + ); + + return $form; +} + +function redirect_generate_form_submit(&$form, &$form_state) { + // Run the batch. + $batch = redirect_generate_redirects_batch_info($form_state['values']['count'], $form_state['values']['delete']); + batch_set($batch); +} + +function redirect_generate_redirects_batch_info($count, $delete = FALSE) { + if ($delete) { + $operations[] = array('redirect_generate_batch_delete', array()); + } + + $operations[] = array('redirect_generate_batch_generate', array($count)); + + return array( + 'operations' => $operations, + 'finished' => 'redirect_generate_batch_finished', + 'file' => drupal_get_path('module', 'redirect') . '/redirect.generate.inc', + ); +} + +function redirect_generate_batch_delete(array &$context) { + if (empty($context['sandbox'])) { + $context['sandbox'] = array(); + $context['sandbox']['progress'] = 0; + $context['sandbox']['current_rid'] = 0; + $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT rid) FROM {redirect}')->fetchField(); + } + + $limit = 20; + $rids = db_query_range("SELECT rid FROM {redirect} WHERE rid > :rid ORDER BY rid", 0, $limit, array(':rid' => $context['sandbox']['current_rid']))->fetchCol(); + redirect_delete_multiple($rids); + + // Update our progress information. + $context['sandbox']['progress'] += count($rids); + $context['sandbox']['current_rid'] = end($rids); + $context['message'] = t('Deleted URL redirect @rid.', array('@rid' => end($rids))); + + // Inform the batch engine that we are not finished, + // and provide an estimation of the completion level we reached. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']); + } +} + +function redirect_generate_batch_generate($num, array &$context) { + if (empty($context['sandbox'])) { + $context['sandbox'] = array(); + $context['sandbox']['progress'] = 0; + $context['sandbox']['max'] = $num; + + $query = db_select('node', 'n'); + $query->addField('n', 'nid'); + $query->condition('n.status', NODE_PUBLISHED); + $query->addTag('node_access'); + $context['sandbox']['nids'] = $query->execute()->fetchAllKeyed(0, 0); + } + + module_load_include('inc', 'devel_generate'); + + $limit = 20; + $types = array_keys(redirect_status_code_options()); + $languages = module_exists('locale') ? array_keys(locale_language_list('name')) : array(); + + for ($i = 0; $i < min($limit, $context['sandbox']['max'] - $context['sandbox']['progress']); $i++) { + $rand = mt_rand(0, 100); + + $redirect = new stdClass(); + redirect_object_prepare($redirect); + $redirect->source = _redirect_generate_url(); + $redirect->devel_generate = TRUE; + + if ($context['sandbox']['nids'] && $rand >= 40) { + $redirect->redirect = 'node/'. array_rand($context['sandbox']['nids']); + } + else { + $redirect->redirect = _redirect_generate_url(TRUE); + if ($rand <= 20) { + $redirect->redirect_options['query'] = _redirect_generate_querystring(); + } + if ($rand <= 5) { + $redirect->redirect_options['fragment'] = devel_generate_word(mt_rand(4, 8)); + } + } + + if ($rand <= 20) { + $redirect->status_code = $types[array_rand($types)]; + } + + if ($languages && $rand <= 20) { + $redirect->language = $languages[array_rand($languages)]; + } + + if ($rand <= 30) { + $redirect->source_options['query'] = _redirect_generate_querystring(); + } + + redirect_save($redirect); + + if (mt_rand(0, 1)) { + db_update('redirect') + ->fields(array( + 'count' => mt_rand(1, 500), + 'access' => mt_rand(REQUEST_TIME - 31536000, REQUEST_TIME), + )) + ->condition('rid', $redirect->rid) + ->execute(); + } + + $context['results'][] = $redirect->rid; + } + + // Update our progress information. + $context['sandbox']['progress'] += $limit; + //$context['message'] = t('Deleted URL redirect @rid.', array('@rid' => end($rids))); + + // Inform the batch engine that we are not finished, + // and provide an estimation of the completion level we reached. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']); + } +} + +function redirect_generate_batch_finished($success, $results, $operations) { + if ($success) { + drupal_set_message(format_plural(count($results), 'One URL redirect created.', '@count URL redirects created.')); + } + else { + // An error occurred. + // $operations contains the operations that remained unprocessed. + $error_operation = reset($operations); + drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE)))); + } +} + +function _redirect_generate_url($external = FALSE, $max_levels = 2) { + module_load_include('inc', 'devel_generate'); + + $url = array(); + if ($external) { + $tlds = array('com', 'net', 'org'); + $url[] = 'http://www.example.'. $tlds[array_rand($tlds)]; + } + $max_levels = mt_rand($external ? 0 : 1, $max_levels); + for ($i = 1; $i <= $max_levels; $i++) { + $url[] = devel_generate_word(mt_rand(6 / $i, 8)); + } + return implode('/', $url); +} + +function _redirect_generate_querystring() { + module_load_include('inc', 'devel_generate'); + + $query = array(devel_generate_word(mt_rand(1, 3)) => devel_generate_word(mt_rand(2, 4))); + return $query; +} diff --git a/sites/all/modules/redirect/redirect.info b/sites/all/modules/redirect/redirect.info new file mode 100644 index 0000000000000000000000000000000000000000..8ef05553d4664adc79177c221e7fdd3c7c82dfa8 --- /dev/null +++ b/sites/all/modules/redirect/redirect.info @@ -0,0 +1,23 @@ +name = Redirect +description = Allows users to redirect from old URLs to new URLs. +core = 7.x +files[] = redirect.module +files[] = redirect.admin.inc +files[] = redirect.install +files[] = redirect.test +files[] = views/redirect.views.inc +;files[] = views/redirect_handler_field_redirect_type.inc +files[] = views/redirect_handler_filter_redirect_type.inc +files[] = views/redirect_handler_field_redirect_source.inc +files[] = views/redirect_handler_field_redirect_redirect.inc +files[] = views/redirect_handler_field_redirect_operations.inc +files[] = views/redirect_handler_field_redirect_link_edit.inc +files[] = views/redirect_handler_field_redirect_link_delete.inc +configure = admin/config/search/redirect/settings + +; Information added by drupal.org packaging script on 2013-02-06 +version = "7.x-1.0-rc1+4-dev" +core = "7.x" +project = "redirect" +datestamp = "1360158342" + diff --git a/sites/all/modules/redirect/redirect.install b/sites/all/modules/redirect/redirect.install new file mode 100644 index 0000000000000000000000000000000000000000..fa67aaa53e4ba5416acb74c3e50a6e9f2a5efd39 --- /dev/null +++ b/sites/all/modules/redirect/redirect.install @@ -0,0 +1,358 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the redirect module. + */ + +/** + * Implements hook_schema(). + */ +function redirect_schema() { + $schema['redirect'] = array( + 'description' => 'Stores information on redirects.', + 'fields' => array( + 'rid' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique redirect ID.', + ), + 'hash' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'A unique hash based on source, source_options, and language.', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + 'description' => "The redirect type; if value is 'redirect' it is a normal redirect handled by the module.", + ), + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The {users}.uid of the user who created the redirect.', + ), + 'source' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The source path to redirect from.', + ), + 'source_options' => array( + 'type' => 'text', + 'not null' => TRUE, + 'serialize' => TRUE, + 'description' => 'A serialized array of source options.', + ), + 'redirect' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The destination path to redirect to.', + ), + 'redirect_options' => array( + 'type' => 'text', + 'not null' => TRUE, + 'serialize' => TRUE, + 'description' => 'A serialized array of redirect options.', + ), + 'language' => array( + 'description' => 'The language this redirect is for; if blank, the alias will be used for unknown languages.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => 'und', + ), + 'status_code' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'description' => 'The HTTP status code to use for the redirect.', + ), + 'count' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The number of times the redirect has been used.', + ), + 'access' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The timestamp of when the redirect was last accessed.' + ), + ), + 'primary key' => array('rid'), + 'unique keys' => array( + 'hash' => array('hash'), + ), + 'indexes' => array( + 'expires' => array('type', 'access'), + 'source_language' => array('source', 'language'), + ), + ); + + return $schema; +} + +/** + * Implements hook_install(). + */ +function redirect_install() { + // If the path redirect table exists, then set the schema to run the + // migration update function. + if (db_table_exists('path_redirect')) { + drupal_set_installed_schema_version('redirect', 6999); + } +} + +/** + * Implements hook_uninstall(). + */ +function redirect_uninstall() { + drupal_load('module', 'redirect'); + $variables = array_keys(redirect_variables()); + foreach ($variables as $variable) { + variable_del($variable); + } +} + +/** + * Add the {redirect}.count field. + */ +function redirect_update_1() { + $field = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The number of times the redirect has been used.', + ); + db_add_field('redirect', 'count', $field); +} + +/** + * Add the {redirect}.uid field. + */ +function redirect_update_2() { + $field = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The {users}.uid of the user who created the redirect.', + ); + db_add_field('redirect', 'uid', $field); + db_update('redirect') + ->fields(array('uid' => 1)) + ->execute(); +} + +/** + * Enable bootstrap status for the module. + */ +function redirect_update_3() { + db_update('system') + ->fields(array('bootstrap' => 1)) + ->condition('type', 'module') + ->condition('name', 'redirect') + ->execute(); +} + +/** + * Change empty redirect types to 'redirect'. + */ +function redirect_update_4() { + db_update('redirect') + ->fields(array('type' => 'redirect')) + ->condition('type', '') + ->execute(); +} + +/** + * Rename {redirect}.last_used to {redirect}.access. + */ +function redirect_update_5() { + if (db_field_exists('redirect', 'last_used')) { + db_drop_index('redirect', 'expires'); + db_change_field('redirect', 'last_used', 'access', array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The timestamp of when the redirect was last accessed.', + )); + db_add_index('redirect', 'expires', array('type', 'access')); + } +} + +/** + * Add an index on the source and language columns in the redirect table. + */ +function redirect_update_6() { + if (!db_index_exists('redirect', 'source_language')) { + db_add_index('redirect', 'source_language', array('source', 'language')); + } +} + +/** + * Migrate data and variables from the Drupal 6 path_redirect module. + */ +function redirect_update_7000(&$sandbox) { + if (!isset($sandbox['progress']) && db_table_exists('path_redirect')) { + $sandbox['progress'] = 0; + $sandbox['current_rid'] = 0; + $sandbox['max'] = db_query('SELECT COUNT(rid) FROM {path_redirect}')->fetchField(); + $sandbox['skipped'] = array(); + } + + if (empty($sandbox['max'])) { + $sandbox['#finished'] = 1; + return t('No redirects to migrate.'); + } + + // Ensure the redirect module is loaded since we need to use its functions. + drupal_load('module', 'redirect'); + + $query = db_query_range("SELECT * FROM {path_redirect} WHERE rid > :rid ORDER BY rid", 0, 25, array(':rid' => $sandbox['current_rid'])); + foreach ($query as $old_redirect) { + $redirect = _redirect_migrate_path_redirect_redirect($old_redirect); + + if (empty($redirect->success)) { + $sandbox['skipped'][$old_redirect->rid] = t('RID @rid: @from to @to', array( + '@rid' => $old_redirect->rid, + '@from' => redirect_url($redirect->source, $redirect->source_options), + '@to' => redirect_url($redirect->redirect, $redirect->redirect_options), + )); + } + $sandbox['progress']++; + $sandbox['current_rid'] = $old_redirect->rid; + } + + $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max']; + + if ($sandbox['#finished'] >= 1) { + // Once finished, drop the old table. + db_drop_table('path_redirect'); + + // Migrate variables. + _redirect_migrate_path_redirect_variables(); + + // Remove the path_redirect entry from the system table. + db_delete('system') + ->condition('name', 'path_redirect') + ->execute(); + + // Show a message about how many redirects were migrated, and how many + // were skipped. + $skipped = count($sandbox['skipped']); + $migrated = $sandbox['progress'] - $skipped; + // @todo The following strings should be using t(). + $return = "Migrated $migrated redirects."; + if (!empty($sandbox['skipped'])) { + $return .= " The following $skipped redirects were not migrated since there were already existing redirects for the path and language combination:" . theme('item_list', array('items' => $sandbox['skipped'])); + } + return $return; + } +} + +/** + * Migrate a path redirect redirect to a redirect redirect. + */ +function _redirect_migrate_path_redirect_redirect($old_redirect) { + $redirect = new stdClass(); + redirect_object_prepare($redirect); + + $source_parsed = redirect_parse_url($old_redirect->source); + $redirect->source = $source_parsed['url']; + if (!empty($source_parsed['query'])) { + $redirect->source_options['query'] = $source_parsed['query']; + } + + $redirect_parsed = redirect_parse_url($old_redirect->redirect) + array('query' => drupal_get_query_array($old_redirect->query), 'fragment' => $old_redirect->fragment); + $redirect->redirect = $redirect_parsed['url']; + if (!empty($redirect_parsed['query'])) { + $redirect->redirect_options['query'] = $redirect_parsed['query']; + } + if (!empty($redirect_parsed['fragment'])) { + $redirect->redirect_options['fragment'] = $redirect_parsed['fragment']; + } + if (!empty($redirect_parsed['https'])) { + $redirect->redirect_options['https'] = TRUE; + } + + // Make sure empty language codes get migrated to use the new constant. + $redirect->language = empty($old_redirect->language) ? LANGUAGE_NONE : $old_redirect->language; + + // Default status codes get a value of 0. + if ($old_redirect->type != variable_get('redirect_default_status_code', 301)) { + $redirect->status_code = (int) $old_redirect->type; + } + + redirect_hash($redirect); + if (redirect_load_by_hash($redirect->hash)) { + // If a redirect with the same hash already exists, then it needs to be + // skipped. + $redirect->success = FALSE; + } + else { + // Add the redirect to the database. + db_insert('redirect') + ->fields(array( + 'hash' => $redirect->hash, + 'type' => 'redirect', + 'uid' => 1, + 'source' => $redirect->source, + 'source_options' => serialize($redirect->source_options), + 'redirect' => $redirect->redirect, + 'redirect_options' => serialize($redirect->redirect_options), + 'language' => $redirect->language, + 'status_code' => $redirect->status_code, + 'count' => 0, + 'access' => $old_redirect->last_used, + )) + ->execute(); + + // Migrating this redirect succeeded. + $redirect->success = TRUE; + } + + return $redirect; +} + +/** + * Migrate path redirect variables to redirect variables. + */ +function _redirect_migrate_path_redirect_variables() { + // Port path_redirect variables. + $variables = array( + 'path_redirect_default_status' => 'redirect_default_status_code', + 'path_redirect_purge_inactive' => 'redirect_purge_inactive', + 'path_redirect_retain_query_string' => 'redirect_passthrough_querystring', + 'path_redirect_auto_redirect' => 'redirect_auto_redirect', + 'path_redirect_redirect_warning' => 'redirect_warning', + 'path_redirect_allow_bypass' => NULL, + ); + foreach ($variables as $old_variable => $new_variable) { + if (!empty($new_variable)) { + $old_value = variable_get($old_variable); + $new_value = variable_get($new_variable); + + // Only if the old variable exists, and the new variable hasn't been set + // yet, do we set the new variable with the old value. + if (isset($old_value) && !isset($new_value)) { + variable_set($new_variable, $old_value); + } + } + + // Delete the old path redirect variable. + variable_del($old_variable); + } +} diff --git a/sites/all/modules/redirect/redirect.js b/sites/all/modules/redirect/redirect.js new file mode 100644 index 0000000000000000000000000000000000000000..c59785d6e9ba31065149a47cf6dbafa5e0618666 --- /dev/null +++ b/sites/all/modules/redirect/redirect.js @@ -0,0 +1,18 @@ + +(function ($) { + +Drupal.behaviors.redirectFieldsetSummaries = { + attach: function (context) { + $('fieldset.redirect-list', context).drupalSetSummary(function (context) { + if ($('table.redirect-list tbody td.empty', context).size()) { + return Drupal.t('No redirects'); + } + else { + var redirects = $('table.redirect-list tbody tr').size(); + return Drupal.formatPlural(redirects, '1 redirect', '@count redirects'); + } + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/redirect/redirect.module b/sites/all/modules/redirect/redirect.module new file mode 100644 index 0000000000000000000000000000000000000000..65d5599b5e77a43efba9ce6de2fbc3d3e79e8f03 --- /dev/null +++ b/sites/all/modules/redirect/redirect.module @@ -0,0 +1,1628 @@ +<?php + +/** + * @defgroup redirect_api Redirection API + * @{ + * Functions related to URL redirects. + * + * @} End of "defgroup redirect_api". + */ + +/** + * Modules should return this value from hook_redirect_access() to allow access + * to a redirect. + */ +define('REDIRECT_ACCESS_ALLOW', 'allow'); + +/** + * Modules should return this value from hook_redirect_access() to deny access + * to a redirect. + */ +define('REDIRECT_ACCESS_DENY', 'deny'); + +/** + * Modules should return this value from hook_redirect_access() to not affect + * redirect access. + */ +define('REDIRECT_ACCESS_IGNORE', NULL); + +/** + * Implements hook_entity_info(). + */ +function redirect_entity_info() { + $info['redirect'] = array( + 'label' => t('Redirect'), + 'base table' => 'redirect', + 'controller class' => 'RedirectController', + 'entity keys' => array( + 'id' => 'rid', + 'bundle' => 'type', + ), + 'fieldable' => FALSE, + 'uuid' => FALSE, + 'redirect' => FALSE, + ); + + return $info; +} + +/** + * Controller class for redirects. + * + * This extends the DrupalDefaultEntityController class, adding required + * special handling for redirect objects. + */ +class RedirectController extends DrupalDefaultEntityController { + + protected function attachLoad(&$redirects, $revision_id = FALSE) { + // Unserialize the URL option fields. + foreach ($redirects as $key => $redirect) { + $redirects[$key]->source_options = unserialize($redirect->source_options); + $redirects[$key]->redirect_options = unserialize($redirect->redirect_options); + } + parent::attachLoad($redirects, $revision_id); + } +} + +/** + * Implements hook_hook_info(). + */ +function redirect_hook_info() { + $hooks = array( + 'redirect_load', + 'redirect_load_by_source_alter', + 'redirect_access', + 'redirect_prepare', + 'redirect_validate', + 'redirect_presave', + 'redirect_insert', + 'redirect_update', + 'redirect_delete', + 'redirect_alter', + ); + + return array_fill_keys($hooks, array('group' => 'redirect')); +} + +/** + * Implements hook_permission(). + */ +function redirect_permission() { + $permissions['administer redirects'] = array( + 'title' => t('Administer URL redirections'), + ); + return $permissions; +} + +/** + * Implements hook_help(). + */ +function redirect_help($path, $arg) { + $output = ''; + switch ($path) { + case 'admin/config/search/redirect/404': + $output = '<p>' . t('This page lists all paths that have resulted in 404 errors and do not yet have any redirects assigned to them.') . '</p>'; + break; + case 'admin/reports/page-not-found': + break; + } + return $output; +} + +/** + * Implements hook_menu(). + */ +function redirect_menu() { + $items['admin/config/search/redirect'] = array( + 'title' => 'URL redirects', + 'description' => 'Redirect users from one URL to another.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_list_form'), + 'access arguments' => array('administer redirects'), + 'file' => 'redirect.admin.inc', + ); + $items['admin/config/search/redirect/list'] = array( + 'title' => 'List', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/config/search/redirect/add'] = array( + 'title' => 'Add redirect', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_edit_form'), + 'access callback' => 'redirect_access', + 'access arguments' => array('create', 'redirect'), + 'file' => 'redirect.admin.inc', + 'type' => MENU_LOCAL_ACTION, + ); + $items['admin/config/search/redirect/edit/%redirect'] = array( + 'title' => 'Edit redirect', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_edit_form', 5), + 'access callback' => 'redirect_access', + 'access arguments' => array('update', 5), + 'file' => 'redirect.admin.inc', + ); + $items['admin/config/search/redirect/delete/%redirect'] = array( + 'title' => 'Delete redirect', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_delete_form', 5), + 'access callback' => 'redirect_access', + 'access arguments' => array('delete', 5), + 'file' => 'redirect.admin.inc', + ); + $items['admin/config/search/redirect/settings'] = array( + 'title' => 'Settings', + 'description' => 'Configure behavior for URL redirects.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_settings_form'), + 'access arguments' => array('administer redirects'), + 'file' => 'redirect.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 50, + ); + + // If the database logging module is enabled, add special 404 listing pages. + if (module_exists('dblog')) { + $items['admin/config/search/redirect/404'] = array( + 'title' => 'Fix 404 pages', + 'description' => 'Add redirects for 404 pages.', + 'page callback' => 'redirect_404_list', + 'access arguments' => array('administer redirects'), + 'file' => 'redirect.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 20, + ); + $items['admin/reports/page-not-found/redirect'] = array( + 'title' => 'Fix 404 pages with URL redirects', + 'page callback' => 'drupal_goto', + 'page arguments' => array('admin/config/search/redirect/404'), + 'access arguments' => array('administer redirects'), + 'type' => MENU_LOCAL_ACTION, + ); + } + + // Devel generate integration. + if (module_exists('devel_generate')) { + $items['admin/config/development/generate/redirects'] = array( + 'title' => 'Generate redirects', + 'description' => 'Generate a given number of redirects. Optionally delete current redirects.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redirect_generate_form'), + 'access arguments' => array('administer redirects'), + 'file' => 'redirect.generate.inc', + ); + $items['admin/config/search/redirect/generate'] = $items['admin/config/development/generate/redirects']; + $items['admin/config/search/redirect/generate']['type'] = MENU_LOCAL_ACTION; + } + + return $items; +} + +function redirect_set_current_redirect($redirect) { + $static = &drupal_static(__FUNCTION__); + $static = $redirect; +} + +function redirect_get_current_redirect() { + $redirect = drupal_static('redirect_set_current_redirect', NULL); + + // If a redirect has not been set with redirect_set_current_redirect(), then + // attempt to find a redirect matching the current path, query string, and + // language code. + if (!isset($redirect)) { + $redirect = redirect_load_by_source(current_path(), $GLOBALS['language']->language, drupal_get_query_parameters()); + } + + // @todo Add an alter hook here? + return $redirect; +} + +/** + * Implements hook_url_inbound_alter(). + */ +function redirect_url_inbound_alter(&$path, $original_path, $path_language) { + // If the current path global does not exist, then drupal_get_path_alias() + // will fail. This condition only happens when $path is the front page. + // @todo Remove when http://drupal.org/node/1329914 is fixed in core. + if (empty($_GET['q'])) { + $_GET['q'] = variable_get('site_frontpage', 'node'); + return; + } + + // If the inbound path has been changed, then attempt to find a redirect + // matching the original path and save it for processing later in + // redirect_init(). For example, if the Sub-pathauto module changes the path + // 'foo/redirect' to 'node/1/redirect', and there is a redirect enabled for + // the path 'foo/redirect', then redirect_init() would normally fail since it + // would not find a match. + if ($path != $original_path && $original_path == current_path()) { + $current_langcode = !empty($path_language) ? $path_language : $GLOBALS['language']->language; + $current_query = drupal_get_query_parameters(); + if ($redirect = redirect_load_by_source($original_path, $current_langcode, $current_query)) { + redirect_set_current_redirect($redirect); + } + } + + // Check for empty path. + if (empty($path)) { + return; + } + // Do not redirect if $original_path does not match the requested url. + if ($original_path != $_GET['q']) { + return; + } + // Do not redirect if disallowed. + if (!redirect_can_redirect()) { + return; + } + + // Determine if front page. drupal_is_front_page() is not accurate here + // because drupal_path_initialize() has not executed yet. + $is_front_page = ($path == variable_get('site_frontpage', 'node') ? TRUE : FALSE); + + // Redirect the front page to the root level. + if ($is_front_page + && variable_get('redirect_global_home', 0) + && !variable_get('redirect_global_canonical_front', 0) + && base_path() != request_uri()) { + return redirect_redirect((object)array('redirect' => '', 'type' => 'global')); + } + + // Redirect to the canonical URL. + $alias = drupal_get_path_alias($path, $path_language); + if ((!$is_front_page && variable_get('redirect_global_canonical', 1) + || $is_front_page && variable_get('redirect_global_canonical_front', 0)) + && $alias != $path + && $alias != $original_path) { + return redirect_redirect((object)array('redirect' => $alias, 'type' => 'global')); + } + + // Redirect from default entity paths to the proper entity path. + if ($path_entity = redirect_load_entity_from_path($path)) { + $uri = entity_uri($path_entity['entity_type'], $path_entity['entity']); + if ($path != $uri['path']) { + return redirect_redirect((object)array('redirect' => $uri['path'], 'redirect_options' => $uri['options'], 'type' => 'global')); + } + } +} + +/** + * Implements hook_entity_info_alter(). + */ +function redirect_entity_info_alter(&$info) { + $default_paths = array( + 'node' => 'node/%node', + 'user' => 'user/%user', + 'taxonomy_term' => 'taxonomy/term/%taxonomy_term', + ); + + foreach ($default_paths as $entity_type => $default_path) { + if (isset($info[$entity_type]) && !isset($info[$entity_type]['default path'])) { + $info[$entity_type]['default path'] = $default_path; + } + } + + // Disable support for some entity types that cause problems. + $unsupported_entity_types = array( + 'comment', + 'media', + ); + foreach ($unsupported_entity_types as $unsupported_entity_type) { + if (isset($info[$unsupported_entity_type])) { + $info[$unsupported_entity_type]['redirect'] = FALSE; + } + } +} + +/** + * Check if an entity type supports redirects. + * + * @param $entity_type + * An entity type. + * + * @return + * TRUE if the entity type has an uri callback and supports redirects, or + * FALSE otherwise. + */ +function redirect_entity_type_supports_redirects($entity_type) { + $types = &drupal_static(__FUNCTION__); + + if (!isset($types)) { + $types = array(); + foreach (entity_get_info() as $type => $entity_info) { + $types[$type] = !empty($entity_info['uri callback']) && (!isset($entity_info['redirect']) || !empty($entity_info['redirect'])); + } + } + + return isset($types[$entity_type]) ? $types[$entity_type] : FALSE; +} + +/** + * Implements hook_init(). + */ +function redirect_init() { + if (!redirect_can_redirect()) { + return; + } + + // Fetch the current redirect. + if ($redirect = redirect_get_current_redirect()) { + redirect_redirect((object) reset($redirect)); + } + + // Get the request URI without the $base_path prefix. + if (isset($_REQUEST['q'])) { + $path = $_REQUEST['q']; + } + else { + // This is a request using a clean URL. Extract the path from request_uri(). + $request_path = strtok(request_uri(), '?'); + $base_path_len = drupal_strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); + // Unescape and strip $base_path prefix, leaving q without a leading slash. + $path = drupal_substr(urldecode($request_path), $base_path_len + 1); + } + $request_uri = $original_uri = ltrim($path, '/'); + + // Redirect from non-clean URLs to clean URLs. + if (variable_get('redirect_global_clean', 1) + && variable_get('clean_url', 0) + && strpos(request_uri(), '?q=') !== FALSE) { + redirect_redirect((object)array('redirect' => $request_uri, 'type' => 'global')); + } + + // Strip index.php + if (strpos(request_uri(), 'index.php') !== FALSE) { + if (!drupal_is_front_page() && variable_get('redirect_global_index', 0)) { + $request_uri = str_replace('index.php', '', $request_uri); + redirect_redirect((object)array('redirect' => $request_uri, 'type' => 'global')); + } + elseif (drupal_is_front_page() && variable_get('redirect_global_home', 0)) { + redirect_redirect((object)array('redirect' => '', 'type' => 'global')); + } + } + + // Deslash (Remove trailing slashes from paths). + $langcode = isset($options['language']->language) ? $options['language']->language : ''; + $alias = drupal_get_path_alias(current_path(), $langcode); + if (variable_get('redirect_global_deslash', 0) + && substr($request_uri, -1) == '/' + && $request_uri !== $alias) { + redirect_redirect((object)array('redirect' => rtrim($request_uri, '/'), 'type' => 'global')); + } + // Add slash (Add trailing slashes to paths). + elseif (variable_get('redirect_global_add_slash', 0) + && substr($request_uri, -1) !== '/' + && !drupal_is_front_page()) { + redirect_redirect((object)array('redirect' => $request_uri . '/', 'type' => 'global')); + } + +} + +/** + * Implements hook_cron(). + */ +function redirect_cron() { + // Purge inactive self-managed redirects from the database. + redirect_purge_inactive_redirects(); +} + +/** + * Implements hook_exit(). + */ +function redirect_exit($destination = NULL) { + // If the current page is being cached, track it. + if (drupal_get_http_header('Location') && $rid = drupal_get_http_header('X-Redirect-ID')) { + // Ensure the database is loaded. This is only the next bootstrap step + // after DRUPAL_BOOTSTRAP_DATABASE + drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); + db_update('redirect') + ->fields(array('access' => REQUEST_TIME)) + ->expression('count', 'count + 1') + ->condition('rid', $rid) + ->execute(); + } +} + +/** + * Implements hook_entity_delete(). + */ +function redirect_entity_delete($entity, $entity_type) { + if (redirect_entity_type_supports_redirects($entity_type)) { + redirect_delete_by_entity_path($entity_type, $entity); + } +} + +/** + * Implements hook_path_update(). + */ +function redirect_path_update(array $path) { + if (!variable_get('redirect_auto_redirect', TRUE)) { + return; + } + elseif (isset($path['redirect']) && !$path['redirect']) { + return; + } + + if (!empty($path['original']['pid']) && $path['original']['pid'] == $path['pid'] && $path['original']['alias'] != $path['alias']) { + $redirect = new stdClass(); + redirect_object_prepare($redirect); + $redirect->source = $path['original']['alias']; + $redirect->redirect = $path['source']; + $redirect->language = $path['original']['language']; + // Check if the redirect exists before saving. + $hash = redirect_hash($redirect); + if (!redirect_load_by_hash($hash)) { + redirect_save($redirect); + } + } +} + +/** + * Implements hook_path_delete(). + */ +function redirect_path_delete($path) { + if (!variable_get('redirect_auto_redirect', TRUE)) { + return; + } + elseif (isset($path['redirect']) && !$path['redirect']) { + return; + } + elseif (empty($path)) { + // @todo Remove this condition and allow $path to use an array type hint + // when http://drupal.org/node/1025904 is fixed. + return; + } + + // Redirect from a deleted alias to the system path. + //if (!redirect_load_by_source($path['alias'], $path['language'])) { + // $redirect = new stdClass(); + // redirect_object_prepare($redirect); + // $redirect->source = $path['alias']; + // $redirect->redirect = $path['source']; + // $redirect->language = $path['language']; + // redirect_save($redirect); + //} +} + +/** + * Implements hook_views_api(). + */ +function redirect_views_api() { + return array( + 'api' => 2, + 'path' => drupal_get_path('module', 'redirect') . '/views', + ); +} + +/** + * Implements hook_page_build(). + * + * Adds an action on 404 pages to create a redirect. + */ +function redirect_page_build(&$page) { + if (redirect_is_current_page_404() && user_access('administer redirects')) { + if (!isset($page['content']['system_main']['actions'])) { + $page['content']['system_main']['actions'] = array( + '#theme' => 'links', + '#links' => array(), + '#attributes' => array('class' => array('action-links')), + '#weight' => -100, + ); + } + // We cannot simply use current_path() because if a 404 path is set, then + // that value overrides whatever is in $_GET['q']. The + // drupal_deliver_html_page() function thankfully puts the original current + // path into $_GET['destination']. + $destination = drupal_get_destination(); + $page['content']['system_main']['actions']['#links']['add_redirect'] = array( + 'title' => t('Add URL redirect from this page to another location'), + 'href' => 'admin/config/search/redirect/add', + 'query' => array('source' => $destination['destination']) + drupal_get_destination(), + ); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds support for creating redirects if a node URL alias is changed. + */ +function redirect_form_node_form_alter(&$form, $form_state) { + if (!empty($form['path']['pid']['#value']) && !isset($form['path']['original'])) { + $form['path']['original'] = array('#type' => 'value', '#value' => path_load($form['path']['pid']['#value'])); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds support for creating redirects if a taxonomy term URL alias is changed. + */ +function redirect_form_taxonomy_form_term_alter(&$form, $form_state) { + if (!empty($form['path']['pid']['#value']) && !isset($form['path']['original'])) { + $form['path']['original'] = array('#type' => 'value', '#value' => path_load($form['path']['pid']['#value'])); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds support for creating redirects if an URL alias is changed. + */ +function redirect_form_path_admin_form_alter(&$form, $form_state) { + if (!empty($form['pid']['#value']) && !isset($form['original'])) { + $form['original'] = array('#type' => 'value', '#value' => path_load($form['pid']['#value'])); + } +} + +/** + * Load an URL redirect from the database. + * + * @param $rid + * The URL redirect ID. + * @param $reset + * Whether to reset the redirect_load_multiple cache. + * + * @return + * An URL redirect object, or FALSE if loading failed. + * + * @ingroup redirect_api + */ +function redirect_load($rid, $reset = FALSE) { + $redirects = entity_load('redirect', array($rid), array(), $reset); + return !empty($redirects) ? reset($redirects) : FALSE; +} + +/** + * Load an URL redirect from the database by {redirect}.hash. + * + * @param $hash + * The hash of the URL redirect. + * @param $reset + * Whether to reset the redirect_load_multiple cache. + * + * @return + * An URL redirect object, or FALSE if loading failed. + * + * @ingroup redirect_api + */ +function redirect_load_by_hash($hash, $reset = FALSE) { + $redirects = entity_load('redirect', FALSE, array('hash' => $hash), $reset); + return !empty($redirects) ? reset($redirects) : FALSE; +} + +/** + * Load multiple URL redirects from the database by {redirect}.source. + * + * @param $source + * The source of the URL redirect. + * + * @return + * An array of URL redirect objects indexed by redirect IDs. + * + * @see redirect_load_multiple() + * @see _redirect_uasort() + * @see redirect_compare_array_recursive() + * + * @ingroup redirect_api + */ +function redirect_load_by_source($source, $language = LANGUAGE_NONE, array $query = array()) { + // Run a case-insensitive query for matching RIDs first. + $rid_query = db_select('redirect'); + $rid_query->addField('redirect', 'rid'); + if ($source != variable_get('site_frontpage', 'node')) { + $rid_query->condition('source', db_like($source), 'LIKE'); + } + else { + $source_condition = db_or(); + $source_condition->condition('source', db_like($source), 'LIKE'); + $source_condition->condition('source', ''); + $rid_query->condition($source_condition); + } + $rid_query->condition('language', array($language, LANGUAGE_NONE)); + $rids = $rid_query->execute()->fetchCol(); + + if ($rids && $redirects = redirect_load_multiple($rids)) { + // Narrow down the list of candidates. + foreach ($redirects as $rid => $redirect) { + if (!empty($redirect->source_options['query'])) { + if (empty($query) || !redirect_compare_array_recursive($redirect->source_options['query'], $query)) { + unset($redirects[$rid]); + continue; + } + } + + // Add a case sensitive matches condition to be used in sorting. + if ($source !== $redirect->source) { + $redirects[$rid]->weight = 1; + } + } + + if (!empty($redirects)) { + // Sort the redirects in the proper order. + uasort($redirects, '_redirect_uasort'); + + // Allow other modules to alter the redirect candidates before selecting the top one. + $context = array('language' => $language, 'query' => $query); + drupal_alter('redirect_load_by_source', $redirects, $source, $context); + + return !empty($redirects) ? $redirects : FALSE; + } + } + + return FALSE; +} + +/** + * Load multiple URL redirects from the database. + * + * @param $rids + * An array of redirect IDs. + * @param $conditions + * An array of conditions on the {redirect} table in the form 'field' => + * $value. + * @param $reset + * Whether to reset the redirect_load_multiple cache. + * + * @return + * An array of URL redirect objects indexed by redirect IDs. + * + * @ingroup redirect_api + */ +function redirect_load_multiple($rids = array(), array $conditions = array(), $reset = FALSE) { + return entity_load('redirect', $rids, $conditions, $reset); +} + +/** + * Determine whether the current user may perform the given operation on the + * specified redirect. + * + * @param $op + * The operation to be performed on the redirect. Possible values are: + * - "create" + * - "update" + * - "delete" + * @param $redirect + * The redirect object on which the operation is to be performed, or redirect + * type (e.g. 'feedburner') for the "create" operation. + * @param $account + * Optional, a user object representing the user for whom the operation is to + * be performed. Determines access for a user other than the current user. + * + * @return + * TRUE if the operation may be performed, FALSE otherwise. + */ +function redirect_access($op, $redirect, $account = NULL) { + global $user; + + $rights = &drupal_static(__FUNCTION__, array()); + + if (!$redirect || !in_array($op, array('create', 'update', 'delete'), TRUE)) { + // If there was no redirect to check against, or the $op was not one of the + // supported ones, we return access denied. + return FALSE; + } + // If no user object is supplied, the access check is for the current user. + if (empty($account)) { + $account = $user; + } + + $cid = isset($redirect->rid) ? $redirect->rid : $redirect; + + // Return cached value if access already checked for this redirect, user and op. + if (isset($rights[$account->uid][$cid][$op])) { + return $rights[$account->uid][$cid][$op]; + } + + // Administrators can access all redirects. + if (user_access('administer redirects', $account)) { + $rights[$account->uid][$cid][$op] = TRUE; + return TRUE; + } + + // We grant access to the redirect if both of the following conditions are met: + // - No modules say to deny access. + // - At least one module says to grant access. + $access = module_invoke_all('redirect_access', $op, $redirect, $account); + if (in_array(REDIRECT_ACCESS_DENY, $access, TRUE)) { + $rights[$account->uid][$cid][$op] = FALSE; + return FALSE; + } + elseif (in_array(REDIRECT_ACCESS_ALLOW, $access, TRUE)) { + $rights[$account->uid][$cid][$op] = TRUE; + return TRUE; + } + + return FALSE; +} + +/** + * Validate a redirect. + */ +function redirect_validate($redirect, $form, &$form_state) { + $redirect = (object) $redirect; + + // check that there there are no redirect loops + if (url($redirect->source) == url($redirect->redirect)) { + form_set_error('redirect', t('You are attempting to redirect the page to itself. This will result in an infinite loop.')); + } + + redirect_hash($redirect); + if ($existing = redirect_load_by_hash($redirect->hash)) { + if ($redirect->rid != $existing->rid) { + form_set_error('source', t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => redirect_url($redirect->source, $redirect->source_options), '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid)))); + } + } + + // Allow other modules to validate the SSH public key. + foreach (module_implements('redirect_validate') as $module) { + $function = $module . '_redirect_validate'; + $function($redirect, $form, $form_state); + } +} + +function redirect_object_prepare(stdClass $redirect, $defaults = array()) { + $defaults += array( + 'rid' => NULL, + 'type' => 'redirect', + 'uid' => $GLOBALS['user']->uid, + 'source_options' => array(), + 'redirect_options' => array(), + 'language' => LANGUAGE_NONE, + 'status_code' => 0, + 'count' => 0, + 'access' => 0, + 'hash' => '', + ); + + foreach ($defaults as $key => $default) { + if (!isset($redirect->{$key})) { + $redirect->{$key} = $default; + } + } + + module_invoke_all('redirect_prepare', $redirect); +} + +/** + * Save an URL redirect. + * + * @param $redirect + * The URL redirect object to be saved. If $redirect->rid is omitted (or + * $redirect->is_new is TRUE), a new redirect will be added. + * + * @ingroup redirect_api + */ +function redirect_save($redirect) { + $transaction = db_transaction(); + + try { + if (!empty($redirect->rid) && !isset($redirect->original)) { + $redirect->original = entity_load_unchanged('redirect', $redirect->rid); + } + + // Determine if we will be inserting a new node. + if (!isset($redirect->is_new)) { + $redirect->is_new = empty($redirect->rid); + } + + // The changed timestamp is always updated for bookkeeping purposes. + //$redirect->changed = time(); + + redirect_hash($redirect); + if ($redirect->is_new || $redirect->hash != $redirect->original->hash) { + // Only new or changed redirects reset the last used value. + $redirect->count = 0; + $redirect->access = 0; + } + + // Allow other modules to alter the redirect before saving. + module_invoke_all('redirect_presave', $redirect); + module_invoke_all('entity_presave', $redirect, 'redirect'); + + // Save the redirect to the database and invoke the post-save hooks. + if ($redirect->is_new) { + drupal_write_record('redirect', $redirect); + module_invoke_all('redirect_insert', $redirect); + module_invoke_all('entity_insert', $redirect, 'redirect'); + } + else { + drupal_write_record('redirect', $redirect, array('rid')); + module_invoke_all('redirect_update', $redirect); + module_invoke_all('entity_update', $redirect, 'redirect'); + } + + // Clear internal properties. + unset($redirect->is_new); + unset($redirect->original); + + // Clear the static loading cache. + entity_get_controller('redirect')->resetCache(array($redirect->rid)); + + // 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('redirect', $e); + throw $e; + } +} + +/** + * Implements hook_redirect_insert(). + */ +function redirect_redirect_insert($redirect) { + redirect_page_cache_clear($redirect); +} + +/** + * Implements hook_redirect_update(). + */ +function redirect_redirect_update($redirect) { + redirect_page_cache_clear($redirect); + + // Clear the page cache for the original redirect as well. + if (!empty($redirect->original) && $redirect->original->source != $redirect->source) { + redirect_page_cache_clear($redirect->original); + } +} + +/** + * Implements hook_redirect_delete(). + */ +function redirect_redirect_delete($redirect) { + redirect_page_cache_clear($redirect); +} + +/** + * Delete a single URL redirect. + * + * @param $rid + * The ID of the redirect to delete. + * + * @ingroup redirect_api + */ +function redirect_delete($rid) { + return redirect_delete_multiple(array($rid)); +} + +/** + * Delete any redirects associated with a path or any of its sub-paths. + * + * Given a source like 'node/1' this function will delete any redirects that + * have that specific source or any sources that match 'node/1/%'. + * + * @param $path + * An string with an internal Drupal path. + * + * @ingroup redirect_api + */ +function redirect_delete_by_path($path) { + $query = db_select('redirect'); + $query->addField('redirect', 'rid'); + $query_or = db_or(); + $query_or->condition('source', db_like($path), 'LIKE'); + $query_or->condition('source', db_like($path . '/') . '%', 'LIKE'); + $query_or->condition('redirect', db_like($path), 'LIKE'); + $query_or->condition('redirect', db_like($path . '/') . '%', 'LIKE'); + $query->condition($query_or); + $rids = $query->execute()->fetchCol(); + + if ($rids) { + return redirect_delete_multiple($rids); + } +} + +/** + * Delete an entity URL alias and any of its sub-paths. + * + * This function also checks to see if the default entity URI is different from + * the current entity URI and will delete any of the default aliases. + * + * @param $entity_type + * A string with the entity type. + * @param $entity + * An entity object. + * + * @ingroup redirect_api + */ +function redirect_delete_by_entity_path($entity_type, $entity) { + $uri = entity_uri($entity_type, $entity); + if (!empty($uri['path'])) { + redirect_delete_by_path($uri['path']); + } + + //$info = entity_get_info($entity_type); + //if (isset($info['default path'])) { + // list($id) = entity_extract_ids($entity_type, $entity); + // $default_path = str_replace('[id]', $id, $info['default path']); + // if ($uri['path'] !== $default_path) { + // redirect_delete_by_path($default_path); + // } + //} +} + +/** + * Delete multiple URL redirects. + * + * @param $rids + * An array of redirect IDs to delete. + * + * @ingroup redirect_api + */ +function redirect_delete_multiple(array $rids) { + $transaction = db_transaction(); + if (!empty($rids)) { + $redirects = redirect_load_multiple($rids); + + try { + // Let modules react to the individual redirects being deleted. + foreach ($redirects as $rid => $redirect) { + module_invoke_all('redirect_delete', $redirect); + module_invoke_all('entity_delete', $redirect, 'redirect'); + } + + db_delete('redirect') + ->condition('rid', $rids, 'IN') + ->execute(); + } + catch (Exception $e) { + $transaction->rollback(); + watchdog_exception('redirect', $e); + throw $e; + } + + // Clear the redirect_load_multiple cache. + entity_get_controller('redirect')->resetCache(); + } +} + +/** + * Purge inactive redirects from the database. + * + * @param $types + * An array of redirect types to remove. Default is only the self-managed + * 'redirect'. If not provided all redirect types will be eligible for + * removal. + * @param $interval + * The number of seconds to subtract from the current time and used to + * find the inactive redirects. + * + * @return + * An array of redirect IDs that were deleted or FALSE if none were. + */ +function redirect_purge_inactive_redirects(array $types = array('redirect'), $interval = NULL) { + if (!isset($interval)) { + $interval = variable_get('redirect_purge_inactive', 0); + } + + if (!$interval || !variable_get('redirect_page_cache', 0) || !variable_get('page_cache_invoke_hooks', TRUE)) { + // If serving redirects from the page cache is enabled and hooks are not + // executed during page caching, then we cannot track when a redirect is + // used. Therefore, we cannot remove unused redirects. + return FALSE; + } + + $query = db_select('redirect'); + $query->addField('redirect', 'rid'); + if (!empty($types)) { + $query->condition('type', $types); + } + $query->condition('access', REQUEST_TIME - $interval, '<'); + $query->addTag('redirect_purge'); + $rids = $query->execute()->fetchCol(); + + if (count($rids)) { + redirect_delete_multiple($rids); + watchdog('redirect', format_plural(count($rids), 'Removed 1 inactive redirect from the database.', 'Removed @count inactive redirects from the database.')); + return $rids; + } +} + +/** + * Perform an URL redirect. + * + * @param $redirect + * An optional URL redirect array. + * + * @ingroup redirect_api + */ +function redirect_redirect(stdClass $redirect = NULL) { + // First check if we're in an infinite loop. + $session_id = session_id(); + if (flood_is_allowed('redirection', 5, 15, $session_id ? $session_id : NULL)) { + flood_register_event('redirection', 60, $session_id ? $session_id : NULL); + } + else { + trigger_error(t('Infinite redirect loop prevented.'), E_USER_WARNING); + return FALSE; + } + + if (!isset($redirect)) { + $redirect = new stdClass(); + } + redirect_object_prepare($redirect, array('redirect' => current_path(), 'type' => 'manual', 'callback' => 'redirect_goto', 'cache' => TRUE)); + + if (empty($redirect->status_code)) { + $redirect->status_code = variable_get('redirect_default_status_code', 301); + } + + if (variable_get('redirect_passthrough_querystring', 1)) { + // Preserve the current query parameters in the redirect. + $redirect->redirect_options += array('query' => array()); + $redirect->redirect_options['query'] += drupal_get_query_parameters(); + } + + // Prevent the destination query parameter from overriding this redirect. + //if (isset($_GET['destination'])) { + // Simply unset the parameter since it has already been passed into + // $options['query'] in the previous code. + // unset($_GET['destination']); + //} + + // Allow other modules to alter the redirect before passing to drupal_goto(). + drupal_alter('redirect', $redirect); + + // Continue if the redirect has not been disabled by hook_redirect_alter(). + if (isset($redirect->redirect) && isset($redirect->callback) && $redirect->redirect !== FALSE && function_exists($redirect->callback)) { + // Perform the actual redirect. + $callback = $redirect->callback; + $callback($redirect); + } +} + +/** + * Redirect callback; perform an URL redirect. + */ +function redirect_goto($redirect) { + $redirect->redirect_options['absolute'] = TRUE; + // Prevent a path like 'index.php?q=node/1' from redirecting to '?q=path-alias' + // if canonical redirection is disabled. This will make url() treat 'node/1' + // as if it is already an alias and prevent a drupal_get_path_alias() lookup. + if (!variable_get('redirect_global_canonical', 0)) { + $redirect->redirect_options['alias'] = TRUE; + } + $url = url($redirect->redirect, $redirect->redirect_options); + drupal_add_http_header('Location', $url); + drupal_add_http_header('Status', redirect_status_code_options($redirect->status_code)); + + if (!empty($redirect->rid)) { + // Add a custom header for the redirect ID so when the redirect is served + // from the page cache, we can track it. + drupal_add_http_header('X-Redirect-ID', $redirect->rid); + } + + if (!variable_get('redirect_page_cache', 0) || !variable_get('cache', 0) || !drupal_page_is_cacheable() || empty($redirect->cache)) { + drupal_exit($url); + } + + // @see drupal_exit() + if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) { + if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { + module_invoke_all('exit', $url); + } + drupal_session_commit(); + if (variable_get('cache', 0)) { + // We must output something to allow the request to be cached. + echo ' '; + if ($cache = drupal_page_set_cache()) { + // When caching this redirect for the first time we still need to ensure + // that the correct cache headers are sent. + // @see drupal_page_footer() + drupal_serve_page_from_cache($cache); + } + } + } + + exit; +} + +function redirect_hash($redirect) { + $hash = array( + 'source' => $redirect->source, + 'language' => $redirect->language, + ); + if (!empty($redirect->source_options['query'])) { + $hash['source_query'] = $redirect->source_options['query']; + } + drupal_alter('redirect_hash', $hash, $redirect); + redirect_sort_recursive($hash, 'ksort'); + $redirect->hash = drupal_hash_base64(serialize($hash)); + return $redirect->hash; +} + +/** + * Clear a page from the page cache. + */ +function redirect_page_cache_clear($redirect = NULL) { + if (!variable_get('redirect_page_cache', 0)) { + return; + } + + if (isset($redirect)) { + $path = url($redirect->source, array('absolute' => TRUE)); + // Use a wildcard to catch paths with query strings. + cache_clear_all($path, 'cache_page', TRUE); + } + else { + // Clear the entire page cache. + cache_clear_all('*', 'cache_page', TRUE); + } +} + +/** + * Given a path determine if it is an entity default path. + * + * @param $path + * The internal path. The id of the entity should be in the string as '[id]'. + * @return + * An array with the entity type and the loaded entity object. + */ +function redirect_load_entity_from_path($path) { + $entity_paths = &drupal_static(__FUNCTION__); + + if (!isset($entity_paths)) { + $entity_paths = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if (isset($entity_info['default path'])) { + $default_path = $entity_info['default path']; + $default_path = preg_quote($default_path, '/'); + $default_path = str_replace(preg_quote('%' . $entity_type, '/'), '(\d+)', $default_path); + $entity_paths[$entity_type] = $default_path; + } + } + } + + foreach ($entity_paths as $entity_type => $default_path) { + if (preg_match("/^{$default_path}$/", $path, $matches)) { + if ($entity = entity_load($entity_type, array($matches[1]))) { + return array('entity_type' => $entity_type, 'entity' => reset($entity)); + } + break; + } + } +} + +/** + * Check the ability to perform redirects with the current request context. + * + * This function checks the following conditions: + * - If the PHP entry point is the root index.php file. + * - If PHP is not running as CLI. + * - If the site is not offline or in install/update mode. + * - If the curerent page is not an admin page (check can be disabled). + * - If the current request does not have any POST data since a redirect + * may interrupt form submission. + * + * @return + * TRUE if redirections can be performed, or FALSE otherwise. + */ +function redirect_can_redirect() { + $can_redirect = &drupal_static(__FUNCTION__); + + if (!isset($can_redirect)) { + $path = current_path(); + $can_redirect = TRUE; + + if ($_SERVER['SCRIPT_NAME'] != $GLOBALS['base_path'] . 'index.php') { + // Do not redirect if the root script is not /index.php. + $can_redirect = FALSE; + } + elseif (!empty($_POST)) { + // Do not redirect if this is a post request with data. + $can_redirect = FALSE; + } + elseif (drupal_is_cli()) { + // If this is a command line request (Drush, etc), skip processing. + $can_redirect = FALSE; + } + elseif (variable_get('maintenance_mode', 0) || defined('MAINTENANCE_MODE')) { + // Do not redirect in offline or maintenance mode. + $can_redirect = FALSE; + } + elseif (!variable_get('redirect_global_admin_paths', 0) && path_is_admin($path)) { + // Do not redirect on admin paths. + $can_redirect = FALSE; + } + } + + return $can_redirect; +} + +/** + * Compare tha all values and associations in one array match another array. + * + * We cannot use array_diff_assoc() here because we need to be recursive. + * + * @param $match + * The array that has the values. + * @param $haystack + * The array that will be searched for values. + * @return + * TRUE if all the elements of $match were found in $haystack, or FALSE + * otherwise. + */ +function redirect_compare_array_recursive($match, $haystack) { + foreach ($match as $key => $value) { + if (!array_key_exists($key, $haystack)) { + return FALSE; + } + elseif (is_array($value)) { + if (!is_array($haystack[$key])) { + return FALSE; + } + elseif (!redirect_compare_array_recursive($value, $haystack[$key])) { + return FALSE; + } + } + elseif ($value != $haystack[$key]) { + return FALSE; + } + } + return TRUE; +} + +/** + * Sort an array recusively. + * + * @param $array + * The array to sort, by reference. + * @param $callback + * The sorting callback to use (e.g. 'sort', 'ksort', 'asort'). + * + * @return + * TRUE on success or FALSE on failure. + */ +function redirect_sort_recursive(&$array, $callback = 'sort') { + $result = $callback($array); + foreach ($array as $key => $value) { + if (is_array($value)) { + $result &= redirect_sort_recursive($array[$key], $callback); + } + } + return $result; +} + +/** + * Load a language object by its language code. + * + * @todo Remove when http://drupal.org/node/660736 is fixed in Drupal core. + * + * @param $language + * A language code. If not provided the default language will be returned. + * @return + * A language object. + */ +function redirect_language_load($language = LANGUAGE_NONE) { + $languages = &drupal_static(__FUNCTION__); + + if (!isset($languages)) { + $languages = language_list(); + $languages[LANGUAGE_NONE] = NULL; + } + + return isset($languages[$language]) ? $languages[$language] : NULL; +} + +/** + * Build the URL of a redirect for display purposes only. + */ +function redirect_url($path, array $options = array(), $clean_url = NULL) { + if (!isset($clean_url)) { + $clean_url = variable_get('clean_url', 0); + } + + if ($path == '') { + $path = '<front>'; + } + + if (!isset($options['alter']) || !empty($options['alter'])) { + drupal_alter('redirect_url', $path, $options); + } + + // The base_url might be rewritten from the language rewrite in domain mode. + if (!isset($options['base_url'])) { + if (isset($options['https']) && variable_get('https', FALSE)) { + if ($options['https'] === TRUE) { + $options['base_url'] = $GLOBALS['base_secure_url']; + $options['absolute'] = TRUE; + } + elseif ($options['https'] === FALSE) { + $options['base_url'] = $GLOBALS['base_insecure_url']; + $options['absolute'] = TRUE; + } + } + else { + $options['base_url'] = $GLOBALS['base_url']; + } + } + + if (empty($options['absolute']) || url_is_external($path)) { + $url = $path; + } + else { + $url = $options['base_url'] . base_path() . $path; + } + + if (isset($options['query'])) { + $url .= $clean_url ? '?' : '&'; + $url .= drupal_http_build_query($options['query']); + } + if (isset($options['fragment'])) { + $url .= '#' . $options['fragment']; + } + + return $url; +} + +function redirect_variables() { + return array( + 'redirect_default_status_code' => 301, + 'redirect_auto_redirect' => TRUE, + 'redirect_warning' => FALSE, + 'redirect_passthrough_querystring' => 1, + 'redirect_page_cache' => 0, + 'redirect_purge_inactive' => 0, + 'redirect_global_home' => 1, + 'redirect_global_index' => 0, + 'redirect_global_clean' => 1, + 'redirect_global_canonical' => 1, + 'redirect_global_canonical_front' => 0, + 'redirect_global_deslash' => 1, + 'redirect_global_add_slash' => 0, + 'redirect_global_admin_paths' => 0, + ); +} + +//function redirect_get_redirect_info() { +// $info = &drupal_static(__FUNCTION__); +// +// if (!isset($info)) { +// if ($cache = cache_get('redirect:info')) { +// $info = $cache->data; +// } +// else { +// $info = module_invoke_all('redirect_info'); +// drupal_alter('redirect_info', $info); +// cache_set('redirect:info', $info); +// } +// } +// +// return $info; +//} + +function redirect_parse_url($url) { + $original_url = $url; + $url = trim($url, " \t\n\r\0\x0B\/"); + $parsed = parse_url($url); + + if (isset($parsed['fragment'])) { + $url = substr($url, 0, -strlen($parsed['fragment'])); + $url = trim($url, '#'); + } + if (isset($parsed['query'])) { + $url = substr($url, 0, -strlen($parsed['query'])); + $url = trim($url, '?&'); + $parsed['query'] = drupal_get_query_array($parsed['query']); + } + + // Convert absolute to relative. + if (isset($parsed['scheme']) && isset($parsed['host'])) { + $base_secure_url = rtrim($GLOBALS['base_secure_url'] . base_path(), '/'); + $base_insecure_url = rtrim($GLOBALS['base_insecure_url'] . base_path(), '/'); + if (strpos($url, $base_secure_url) === 0) { + $url = str_replace($base_secure_url, '', $url); + $parsed['https'] = TRUE; + } + elseif (strpos($url, $base_insecure_url) === 0) { + $url = str_replace($base_insecure_url, '', $url); + } + } + + $url = trim($url, '/'); + + // Convert to frontpage paths. + if ($url == '<front>') { + $url = ''; + } + + //$parsed['url'] = http_build_query($url, HTTP_URL_STRIP_QUERY | HTTP_URL_STRIP_FRAGMENT); + $parsed['url'] = $url; + + // Allow modules to alter the parsed URL. + drupal_alter('redirect_parse_url', $parsed, $original_url); + + return $parsed; +} + +function redirect_status_code_options($code = NULL) { + $codes = array( + 300 => t('300 Multiple Choices'), + 301 => t('301 Moved Permanently'), + 302 => t('302 Found'), + 303 => t('303 See Other'), + 304 => t('304 Not Modified'), + 305 => t('305 Use Proxy'), + 307 => t('307 Temporary Redirect'), + ); + return isset($codes[$code]) ? $codes[$code] : $codes; +} + +/** + * Returns if the current page request is a page not found (404 status error). + * + * Why the fuck do we have to do this? Why is there not an easier way??? + * + * @return + * TRUE if the current page is a 404, or FALSE otherwise. + */ +function redirect_is_current_page_404() { + return drupal_get_http_header('Status') == '404 Not Found'; +} + +/** + * uasort callback; Compare redirects based on language neutrality and rids. + */ +function _redirect_uasort($a, $b) { + $a_weight = isset($a->weight) ? $a->weight : 0; + $b_weight = isset($b->weight) ? $b->weight : 0; + if ($a_weight != $b_weight) { + // First sort by weight (case sensitivity). + return $a_weight > $b_weight; + } + elseif ($a->language != $b->language) { + // Then sort by language specific over language neutral. + return $a->language == LANGUAGE_NONE; + } + elseif (!empty($a->source_options['query']) != !empty($b->source_options['query'])) { + // Then sort by redirects that do not have query strings over ones that do. + return empty($a->source_options['query']); + } + else { + // Lastly sort by the highest redirect ID. + return $a->rid < $b->rid; + } +} + +/** + * Implements hook_form_FORM_ID_alter() on behalf of locale.module. + */ +function locale_form_redirect_edit_form_alter(&$form, &$form_state) { + $form['language'] = array( + '#type' => 'select', + '#title' => t('Language'), + '#options' => array(LANGUAGE_NONE => t('All languages')) + locale_language_list('name'), + '#default_value' => $form['language']['#value'], + '#description' => t('A redirect set for a specific language will always be used when requesting this page in that language, and takes precedence over redirects set for <em>All languages</em>.'), + ); +} + +/** + * Implements hook_field_attach_form(). + * + * @todo Investigate using hook_entity_load() to load all entity redirects. + * @todo Figure out how to support entity URIs that contain query strings. + */ +function redirect_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { + list($id) = entity_extract_ids($entity_type, $entity); + if (!empty($form['redirect']) || empty($id)) { + return; + } + + // Check if this entity type supports redirects. + if (!redirect_entity_type_supports_redirects($entity_type)) { + return; + } + + $uri = entity_uri($entity_type, $entity); + if (empty($uri['path'])) { + // If the entity has no source path, then we cannot lookup the existing + // redirects. + return; + } + + $info = entity_get_info($entity_type); + $form['redirect'] = array( + '#type' => 'fieldset', + '#title' => t('URL redirects'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#access' => user_access('administer redirects'), + '#weight' => 30, + '#attributes' => array('class' => array('redirect-list')), + ); + + // Only support vertical tabs if there is a vertical tab element. + foreach (element_children($form) as $key) { + if (isset($form[$key]['#type']) && $form[$key]['#type'] == 'vertical_tabs') { + $form['redirect']['#group'] = $key; + $form['redirect']['#attached']['js']['vertical-tabs'] = drupal_get_path('module', 'redirect') . '/redirect.js'; + } + } + + $redirect = array( + 'redirect' => $uri['path'], + 'redirect_options' => array_diff_key($uri['options'], array('entity_type' => '', 'entity' => '')), + 'language' => $langcode, + ); + + $form['redirect']['actions'] = array( + '#theme' => 'links', + '#links' => array(), + '#attributes' => array('class' => array('action-links')), + ); + if (redirect_access('create', 'redirect')) { + $form['redirect']['actions']['#links']['add'] = array( + 'title' => t('Add URL redirect to this @entitytype', array('@entitytype' => drupal_strtolower($info['label']))), + 'href' => 'admin/config/search/redirect/add', + 'query' => array_filter($redirect) + drupal_get_destination(), + ); + } + + // We don't have to put our include in $form_state['build_info']['files'] + // since the build array will already be cached. + module_load_include('inc', 'redirect', 'redirect.admin'); + $redirects = redirect_load_multiple(FALSE, array('redirect' => $uri['path'])); + $header = array('source', 'status_code', 'language', 'count', 'access', 'operations'); + $form['redirect'] += redirect_list_table($redirects, $header); +} + +/** + * Implements hook_field_extra_fields(). + */ +function redirect_field_extra_fields() { + $entity_info = entity_get_info(); + foreach (array_keys($entity_info) as $entity_type) { + if ($entity_type == 'comment') { + // The comment entity type supports URIs, but they're not real. + continue; + } + foreach (array_keys($entity_info[$entity_type]['bundles']) as $bundle) { + if (!isset($entity_info[$entity_type]['bundles'][$bundle]['uri callback']) && !isset($entity_info[$entity_type]['uri callback'])) { + // The bundle or base entity must have an URI callback defined otherwise + // we cannot use the entity_uri() function to lookup the entity's source + // path. + continue; + } + $info[$entity_type][$bundle]['form']['redirect'] = array( + 'label' => t('URL redirects'), + 'description' => t('Redirect module form elements'), + 'weight' => 30, + ); + } + } + return $info; +} + +/** + * Fetch an array of redirect bulk operations. + * + * @see hook_redirect_operations() + * @see hook_redirect_operations_alter() + */ +function redirect_get_redirect_operations() { + $operations = &drupal_static(__FUNCTION__); + + if (!isset($operations)) { + $operations = module_invoke_all('redirect_operations'); + drupal_alter('redirect_operations', $operations); + } + + return $operations; +} + +/** + * Implements hook_redirect_operations(). + */ +function redirect_redirect_operations() { + $operations['delete'] = array( + 'action' => t('Delete'), + 'action_past' => t('Deleted'), + 'callback' => 'redirect_delete_multiple', + 'confirm' => TRUE, + ); + return $operations; +} diff --git a/sites/all/modules/redirect/redirect.test b/sites/all/modules/redirect/redirect.test new file mode 100644 index 0000000000000000000000000000000000000000..1c237f83bc803ce8365723340bd4e48af51302ad --- /dev/null +++ b/sites/all/modules/redirect/redirect.test @@ -0,0 +1,244 @@ +<?php + +/** + * @file + * Unit tests for the redirect module. + */ + +class RedirectTestHelper extends DrupalWebTestCase { + function setUp(array $modules = array()) { + array_unshift($modules, 'redirect'); + parent::setUp($modules); + } + + protected function assertRedirect($redirect) { + $source_url = url($redirect->source, array('absolute' => TRUE) + $redirect->source_options); + $redirect_url = url($redirect->redirect, array('absolute' => TRUE) + $redirect->redirect_options); + $this->drupalGet($source_url); + $this->assertEqual($this->getUrl(), $redirect_url, t('Page %source was redirected to %redirect.', array('%source' => $source_url, '%redirect' => $redirect_url))); + + // Reload the redirect. + if (!empty($redirect->rid)) { + return redirect_load($redirect->rid); + } + } + + protected function assertNoRedirect($redirect) { + $source_url = url($redirect->source, array('absolute' => TRUE) + $redirect->source_options); + $this->drupalGet($source_url); + $this->assertEqual($this->getUrl(), $source_url, t('Page %url was not redirected.', array('%url' => $source_url))); + } + + /** + * Add an URL redirection + * + * @param $source + * A source path. + * @param $redirect + * A redirect path. + */ + protected function addRedirect($source_path, $redirect_path, array $redirect = array()) { + $source_parsed = redirect_parse_url($source_path); + $redirect['source'] = $source_parsed['url']; + if (isset($source_parsed['query'])) { + $redirect['source_options']['query'] = $source_parsed['query']; + } + + $redirect_parsed = redirect_parse_url($redirect_path); + $redirect['redirect'] = $redirect_parsed['url']; + if (isset($redirect_parsed['query'])) { + $redirect['redirect_options']['query'] = $redirect_parsed['query']; + } + if (isset($redirect_parsed['fragment'])) { + $redirect['redirect_options']['fragment'] = $redirect_parsed['fragment']; + } + + $redirect_object = new stdClass(); + redirect_object_prepare($redirect_object, $redirect); + redirect_save($redirect_object); + return $redirect_object; + } + + protected function assertPageCached($url, array $options = array()) { + $options['absolute'] = TRUE; + $url = url($url, $options); + $cache = cache_get($url, 'cache_page'); + $this->assertTrue($cache, t('Page %url was cached.', array('%url' => $url))); + return $cache; + } + + protected function assertPageNotCached($url, array $options = array()) { + $options['absolute'] = TRUE; + $url = url($url, $options); + $cache = cache_get($url, 'cache_page'); + $this->assertFalse($cache, t('Page %url was not cached.', array('%url' => $url))); + } + + protected function assertHeader($name, $expected, $headers = NULL) { + if (!isset($headers)) { + $headers = $this->drupalGetHeaders(); + $name = strtolower($name); + } + return $this->assertIdentical($headers[$name], $expected); + } +} + +class RedirectUnitTest extends RedirectTestHelper { + public static function getInfo() { + return array( + 'name' => 'Redirect unit tests', + 'description' => 'Test basic functions and functionality.', + 'group' => 'Redirect', + ); + } + + /** + * Test the redirect_compare_array_recursive() function. + */ + function testCompareArrayRecursive() { + $haystack = array('a' => 'aa', 'b' => 'bb', 'c' => array('c1' => 'cc1', 'c2' => 'cc2')); + $cases = array( + array('query' => array('a' => 'aa', 'b' => 'invalid'), 'result' => FALSE), + array('query' => array('b' => 'bb', 'b' => 'bb'), 'result' => TRUE), + array('query' => array('b' => 'bb', 'c' => 'invalid'), 'result' => FALSE), + array('query' => array('b' => 'bb', 'c' => array()), 'result' => TRUE), + array('query' => array('b' => 'bb', 'c' => array('invalid')), 'result' => FALSE), + array('query' => array('b' => 'bb', 'c' => array('c2' => 'invalid')), 'result' => FALSE), + array('query' => array('b' => 'bb', 'c' => array('c2' => 'cc2')), 'result' => TRUE), + ); + foreach ($cases as $index => $case) { + $this->assertEqual($case['result'], redirect_compare_array_recursive($case['query'], $haystack)); + } + } + + /** + * Test redirect_sort_recursive(). + */ + function testSortRecursive() { + $test_cases = array( + array( + 'input' => array('b' => 'aa', 'c' => array('c2' => 'aa', 'c1' => 'aa'), 'a' => 'aa'), + 'expected' => array('a' => 'aa', 'b' => 'aa', 'c' => array('c1' => 'aa', 'c2' => 'aa')), + 'callback' => 'ksort', + ), + ); + foreach ($test_cases as $index => $test_case) { + $output = $test_case['input']; + redirect_sort_recursive($output, $test_case['callback']); + $this->assertIdentical($output, $test_case['expected']); + } + } + + /** + * Test redirect_parse_url(). + */ + function testParseURL() { + //$test_cases = array( + // array( + // 'input' => array('b' => 'aa', 'c' => array('c2' => 'aa', 'c1' => 'aa'), 'a' => 'aa'), + // 'expected' => array('a' => 'aa', 'b' => 'aa', 'c' => array('c1' => 'aa', 'c2' => 'aa')), + // ), + //); + //foreach ($test_cases as $index => $test_case) { + // $output = redirect_parse_url($test_case['input']); + // $this->assertIdentical($output, $test_case['expected']); + //} + } +} + +class RedirectFunctionalTest extends RedirectTestHelper { + private $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Redirect functional tests', + 'description' => 'Test interface functionality.', + 'group' => 'Redirect', + ); + } + + function setUp(array $modules = array()) { + parent::setUp($modules); + + $this->admin_user = $this->drupalCreateUser(array('administer redirects', 'access site reports', 'access content', 'create article content', 'edit any article content', 'create url aliases')); + $this->drupalLogin($this->admin_user); + } + + function test404Interface() { + // Check that 404 pages do get add redirect links for admin users. + $this->drupalGet('invalid-path1'); + $this->drupalGet('invalid-path2'); + $this->assertLink('Add URL redirect from this page to another location'); + + // Check that 403 pages do not get the add redirect link at all. + $this->drupalGet('admin/config/system/actions'); + $this->assertNoLink('Add URL redirect from this page to another location'); + + $this->drupalGet('admin/reports/page-not-found'); + $this->clickLink('Fix 404 pages with URL redirects'); + + // Check that normal users do not see the add redirect link on 404 pages. + $this->drupalLogout(); + $this->drupalGet('invalid-path3'); + $this->assertNoLink('Add an URL redirect from this page to another location'); + } + + function testPageCache() { + // Set up cache variables. + variable_set('cache', 1); + $edit = array( + 'redirect_page_cache' => TRUE, + 'redirect_purge_inactive' => 604800, + ); + $this->drupalPost('admin/config/search/redirect/settings', $edit, 'Save configuration'); + $this->assertText('The configuration options have been saved.'); + $this->drupalLogout(); + + // Add a new redirect. + $redirect = $this->addRedirect('redirect', 'node'); + $this->assertEqual($redirect->access, 0); + $this->assertEqual($redirect->count, 0); + $this->assertPageNotCached('redirect'); + + // Perform the redirect and check that last_used + $redirect = $this->assertRedirect($redirect); + $this->assertEqual($redirect->count, 1); + $this->assertTrue($redirect->access > 0); + $cache = $this->assertPageCached('redirect'); + $this->assertHeader('Location', url('node', array('absolute' => TRUE)), $cache->data['headers']); + $this->assertHeader('X-Redirect-ID', $redirect->rid, $cache->data['headers']); + + // Set a redirect to not used in a while and disable running bootstrap + // hooks during cache page serve. Running cron to remove inactive redirects + // should not remove since they cannot be tracked. + $redirect->access = 1; + redirect_save($redirect); + variable_set('page_cache_invoke_hooks', FALSE); + $this->cronRun(); + $this->assertRedirect($redirect); + + $redirect->access = 1; + redirect_save($redirect); + variable_set('page_cache_invoke_hooks', TRUE); + $this->cronRun(); + $this->assertNoRedirect($redirect); + } + + function testPathChangeRedirects() { + // Create an initial article node with a path alias. + $node = $this->drupalCreateNode(array('type' => 'article', 'path' => array('alias' => 'first-alias'))); + + // Change the node's alias will create an automatic redirect from 'first-alias' to the node. + $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'second-alias'), 'Save'); + //$redirect = redirect_load_by_source('first-alias'); + //$this->assertRedirect($redirect); + + $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'first-alias'), 'Save'); + //$redirect = redirect_load_by_source('second-alias'); + //$this->assertRedirect($redirect); + + $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'second-alias'), 'Save'); + //$redirect = redirect_load_by_source('first-alias'); + //$this->assertRedirect($redirect); + } +} diff --git a/sites/all/modules/redirect/views/redirect.views.inc b/sites/all/modules/redirect/views/redirect.views.inc new file mode 100644 index 0000000000000000000000000000000000000000..86cdc1b4763ac8755ccff3630e8e195695efc670 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect.views.inc @@ -0,0 +1,219 @@ +<?php + +/** + * @file + * Views integration and data for the redirect module. + */ + +/** + * Implements hook_views_data(). + */ +function redirect_views_data() { + // Basic table information. + $data['redirect']['table']['group'] = t('Redirect'); + + // Advertise this table as a possible base table + $data['redirect']['table']['base'] = array( + 'field' => 'rid', + 'title' => t('URL redirects'), + 'help' => t('Listings of URL redirects.'), + 'weight' => 10, + ); + + // {redirect}.rid + $data['redirect']['rid'] = array( + 'title' => t('Redirect ID'), + 'help' => t('The internal ID of the redirect.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + 'allow empty' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + ); + + // {redirect}.type + $data['redirect']['type'] = array( + 'title' => t('Type'), + 'help' => t('The type of redirect.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'redirect_handler_filter_redirect_type', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // {redirect}.uid + $data['users']['table']['join']['redirect'] = array( + 'left_field' => 'uid', + 'field' => 'uid', + ); + $data['redirect']['uid'] = array( + 'title' => t('User ID'), + 'help' => t('ID of user who created the URL redirect.'), + 'field' => array( + 'handler' => 'views_handler_field_user', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'handler' => 'views_handler_argument_user_uid', + 'name field' => 'name', + ), + 'filter' => array( + 'title' => t('Name'), + 'handler' => 'views_handler_filter_user_name', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'base' => 'users', + 'base field' => 'uid', + 'label' => t('user'), + ), + ); + $data['redirect']['uid_current'] = array( + 'real field' => 'uid', + 'title' => t('Current user'), + 'help' => t('Filter the view to the currently logged in user.'), + 'filter' => array( + 'handler' => 'views_handler_filter_user_current', + 'type' => 'yes-no', + ), + ); + + // {redirect}.source + $data['redirect']['source'] = array( + 'title' => t('Source URL'), + 'help' => t('The source URL which generates a redirect'), + 'field' => array( + 'handler' => 'redirect_handler_field_redirect_source', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // {redirect}.redirect + $data['redirect']['redirect'] = array( + 'title' => t('Redirect URL'), + 'help' => t('The destination URL'), + 'field' => array( + 'handler' => 'redirect_handler_field_redirect_redirect', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + // Language field + if (module_exists('locale')) { + $data['redirect']['language'] = array( + 'title' => t('Language'), + 'help' => t('The language the redirect is for.'), + 'field' => array( + 'handler' => 'views_handler_field_locale_language', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_locale_language', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_locale_language', + ), + ); + } + + // {redirect}.count + $data['redirect']['count'] = array( + 'title' => t('Clicks'), + 'help' => t('Number of times this URL redirect has been followed.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + 'allow empty' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + ); + + // {redirect}.access + $data['redirect']['access'] = array( + 'title' => t('Last accessed date'), + 'help' => t('The date/time the URL redirect was last accessed.'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_date', + ), + ); + + $data['redirect']['operations'] = array( + 'field' => array( + 'title' => t('Operations'), + 'help' => t('Provide links to operations the user can perform for the URL redirect.'), + 'handler' => 'redirect_handler_field_redirect_operations', + ), + ); + + $data['redirect']['edit_redirect'] = array( + 'field' => array( + 'title' => t('Edit link'), + 'help' => t('Provide a simple link to edit the URL redirect.'), + 'handler' => 'redirect_handler_field_redirect_link_edit', + ), + ); + + $data['redirect']['delete_redirect'] = array( + 'field' => array( + 'title' => t('Delete link'), + 'help' => t('Provide a simple link to delete the URL redirect.'), + 'handler' => 'redirect_handler_field_redirect_link_delete', + ), + ); + + return $data; +} diff --git a/sites/all/modules/redirect/views/redirect.views_default.inc b/sites/all/modules/redirect/views/redirect.views_default.inc new file mode 100644 index 0000000000000000000000000000000000000000..ec663bd65a158347a636cb1b40a361d0d9d14374 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect.views_default.inc @@ -0,0 +1,16 @@ +<?php + +/** + * Implements hook_views_default_views(). + */ +function redirect_views_default_views() { + $views = array(); + $files = file_scan_directory(drupal_get_path('module', 'redirect') . '/views', '/\.view$/'); + foreach ($files as $path => $file) { + require $path; + if (isset($view)) { + $views[$view->name] = $view; + } + } + return $views; +} diff --git a/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_delete.inc b/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_delete.inc new file mode 100644 index 0000000000000000000000000000000000000000..0d28927ce3ab2a7b77d4d901ddcf509ab7f9acc0 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_delete.inc @@ -0,0 +1,41 @@ +<?php + +/** + * @file + * Redirect field handler for edit links. + */ + +class redirect_handler_field_redirect_link_delete extends views_handler_field { + function construct() { + parent::construct(); + $this->additional_fields['rid'] = 'rid'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['text'] = array('default' => '', 'translatable' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $this->options['text'], + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $rid = $values->{$this->aliases['rid']}; + if (($redirect = redirect_load($rid)) && redirect_access('delete', $redirect)) { + $text = !empty($this->options['text']) ? $this->options['text'] : t('Delete'); + return l($text, "admin/config/search/redirect/delete/" . $rid, array('query' => drupal_get_destination())); + } + } +} diff --git a/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_edit.inc b/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_edit.inc new file mode 100644 index 0000000000000000000000000000000000000000..8724702636840a932280ac95d799c98691c2ef75 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_field_redirect_link_edit.inc @@ -0,0 +1,41 @@ +<?php + +/** + * @file + * Redirect field handler for edit links. + */ + +class redirect_handler_field_redirect_link_edit extends views_handler_field { + function construct() { + parent::construct(); + $this->additional_fields['rid'] = 'rid'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['text'] = array('default' => '', 'translatable' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $this->options['text'], + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $rid = $values->{$this->aliases['rid']}; + if (($redirect = redirect_load($rid)) && redirect_access('update', $redirect)) { + $text = !empty($this->options['text']) ? $this->options['text'] : t('Edit'); + return l($text, "admin/config/search/redirect/edit/" . $rid, array('query' => drupal_get_destination())); + } + } +} diff --git a/sites/all/modules/redirect/views/redirect_handler_field_redirect_operations.inc b/sites/all/modules/redirect/views/redirect_handler_field_redirect_operations.inc new file mode 100644 index 0000000000000000000000000000000000000000..23af5075e5b49ced983c4cdb0dd14f13516be2b0 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_field_redirect_operations.inc @@ -0,0 +1,68 @@ +<?php + +/** + * @file + * Redirect field handler for redirect operations. + */ + +class redirect_handler_field_redirect_operations extends views_handler_field { + function construct() { + parent::construct(); + $this->additional_fields['rid'] = 'rid'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['edit_text'] = array('default' => '', 'translatable' => TRUE); + $options['delete_text'] = array('default' => '', 'translatable' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['edit_text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display for edit links'), + '#default_value' => $this->options['edit_text'], + ); + $form['delete_text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display for delete links'), + '#default_value' => $this->options['delete_text'], + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $rid = $values->{$this->aliases['rid']}; + $redirect = redirect_load($rid); + $destination = drupal_get_destination(); + + $operations = array(); + if (redirect_access('update', $redirect)) { + $operations['edit'] = array( + 'title' => !empty($this->options['edit_text']) ? $this->options['edit_text'] : t('Edit'), + 'href' => 'admin/config/search/redirect/edit/' . $rid, + 'query' => $destination, + ); + } + if (redirect_access('delete', $redirect)) { + $operations['delete'] = array( + 'title' => !empty($this->options['delete_text']) ? $this->options['delete_text'] : t('Delete'), + 'href' => 'admin/config/search/redirect/delete/' . $rid, + 'query' => $destination, + ); + } + + if (!empty($operations)) { + return theme('links', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline', 'nowrap')))); + } + else { + return ''; + } + } +} diff --git a/sites/all/modules/redirect/views/redirect_handler_field_redirect_redirect.inc b/sites/all/modules/redirect/views/redirect_handler_field_redirect_redirect.inc new file mode 100644 index 0000000000000000000000000000000000000000..a5adb9eb75400869c2b689bf1d7e0bfb5b3842d2 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_field_redirect_redirect.inc @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Redirect field handler for {redirect}.redirect. + */ + +class redirect_handler_field_redirect_redirect extends views_handler_field { + function construct() { + parent::construct(); + $this->additional_fields['redirect'] = 'redirect'; + $this->additional_fields['redirect_options'] = 'redirect_options'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['text'] = array('default' => '', 'translatable' => TRUE); + $options['absolute'] = array('default' => FALSE); + $options['alter']['contains']['make_link']['default'] = TRUE; + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // This field will never be empty + $form['empty']['#access'] = FALSE; + $form['empty_zero']['#access'] = FALSE; + $form['hide_empty']['#access'] = FALSE; + + $form['alter']['make_link']['#description'] = t('If checked, this field will be made into a link.'); + $form['alter']['absolute']['#access'] = FALSE; + $form['alter']['path']['#access'] = FALSE; + + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $this->options['text'], + ); + $form['absolute'] = array( + '#type' => 'checkbox', + '#title' => t('Use absolute link (begins with "http://")'), + '#default_value' => $this->options['absolute'], + '#description' => t('If you want to use this as in "output this field as link" in "link path", you have to enabled this option.'), + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $redirect = $values->{$this->aliases['redirect']}; + $redirect_options = unserialize($values->{$this->aliases['redirect_options']}); + $redirect_options['absolute'] = !empty($this->options['absolute']); + + $url = redirect_url($redirect, $redirect_options); + $text = !empty($this->options['text']) ? $this->options['text'] : $url; + + if (!empty($this->options['alter']['make_link'])) { + $this->options['alter']['path'] = $url; + $this->options['alter']['absolute'] = $redirect_options['absolute']; + } + else { + $text = check_plain($text); + } + + return $text; + } +} diff --git a/sites/all/modules/redirect/views/redirect_handler_field_redirect_source.inc b/sites/all/modules/redirect/views/redirect_handler_field_redirect_source.inc new file mode 100644 index 0000000000000000000000000000000000000000..318f1bdb0d911b28fc91b2db76711a5dd1e01743 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_field_redirect_source.inc @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Redirect field handler for {redirect}.source. + */ + +class redirect_handler_field_redirect_source extends views_handler_field { + function construct() { + parent::construct(); + $this->additional_fields['source'] = 'source'; + $this->additional_fields['source_options'] = 'source_options'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['text'] = array('default' => '', 'translatable' => TRUE); + $options['absolute'] = array('default' => FALSE); + $options['alter']['contains']['make_link']['default'] = TRUE; + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // This field will never be empty + $form['empty']['#access'] = FALSE; + $form['empty_zero']['#access'] = FALSE; + $form['hide_empty']['#access'] = FALSE; + + $form['alter']['make_link']['#description'] = t('If checked, this field will be made into a link.'); + $form['alter']['absolute']['#access'] = FALSE; + $form['alter']['path']['#access'] = FALSE; + + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $this->options['text'], + ); + $form['absolute'] = array( + '#type' => 'checkbox', + '#title' => t('Use absolute link (begins with "http://")'), + '#default_value' => $this->options['absolute'], + '#description' => t('If you want to use this as in "output this field as link" in "link path", you have to enabled this option.'), + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $source = $values->{$this->aliases['source']}; + $source_options = unserialize($values->{$this->aliases['source_options']}); + $source_options['absolute'] = !empty($this->options['absolute']); + + $url = redirect_url($source, $source_options); + $text = !empty($this->options['text']) ? $this->options['text'] : $url; + + if (!empty($this->options['alter']['make_link'])) { + $this->options['alter']['path'] = $url; + $this->options['alter']['absolute'] = $source_options['absolute']; + } + else { + $text = check_plain($text); + } + + return $text; + } +} diff --git a/sites/all/modules/redirect/views/redirect_handler_filter_redirect_type.inc b/sites/all/modules/redirect/views/redirect_handler_filter_redirect_type.inc new file mode 100644 index 0000000000000000000000000000000000000000..a559ac36bf75b44e83b87833080a8e11dbebbff0 --- /dev/null +++ b/sites/all/modules/redirect/views/redirect_handler_filter_redirect_type.inc @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Filter by redirect type. + */ + +class redirect_handler_filter_redirect_type extends views_handler_filter_in_operator { + function get_value_options() { + if (!isset($this->value_options)) { + $this->value_title = t('Redirect type'); + $options = array(); + $types = db_query("SELECT DISTINCT type FROM {redirect}")->fetchCol(); + foreach ($types as $type) { + $options[$type] = t(drupal_ucfirst($type)); + } + $this->value_options = $options; + } + } +} diff --git a/sites/all/modules/redirect/views/redirects.view b/sites/all/modules/redirect/views/redirects.view new file mode 100644 index 0000000000000000000000000000000000000000..fd9e777f5b2310aa9516c343d66e258903cd03a6 --- /dev/null +++ b/sites/all/modules/redirect/views/redirects.view @@ -0,0 +1,177 @@ +<?php + +$view = new view(); +$view->name = 'redirects'; +$view->description = 'Displays a list of redirects on user and admin pages.'; +$view->tag = ''; +$view->base_table = 'redirect'; +$view->human_name = 'Redirects'; +$view->core = 0; +$view->api_version = '3.0'; +$view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */ + +/* Display: Defaults */ +$handler = $view->new_display('default', 'Defaults', 'default'); +$handler->display->display_options['use_more_always'] = FALSE; +$handler->display->display_options['access']['type'] = 'perm'; +$handler->display->display_options['access']['perm'] = 'administer redirects'; +$handler->display->display_options['cache']['type'] = 'none'; +$handler->display->display_options['query']['type'] = 'views_query'; +$handler->display->display_options['exposed_form']['type'] = 'basic'; +$handler->display->display_options['pager']['type'] = 'full'; +$handler->display->display_options['pager']['options']['items_per_page'] = '25'; +$handler->display->display_options['pager']['options']['offset'] = '0'; +$handler->display->display_options['pager']['options']['id'] = '0'; +$handler->display->display_options['style_plugin'] = 'table'; +$handler->display->display_options['style_options']['columns'] = array( + 'source' => 'source', + 'redirect' => 'redirect', + 'language' => 'language', + 'count' => 'count', + 'access' => 'access', + 'edit_redirect' => 'edit_redirect', + 'delete_redirect' => 'delete_redirect', +); +$handler->display->display_options['style_options']['default'] = '-1'; +$handler->display->display_options['style_options']['info'] = array( + 'source' => array( + 'sortable' => 1, + 'align' => '', + 'separator' => '', + ), + 'redirect' => array( + 'sortable' => 1, + 'align' => '', + 'separator' => '', + ), + 'language' => array( + 'sortable' => 1, + 'align' => '', + 'separator' => '', + ), + 'count' => array( + 'sortable' => 1, + 'align' => '', + 'separator' => '', + ), + 'access' => array( + 'sortable' => 1, + 'align' => '', + 'separator' => '', + ), + 'edit_redirect' => array( + 'align' => '', + 'separator' => '', + ), + 'delete_redirect' => array( + 'align' => '', + 'separator' => '', + ), +); +$handler->display->display_options['style_options']['sticky'] = TRUE; +/* No results behavior: Global: Text area */ +$handler->display->display_options['empty']['area']['id'] = 'area'; +$handler->display->display_options['empty']['area']['table'] = 'views'; +$handler->display->display_options['empty']['area']['field'] = 'area'; +$handler->display->display_options['empty']['area']['content'] = 'No URL redirects found.'; +$handler->display->display_options['empty']['area']['format'] = '1'; +/* Field: Redirect: Redirect ID */ +$handler->display->display_options['fields']['rid']['id'] = 'rid'; +$handler->display->display_options['fields']['rid']['table'] = 'redirect'; +$handler->display->display_options['fields']['rid']['field'] = 'rid'; +$handler->display->display_options['fields']['rid']['exclude'] = TRUE; +/* Field: Redirect: Source URL */ +$handler->display->display_options['fields']['source']['id'] = 'source'; +$handler->display->display_options['fields']['source']['table'] = 'redirect'; +$handler->display->display_options['fields']['source']['field'] = 'source'; +$handler->display->display_options['fields']['source']['absolute'] = 0; +/* Field: Redirect: Redirect URL */ +$handler->display->display_options['fields']['redirect']['id'] = 'redirect'; +$handler->display->display_options['fields']['redirect']['table'] = 'redirect'; +$handler->display->display_options['fields']['redirect']['field'] = 'redirect'; +$handler->display->display_options['fields']['redirect']['alter']['path'] = 'node'; +$handler->display->display_options['fields']['redirect']['alter']['absolute'] = TRUE; +$handler->display->display_options['fields']['redirect']['absolute'] = 0; +/* Field: Redirect: Language */ +$handler->display->display_options['fields']['language']['id'] = 'language'; +$handler->display->display_options['fields']['language']['table'] = 'redirect'; +$handler->display->display_options['fields']['language']['field'] = 'language'; +$handler->display->display_options['fields']['language']['empty'] = 'All'; +/* Field: Redirect: Clicks */ +$handler->display->display_options['fields']['count']['id'] = 'count'; +$handler->display->display_options['fields']['count']['table'] = 'redirect'; +$handler->display->display_options['fields']['count']['field'] = 'count'; +/* Field: Redirect: Last accessed date */ +$handler->display->display_options['fields']['access']['id'] = 'access'; +$handler->display->display_options['fields']['access']['table'] = 'redirect'; +$handler->display->display_options['fields']['access']['field'] = 'access'; +$handler->display->display_options['fields']['access']['label'] = 'Last accessed'; +$handler->display->display_options['fields']['access']['empty'] = 'Never'; +$handler->display->display_options['fields']['access']['empty_zero'] = TRUE; +$handler->display->display_options['fields']['access']['date_format'] = 'time ago'; +/* Field: Redirect: Operations */ +$handler->display->display_options['fields']['operations']['id'] = 'operations'; +$handler->display->display_options['fields']['operations']['table'] = 'redirect'; +$handler->display->display_options['fields']['operations']['field'] = 'operations'; +/* Filter criterion: Redirect: Type */ +$handler->display->display_options['filters']['type']['id'] = 'type'; +$handler->display->display_options['filters']['type']['table'] = 'redirect'; +$handler->display->display_options['filters']['type']['field'] = 'type'; +$handler->display->display_options['filters']['type']['value'] = array( + 'redirect' => 'redirect', +); + +/* Display: Page: User redirects */ +$handler = $view->new_display('page', 'Page: User redirects', 'page_user'); +$handler->display->display_options['defaults']['hide_admin_links'] = FALSE; +$handler->display->display_options['defaults']['arguments'] = FALSE; +/* Contextual filter: Redirect: User ID */ +$handler->display->display_options['arguments']['uid']['id'] = 'uid'; +$handler->display->display_options['arguments']['uid']['table'] = 'redirect'; +$handler->display->display_options['arguments']['uid']['field'] = 'uid'; +$handler->display->display_options['arguments']['uid']['default_action'] = 'default'; +$handler->display->display_options['arguments']['uid']['default_argument_type'] = 'user'; +$handler->display->display_options['arguments']['uid']['default_argument_options']['user'] = FALSE; +$handler->display->display_options['arguments']['uid']['summary']['format'] = 'default_summary'; +$handler->display->display_options['path'] = 'user/%/redirects'; +$handler->display->display_options['menu']['type'] = 'tab'; +$handler->display->display_options['menu']['title'] = 'Redirects'; +$handler->display->display_options['menu']['weight'] = '0'; + +/* Display: Page: Admin redirects */ +$handler = $view->new_display('page', 'Page: Admin redirects', 'page_admin'); +$handler->display->display_options['defaults']['hide_admin_links'] = FALSE; +$handler->display->display_options['path'] = 'admin/config/search/redirect/list'; +$handler->display->display_options['menu']['type'] = 'default tab'; +$handler->display->display_options['menu']['title'] = 'List'; +$handler->display->display_options['menu']['weight'] = '0'; +$handler->display->display_options['menu']['name'] = 'main-menu'; +$handler->display->display_options['tab_options']['weight'] = '0'; +$translatables['redirects'] = array( + t('Defaults'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort by'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('« first'), + t('‹ previous'), + t('next ›'), + t('last »'), + t('No URL redirects found.'), + t('Redirect ID'), + t('Source URL'), + t('Redirect URL'), + t('Language'), + t('All'), + t('Clicks'), + t('Last accessed'), + t('Never'), + t('Operations'), + t('Page: User redirects'), + t('Page: Admin redirects'), +); diff --git a/sites/all/modules/robotstxt/README.txt b/sites/all/modules/robotstxt/README.txt deleted file mode 100644 index 5774e208d76c7d7935f07528cac885e4c5e60ce6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/README.txt +++ /dev/null @@ -1,65 +0,0 @@ -$Id: README.txt,v 1.3.2.2 2011/01/05 23:24:10 hass Exp $ - -CONTENTS OF THIS FILE ---------------------- - - * Introduction - * Installation - * Frequently Asked Questions (FAQ) - * Known Issues - * How Can You Contribute? - - -INTRODUCTION ------------- - -Maintainer: hass <http://drupal.org/user/85918> -Project Page: http://drupal.org/project/robotstxt - -Use this module when you are running multiple Drupal sites from a single code -base (multisite) and you need a different robots.txt file for each one. This -module generates the robots.txt file dynamically and gives you the chance to -edit it, on a per-site basis. - -For developers, you can automatically add paths to the robots.txt file by -implementing hook_robotstxt(). See robotstxt.api.php for more documentation. - - -INSTALLATION ------------- - -See http://drupal.org/getting-started/install-contrib for instructions on -how to install or update Drupal modules. - -Once you have the RobotsTxt modules installed, make sure to delete or rename -the robots.txt file in the root of your Drupal installation. Otherwise, the -module cannot intercept requests for the /robots.txt path. - - -FREQUENTLY ASKED QUESTIONS --------------------------- - -Q: Can this module work if I have clean URLs disabled? -A: Yes it can! In the .htaccess file of your Drupal's root directory, add the - following two lines to the mod_rewrite section, immediately after the line - that says "RewriteEngine on": - - # Add redirection for the robots.txt path for use with the RobotsTxt module. - RewriteRule ^(robots.txt)$ index.php?q=$1 - - -KNOWN ISSUES ------------- - -There are no known issues at this time. - -To report new bug reports, feature requests, and support requests, visit -http://drupal.org/project/issues/robotstxt. - - -HOW CAN YOU CONTRIBUTE? ---------------------- - -- Report any bugs, feature requests, etc. in the issue tracker. - http://drupal.org/project/issues/robotstxt - diff --git a/sites/all/modules/robotstxt/robotstxt.admin.inc b/sites/all/modules/robotstxt/robotstxt.admin.inc deleted file mode 100644 index bde618f5b0e1a1c05e5d6c5155bf4731aae27ee6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.admin.inc +++ /dev/null @@ -1,25 +0,0 @@ -<?php -// $Id: robotstxt.admin.inc,v 1.3.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * @file - * Administrative page callbacks for the robotstxt module. - */ - -/** - * Administration settings form. - * - * @see system_settings_form() - */ -function robotstxt_admin_settings() { - $form['robotstxt'] = array( - '#type' => 'textarea', - '#title' => t('Contents of robots.txt'), - '#default_value' => _robotstxt_get_content(), - '#cols' => 60, - '#rows' => 20, - '#wysiwyg' => FALSE, - ); - - return system_settings_form($form, FALSE); -} diff --git a/sites/all/modules/robotstxt/robotstxt.api.php b/sites/all/modules/robotstxt/robotstxt.api.php deleted file mode 100644 index b481161bbce6ad325c300f0c1dfb942cd144aeba..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.api.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -// $Id: robotstxt.api.php,v 1.2.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * @file - * Hooks provided by the robotstxt module. - */ - -/** - * Add additional lines to the site's robots.txt file. - * - * @return - * An array of strings to add to the robots.txt. - */ -function hook_robotstxt() { - return array( - 'Disallow: /foo', - 'Disallow: /bar', - ); -} diff --git a/sites/all/modules/robotstxt/robotstxt.info b/sites/all/modules/robotstxt/robotstxt.info deleted file mode 100644 index 7c937b7cb3146e41d5732116bc3816ba3fc8536c..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.info +++ /dev/null @@ -1,15 +0,0 @@ -; $Id: robotstxt.info,v 1.6.2.2 2011/01/05 23:24:10 hass Exp $ -name = robots.txt -description = "Generates the robots.txt file dynamically and gives you the chance to edit it, on a per-site basis, from the web UI." -core = 7.x -files[] = robotstxt.module -files[] = robotstxt.admin.inc -files[] = robotstxt.install -configure = admin/config/search/robotstxt - -; Information added by drupal.org packaging script on 2011-01-05 -version = "7.x-1.0" -core = "7.x" -project = "robotstxt" -datestamp = "1294270341" - diff --git a/sites/all/modules/robotstxt/robotstxt.install b/sites/all/modules/robotstxt/robotstxt.install deleted file mode 100644 index c5d70fb79586ffd5af4f98e1f384b6a2ab9ad313..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.install +++ /dev/null @@ -1,85 +0,0 @@ -<?php -// $Id: robotstxt.install,v 1.13.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * Implements hook_install(). - */ -function robotstxt_install() { - if (file_exists(DRUPAL_ROOT . '/robots.txt')) { - variable_set('robotstxt', file_get_contents(DRUPAL_ROOT . '/robots.txt')); - } - elseif (file_exists(drupal_get_path('module', 'robotstxt') . '/robots.txt')) { - variable_set('robotstxt', file_get_contents(drupal_get_path('module', 'robotstxt') . '/robots.txt')); - } -} - -/** - * Implements hook_uninstall(). - */ -function robotstxt_uninstall() { - variable_del('robotstxt'); -} - -/** - * Implements hook_requirements(). - */ -function robotstxt_requirements($phase) { - $requirements = array(); - $t = get_t(); - - switch ($phase) { - case 'runtime' : - // Module cannot work without Clean URLs. - if (!variable_get('clean_url', 0)) { - $requirements['robotstxt_cleanurl'] = array( - 'title' => $t('RobotsTxt'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('<a href="!clean_url">Clean URLs</a> are mandatory for this module.', array('!clean_url' => url('admin/config/search/clean-urls'))), - ); - } - - // Webservers prefer the robots.txt file on disk and does not allow menu path overwrite. - if (file_exists(DRUPAL_ROOT . '/robots.txt')) { - $requirements['robotstxt_file'] = array( - 'title' => $t('RobotsTxt'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('RobotsTxt module works only if you remove the existing robots.txt file in your website root.'), - ); - } - } - return $requirements; -} - -/** - * Automatically add the 'administer robots.txt' permission to granted users. - */ -function robotstxt_update_6100() { - $roles = user_roles(FALSE, 'administer site configuration'); - foreach ($roles as $rid => $role) { - _update_7000_user_role_grant_permissions($rid, array('administer robots.txt'), 'robotstxt'); - } - return t("Added 'administer robots.txt' permission to all roles with 'administer site configuration' permission."); -} - -/** - * #337820: Rename menu path 'logout' to 'user/logout' for consistency. - */ -function robotstxt_update_7000() { - $robotstxt = variable_get('robotstxt'); - $robotstxt = str_replace('Disallow: /logout/', 'Disallow: /user/logout/', $robotstxt); - $robotstxt = str_replace('Disallow: /?q=logout/', 'Disallow: /?q=user/logout/', $robotstxt); - variable_set('robotstxt', $robotstxt); - - return t("Renamed menu path 'logout' to 'user/logout'."); -} - -/** - * #494462: Allow crawling of sites/default/files by search engines, don't disallow it in robots.txt. - */ -function robotstxt_update_7100() { - $robotstxt = variable_get('robotstxt'); - $robotstxt = preg_replace("/Disallow:\s\/sites\/(\r\n?|\n)/", '', $robotstxt); - variable_set('robotstxt', $robotstxt); - - return t("Allowed crawling of sites/default/files by search engines."); -} diff --git a/sites/all/modules/robotstxt/robotstxt.module b/sites/all/modules/robotstxt/robotstxt.module deleted file mode 100755 index 47e2354d83f4eb4fc058942716c18e88519614f6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.module +++ /dev/null @@ -1,97 +0,0 @@ -<?php -// $Id: robotstxt.module,v 1.9.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * Implements hook_help(). - */ -function robotstxt_help($path, $arg) { - switch ($path) { - case 'admin/help#robotstxt': - return '<p>'. t('In a multisite environment, there is no mechanism for having a separate robots.txt file for each site. This module addresses that need by letting you administer the robots.txt file from the settings interface.') .'</p>'; - break; - - case 'admin/config/search/robotstxt': - if (file_exists('./robots.txt')) { - drupal_set_message(t('One or more problems have been detected with the RobotsTxt configuration. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'warning'); - } - return t('See <a href="http://www.robotstxt.org/">http://www.robotstxt.org/</a> for more information concerning how to write your <a href="@robotstxt">robots.txt</a> file.', array('@robotstxt' => base_path() . 'robots.txt')); - break; - } -} - -/** - * Implements hook_permission(). - */ -function robotstxt_permission() { - return array( - 'administer robots.txt' => array( - 'title' => t('Administer robots.txt'), - 'description' => t('Perform maintenance tasks for robots.txt.'), - ), - ); -} - -/** - * Implements hook_menu(). - */ -function robotstxt_menu() { - $items['robots.txt'] = array( - 'page callback' => 'robotstxt_robots', - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - ); - $items['admin/config/search/robotstxt'] = array( - 'title' => 'RobotsTxt', - 'description' => 'Manage your robots.txt file.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('robotstxt_admin_settings'), - 'access arguments' => array('administer robots.txt'), - 'file' => 'robotstxt.admin.inc', - ); - - return $items; -} - -/** - * Show the robots.txt file. - */ -function robotstxt_robots() { - $content = array(); - $content[] = _robotstxt_get_content(); - - // Hook other modules for adding additional lines. - if ($additions = module_invoke_all('robotstxt')) { - $content = array_merge($content, $additions); - } - - // Trim any extra whitespace and filter out empty strings. - $content = array_map('trim', $content); - $content = array_filter($content); - - drupal_add_http_header('Content-type', 'text/plain'); - echo implode("\n", $content); - exit; -} - -/** - * Retrieve contents of robots.txt from the database variable, site root, or - * module directory. - */ -function _robotstxt_get_content() { - $content = variable_get('robotstxt', FALSE); - - if ($content === FALSE) { - $files = array( - DRUPAL_ROOT . '/robots.txt', - drupal_get_path('module', 'robotstxt') . '/robots.txt', - ); - foreach ($files as $file) { - if (file_exists($file) && is_readable($file)) { - $content = file_get_contents($file); - break; - } - } - } - - return $content; -} diff --git a/sites/all/modules/scanner/INSTALL.txt b/sites/all/modules/scanner/INSTALL.txt new file mode 100644 index 0000000000000000000000000000000000000000..69b0e93ed03437d49f6a3943160861b186930a97 --- /dev/null +++ b/sites/all/modules/scanner/INSTALL.txt @@ -0,0 +1,67 @@ +Scanner Search and Replace +-------------------------- + +BEFORE YOU START: + +Backup your database! Scanner Search and Replace is a very powerful tool, and +as such is very dangerous. You can easily destroy your entire site with it. Be +sure to backup your database before using it. No, really. + + +I. INSTALLATION: + +http://drupal.org/documentation/install/modules-themes/modules-7 + +1. Place the entire scanner folder in your sites/all/modules directory. + +2. Go to [Administration -> Modules] and enable the scanner module. + +3. Go to [Administration -> People -> Permissions] and apply your +preferred access settings to the scanner module. You can set which roles can +administer the module -- e.g., determine which fields can be scanned and modify +defaults -- and which roles can use the module. This is is handy if, say, you +only want the site admin to administer the module, but you want content +managers to be able to perform search and replace actions. + +4. Go to the Scanner administration panel and select which fields you want to +include in search and replace actions. More information on that is available +below in the Administration Options section. + + +II. ADMINISTRATION OPTIONS: + +You can access the Scanner admin settings two ways: + +- Go to [Administration -> Configuration -> Search and Replace Scanner]. + +- Go to [Administration -> Content -> Search and Replace Scanner] and +select the "Settings" tab. + +A. Default Options: + +In this section, you can set the defaults for several search options that +Scanner users will see when they use the search and replace form. Users will +still be able to change the options on their own, but the defaults can make +things easier for them if they're likely to only perform one kind of search +most of the time. + +You can also select whether teasers for nodes should be rebuilt after a +replacement has been made to the body or other fields for that node. Most +admins will select this option, because it ensures that teasers are in synch +with node content. But see section II.A above for more info on why leaving +this option unselected might be helpful. + +If your site has categories set up in the Taxonomy module, you can restrict +search and replace actions to nodes that contain terms in a given category +(a.k.a "vocabulary"). Select the vocabularies that you want to allow +restricting by. When users go to the search and replace form, they will have +the option of selecting one or more terms from those vocabularies for limiting +their searches. + +B. Fields that can be searched: + +The most important part of administering Scanner is making sure you select one +or more options in the "Fields that can be searched" section. Fields are listed +in [nodetype: fieldname] format. If you only want to allow people with access +to search and replace on the Body field of the Basic Page content type, +select [page: body]. diff --git a/sites/all/modules/robotstxt/LICENSE.txt b/sites/all/modules/scanner/LICENSE.txt similarity index 100% rename from sites/all/modules/robotstxt/LICENSE.txt rename to sites/all/modules/scanner/LICENSE.txt diff --git a/sites/all/modules/scanner/README.txt b/sites/all/modules/scanner/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..1367f3d6e764829b82a353b030f37a3ba023be8d --- /dev/null +++ b/sites/all/modules/scanner/README.txt @@ -0,0 +1,108 @@ +Scanner Search and Replace +-------------------------- + +I. DESCRIPTION: + +The Search and Replace Scanner can do regular expression matches +against a configurable list of title, body and text fields. +This is useful for finding html strings that Drupal's normal search will +ignore. It can also replace the matched text. Very handy if you are changing +the name of your company, or are changing the URL of a link included +multiple times in multiple nodes. + + +II. LIMITATIONS: + +1. A user can only have one instance of Search and Replace Scanner running at a time. +Attempting to open Scanner in two separate windows to perform replacements at the +same time can lead to unknown errors if you encounter a timeout. + +2. Only works on sites using a MySQL database. + +3. Large search and replace actions may not be completed on sites that are +hosted in environments where PHP's max_execution_time variable can't be +dynamically expanded. The module automatically attempts to expand the +maximum execution time of a script to 10 minutes. (It's often set at 2 minutes.) +If your Web host doesn't let you adjust this variable dynamically, you may be +able to ask your hosting provider to make the change for your account. + + +III. WARNING: + +This is a very powerful tool, and as such is very dangerous. You can easily +destroy your entire site with it. Be sure to backup your database before using +it. No, really. + + +IV. FEATURES: + +1. Plain text search and replace. + +2. Regular expression search and replace. + +3. Case sensitive search option. + +4. Plain text search allows 'whole word' matching option. For example, +searching for "run" with the whole word option selected will filter out +"running" and "runs", but will match "run!". + +5. You can specify text that should precede or follow the search text in order for a match to be valid. + +6. Can limit search and replace to published nodes only. + +7. Can search and replace on text fields, in addition to the node title. + +8. Searches can be limited to specific fields in specific node types. + +9. Searches can be further limited by nodes containing specific taxonomy terms. + +10. Will save a new node revision when a replacement is made, in case you want +to revert a change. + +11. Provides an Undo option that lets you revert all nodes that were udpated in +a specific replacement action. + +12. Updates summary part of text fields that use 'Text area with a summary' widget. + +13. Will dynamically expand PHP's maximum execution time for scripts up to +10 minutes on servers that support it. This allows complex queries on large +bodies of content. + +14. Search results for searches and replacements can be themed. + + +V. INSTALLATION AND ADMINISTRATION: + +See INSTALL.txt for installation and administration instructions. + + +VI. CREDITS: + +Version 7.x-dev by: + - Michael Rossetti + Drupal username: MikeyR + rossetti [at] mit.edu + +Version 6.x-dev by: + - Amit Asaravala + Drupal username: aasarava + http://www.returncontrol.com + amit [at] returncontrol.com + +Version 5.x-2.0 by: + - Amit Asaravala + Drupal username: aasarava + http://www.returncontrol.com + amit [at] returncontrol.com + - Jason Salter + Drupal username: jpsalter + http://www.fivepaths.com + jason [at] fivepaths.com + - Sponsored by Five Paths Consulting + http://www.fivepaths.com + +Version 5.x-1.0 by: + - Tao Starbow + Drupal username: starbow + http://www.starbowconsulting.com + tao [at] starbowconsulting.com diff --git a/sites/all/modules/scanner/scanner.css b/sites/all/modules/scanner/scanner.css new file mode 100644 index 0000000000000000000000000000000000000000..ce0caf44a09bd3cfd06a23905efc92cec06a3975 --- /dev/null +++ b/sites/all/modules/scanner/scanner.css @@ -0,0 +1,15 @@ +#scanner-confirm-form .scanner-buttons .scanner-button-msg { + position: absolute; + top: 0; + left: 0; + z-index: 100; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0.75; + font-size: 1.2em; + padding-left: 1em; +} +#scanner-confirm-form .scanner-buttons .scanner-button-msg p { + color: #fff; +} diff --git a/sites/all/modules/scanner/scanner.info b/sites/all/modules/scanner/scanner.info new file mode 100644 index 0000000000000000000000000000000000000000..9dace9224c66a14335ffb95a3396445df4f7fb08 --- /dev/null +++ b/sites/all/modules/scanner/scanner.info @@ -0,0 +1,6 @@ +name = Search and Replace Scanner +description = Perform search and replace on chosen text fields. + +core = 7.x +configure = admin/config/content/scanner + diff --git a/sites/all/modules/scanner/scanner.install b/sites/all/modules/scanner/scanner.install new file mode 100644 index 0000000000000000000000000000000000000000..1387c026548a928788c3f48b8752efea9ecdb698 --- /dev/null +++ b/sites/all/modules/scanner/scanner.install @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Search and Replace Scanner install - creates necessary tables. + */ + +/** + * Implements hook_schema(). + */ +function scanner_schema() { + $schema['scanner'] = array( + 'description' => 'Holds info on recent replacements in case undo is needed.', + 'fields' => array( + 'undo_id' => array( + 'description' => 'Row identifier', + 'type' => 'serial', + 'not null' => TRUE, + ), + 'undo_data' => array( + 'description' => 'What was changed', + 'type' => 'text', + 'size' => 'big', + 'not null' => TRUE, + ), + 'undone' => array( + 'description' => 'Whether the replacement has been undone', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + ), + 'searched' => array( + 'description' => 'Text that was searched for', + 'type' => 'varchar', + 'length' => 256, + 'not null' => TRUE, + ), + 'replaced' => array( + 'description' => 'Text that was used as replacement', + 'type' => 'varchar', + 'length' => 256, + 'not null' => TRUE, + ), + 'count' => array( + 'description' => 'How many fields were modified on replacement', + 'type' => 'int', + 'not null' => TRUE, + ), + 'time' => array( + 'description' => 'How long the replacement took', + 'type' => 'int', + 'not null' => TRUE, + ), + ), + 'primary key' => array('undo_id'), + ); + + return $schema; +} diff --git a/sites/all/modules/scanner/scanner.js b/sites/all/modules/scanner/scanner.js new file mode 100644 index 0000000000000000000000000000000000000000..557ec1ebb17d15554a96e0ca5273e6f351fd2856 --- /dev/null +++ b/sites/all/modules/scanner/scanner.js @@ -0,0 +1,17 @@ +(function ($) { + +/** + * Prevents submit button double-click on replace confirmation form. + */ +Drupal.behaviors.hideSubmitButton = { + attach: function(context) { + $('input#edit-confirm').click(function() { + $('.scanner-buttons').css('position','relative') + .append('<div class="scanner-button-msg"><p>Replacing items... please wait...</p></div>') + $('.scanner-button-msg').click(function() { return false; }); + return true; + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/scanner/scanner.module b/sites/all/modules/scanner/scanner.module new file mode 100644 index 0000000000000000000000000000000000000000..710acf40578ba673f9068f86e4ac65fed4aec1da --- /dev/null +++ b/sites/all/modules/scanner/scanner.module @@ -0,0 +1,1230 @@ +<?php + +/** + * @file + * Search and Replace Scanner - works on all nodes text content. + */ + +// The special characters to escape if a search string is not a regex string. +define('SCANNER_REGEX_CHARS', '.\/+*?[^]$() {}=!<>|:'); + +// The modes that the search-and-replace process can be in. +// We need to track the modes to prevent accidentally starting a replacement +// or a long search if a user leaves mid-way through the process +// and comes back again w/ the same session variables. +define('SCANNER_STATUS_GO_SEARCH', 1); +define('SCANNER_STATUS_GO_CONFIRM', 2); +define('SCANNER_STATUS_GO_REPLACE', 3); + +/** + * Implements hook_menu(). + */ +function scanner_menu() { + $items['admin/content/scanner'] = array( + 'title' => 'Search and Replace Scanner', + 'description' => 'Find (and replace) keywords in all your content.', + 'page callback' => 'scanner_view', + 'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM, + 'access arguments' => array('perform search and replace'), + ); + $items['admin/content/scanner/scan'] = array( + 'title' => 'Search', + 'access arguments' => array('perform search and replace'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/content/scanner/scan/confirm'] = array( + 'title' => 'Confirm Replace', + 'access arguments' => array('perform search and replace'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('scanner_confirm_form'), + 'type' => MENU_CALLBACK, + ); + $items['admin/content/scanner/undo/confirm'] = array( + 'title' => 'Confirm Undo', + 'access arguments' => array('perform search and replace'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('scanner_undo_confirm_form'), + 'type' => MENU_CALLBACK, + ); + $items['admin/content/scanner/settings'] = array(// Shows up on scanner page as tab. + 'title' => 'Settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('scanner_admin_form'), + 'access arguments' => array('administer scanner settings'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ); + $items['admin/content/scanner/undo'] = array(// Shows up on scanner page as tab. + 'title' => 'Undo', + 'page callback' => 'scanner_undo_page', + 'access arguments' => array('perform search and replace'), + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/config/content/scanner'] = array(// Shows up on admin page. + 'title' => 'Search and Replace Scanner', + 'description' => 'Configure defaults and what fields can be searched and replaced.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('scanner_admin_form'), + 'access arguments' => array('administer scanner settings'), + ); + + return $items; +} + +/** + * Implements hook_theme(). + */ +function scanner_theme() { + return array( + 'scanner_results' => array( + 'file' => 'scanner.theme.inc', + 'variables' => array( + 'results' => NULL, + ), + ), + 'scanner_item' => array( + 'file' => 'scanner.theme.inc', + 'variables' => array( + 'item' => NULL, + ), + ), + 'scanner_replace_results' => array( + 'file' => 'scanner.theme.inc', + 'variables' => array( + 'results' => NULL, + ), + ), + 'scanner_replace_item' => array( + 'file' => 'scanner.theme.inc', + 'variables' => array( + 'item' => NULL, + ), + ), + ); +} + +/** + * Implements hook_permission(). + */ +function scanner_permission() { + return array( + 'administer scanner settings' => array( + 'title' => t('Administer scanner settings'), + ), + 'perform search and replace' => array( + 'title' => t('Perform search and replace'), + ), + ); +} + +/** + * Menu callback; presents the scan form and results. + */ +function scanner_view() { + $output=''; + + // Using set_html_head because it seems unecessary to load a separate css + // file for just two simple declarations. + drupal_add_css(' + #scanner-form .form-submit { margin-top:0; } + #scanner-form .form-item { margin-bottom:0; } + ', array('type' => 'inline')); + + // Javascript checks to make sure user has entered some search text. + drupal_add_js(" + jQuery(document).ready(function() { + var searchfield = jQuery('#edit-search'); + jQuery('input[type=submit][value=". str_replace('\'', '\\\'', t('Search')) . "]').click(function() { + var chars = searchfield.val().length; + if (chars == 0) { + alert('". str_replace('\'', '\\\'', t('Please provide some search text and try again.')) . "'); + searchfield.addClass('error'); + searchfield[0].focus(); + return false; + } else if (chars < 3) { + return confirm('". str_replace('\'', '\\\'', t('Searching for a keyword that has fewer than three characters could take a long time. Are you sure you want to continue?')) . "'); + } + return true; + }); + searchfield.keyup(function() { + searchfield.removeClass('error'); + }); + }); + ", array('type' => 'inline', 'group' => JS_DEFAULT)); + + if (isset($_SESSION['scanner_search'])) { + $search = $_SESSION['scanner_search']; + } + else { + $search = NULL; + } + + if (isset($_SESSION['scanner_status'])) { + $status = $_SESSION['scanner_status']; + } + else { + $status = NULL; + } + + if (!is_NULL($search) && $status >= SCANNER_STATUS_GO_SEARCH) { + if ($status == SCANNER_STATUS_GO_CONFIRM) { + drupal_goto('admin/content/scanner/scan/confirm'); + } + elseif ($status == SCANNER_STATUS_GO_REPLACE) { + $resulttxt = '<a name="results"></a>' . t('Replacement Results'); + $results = scanner_execute('replace'); + } + else { + $resulttxt = t('Search Results'); + $results = scanner_execute('search'); + } + + if ($results) { + // TODO Please change this theme call to use an associative array for the $variables parameter. + $results = '<a name="results"></a><div><h2>' . $resulttxt . '</h2>' . $results; + } + else { + // TODO Please change this theme call to use an associative array for the $variables parameter. + $results = t('Your search yielded no results.'); + } + + $scanner_form = drupal_get_form('scanner_form'); + $output = drupal_render($scanner_form); + $output .= $results; + + // Clear any old search form input. + unset($_SESSION['scanner_search']); + unset($_SESSION['scanner_replace']); + unset($_SESSION['scanner_preceded']); + unset($_SESSION['scanner_followed']); + unset($_SESSION['scanner_mode']); + unset($_SESSION['scanner_wholeword']); + unset($_SESSION['scanner_published']); + unset($_SESSION['scanner_pathauto']); + unset($_SESSION['scanner_regex']); + unset($_SESSION['scanner_terms']); + // Clear old status. + unset($_SESSION['scanner_status']); + + return $output; + } + + $scanner_form = drupal_get_form('scanner_form'); + $output = drupal_render($scanner_form); + + return $output; +} + +/** + * Form constructor for the search and replace form. + * + * @see scanner_form_validate() + * @see scanner_form_submit() + * @ingroup forms + */ +function scanner_form($form, &$form_state) { + $form = array(); + + if (isset($_SESSION['scanner_search'])) { + $search = $_SESSION['scanner_search']; + } + else { + $search = NULL; + } + + if (isset($_SESSION['scanner_replace'])) { + $replace = $_SESSION['scanner_replace']; + } + else { + $replace = NULL; + } + if (isset($_SESSION['scanner_preceded'])) { + $preceded = $_SESSION['scanner_preceded']; + } + else { + $preceded = NULL; + } + + if (isset($_SESSION['scanner_followed'])) { + $followed = $_SESSION['scanner_followed']; + } + else { + $followed = NULL; + } + + $mode = isset($_SESSION['scanner_mode']) ? $_SESSION['scanner_mode'] : variable_get('scanner_mode', 0); + $wholeword = isset($_SESSION['scanner_wholeword']) ? $_SESSION['scanner_wholeword'] : variable_get('scanner_wholeword', 0); + $regex = isset($_SESSION['scanner_regex']) ? $_SESSION['scanner_regex'] : variable_get('scanner_regex', 0); + $published = isset($_SESSION['scanner_published']) ? $_SESSION['scanner_published'] : variable_get('scanner_published', 1); + $pathauto = isset($_SESSION['scanner_pathauto']) ? $_SESSION['scanner_pathauto'] : variable_get('scanner_pathauto', 1); + + if (isset($_SESSION['scanner_terms'])) { + $terms = $_SESSION['scanner_terms']; + } + else { + $terms = NULL; + } + + $form['search'] = array( + '#type' => 'textfield', + '#default_value' => $search, + '#title' => t('Step 1: Search for'), + '#maxlength' => 256, + ); + $form['submit_search'] = array( + '#type' => 'submit', + '#value' => t('Search'), + ); + + $form['replace'] = array( + '#type' => 'textfield', + '#default_value' => $replace, + '#title' => t('Step 2: Replace with'), + '#maxlength' => 256, + ); + $form['submit_replace'] = array( + '#type' => 'submit', + '#value' => t('Replace'), + ); + + $form['options'] = array( + '#type' => 'fieldset', + '#title' => t('Search Options'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + $form['options']['surrounding'] = array( + '#type' => 'fieldset', + '#title' => t('Surrounding Text'), + '#collapsible' => FALSE, + '#description' => t('You can limit matches by providing the text that should appear immediately before or after the search text. Remember to account for spaces. Note: Case sensitivity and regular expression options will all apply here, too. Whole word is not recommended.'), + ); + $form['options']['surrounding']['preceded'] = array( + '#type' => 'textfield', + '#title' => t('Preceded by'), + '#default_value' => $preceded, + '#maxlength' => 256, + ); + $form['options']['surrounding']['followed'] = array( + '#type' => 'textfield', + '#title' => t('Followed by'), + '#default_value' => $followed, + '#maxlength' => 256, + ); + + $form['options']['mode'] = array( + '#type' => 'checkbox', + '#title' => t('Case sensitive search'), + '#default_value' => $mode, + '#description' => t("Check this if the search should only return results that exactly match the capitalization of your search terms."), + ); + $form['options']['wholeword'] = array( + '#type' => 'checkbox', + '#title' => t('Match whole word'), + '#default_value' => $wholeword, + '#description' => t("Check this if you don't want the search to match any partial words. For instance, if you search for 'run', a whole word search will <em>not</em> match 'running'."), + ); + $form['options']['regex'] = array( + '#type' => 'checkbox', + '#title' => t('Use regular expressions in search'), + '#default_value' => $regex, + '#description' => t('Check this if you want to use regular expressions in your search terms.'), + ); + $form['options']['published'] = array( + '#type' => 'checkbox', + '#title' => t('Published nodes only'), + '#default_value' => $published, + '#description' => t('Check this if you only want your search and replace to affect fields in nodes that are published.'), + ); + $form['options']['pathauto'] = array( + '#type' => 'checkbox', + '#title' => t('Maintain custom aliases'), + '#default_value' => $pathauto, + '#description' => t("Prevent custom URL aliases from being overwritten with ones generated from Path Auto's URL alias patterns."), + ); + + $scanner_vocabularies = array_filter(variable_get('scanner_vocabulary', array())); + + if (count($scanner_vocabularies)) { + $vocabularies = taxonomy_get_vocabularies(); + $options = array(); + + foreach ($vocabularies as $vid => $vocabulary) { + if (in_array($vid, $scanner_vocabularies)) { + $tree = taxonomy_get_tree($vid); + if ($tree && (count($tree) > 0)) { + $options[$vocabulary->name] = array(); + foreach ($tree as $term) { + $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name; + } + } + } + } + + $form['options']['terms'] = array( + '#type' => 'select', + '#title' => t('Only match nodes with these terms'), + '#options' => $options, + '#default_value' => $terms, + '#multiple' => TRUE, + ); + } + + return $form; +} + +/** + * Form validation handler for scanner_form(). + * + * @see scanner_form() + * @see scanner_form_submit() + */ +function scanner_form_validate($form, &$form_state) { + $search = trim($form_state['values']['search']); + if ($search == '') { + form_set_error('search', t('Please enter some keywords.')); + } +} + +/** + * Form submission handler for scanner_form(). + * + * @see scanner_form() + * @see scanner_form_validate() + */ +function scanner_form_submit($form, &$form_state) { + // Save form input into session. + $_SESSION['scanner_search'] = $form_state['values']['search']; + $_SESSION['scanner_preceded'] = $form_state['values']['preceded']; + //$_SESSION['scanner_notpreceded'] = $form_state['values']['notpreceded']; + $_SESSION['scanner_followed'] = $form_state['values']['followed']; + //$_SESSION['scanner_notfollowed'] = $form_state['values']['notfollowed']; + $_SESSION['scanner_mode'] = $form_state['values']['mode']; + $_SESSION['scanner_wholeword'] = $form_state['values']['wholeword']; + $_SESSION['scanner_regex'] = $form_state['values']['regex']; + $_SESSION['scanner_published'] = $form_state['values']['published']; + $_SESSION['scanner_pathauto'] = $form_state['values']['pathauto']; + if (isset($form_state['values']['terms'])) { + $_SESSION['scanner_terms'] = $form_state['values']['terms']; + } + $_SESSION['scanner_replace'] = $form_state['values']['replace']; + + /* TODO The 'op' element in the form values is deprecated. + Each button can have #validate and #submit functions associated with it. + Thus, there should be one button that submits the form and which invokes + the normal form_id_validate and form_id_submit handlers. Any additional + buttons which need to invoke different validate or submit functionality + should have button-specific functions. */ + if ($form_state['values']['op'] == t('Replace')) { + $_SESSION['scanner_status'] = SCANNER_STATUS_GO_CONFIRM; + } + else { + $_SESSION['scanner_status'] = SCANNER_STATUS_GO_SEARCH; + } + $form_state['redirect'] = 'admin/content/scanner'; +} + +/** + * Form constructor for the confirmation form. + * + * @see block_add_block_form_submit() + * @ingroup forms + */ +function scanner_confirm_form($form, &$form_state) { + $form = array(); + + $form['#attached']['js'][] = drupal_get_path('module', 'scanner') . '/scanner.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'scanner') . '/scanner.css'; + + $search = $_SESSION['scanner_search']; + $replace = $_SESSION['scanner_replace']; + $preceded = $_SESSION['scanner_preceded']; + $followed = $_SESSION['scanner_followed']; + $wholeword = $_SESSION['scanner_wholeword']; + $regex = $_SESSION['scanner_regex']; + $mode = $_SESSION['scanner_mode']; + + $modetxt = ($mode) ? t('Case sensitive') : t('Not case sensitive: will replace any matches regardless of capitalization.'); + + $msg = ( + '<p>' . t('Are you sure you want to make the following replacement?') . '</p>' . + '<div class="scanner-confirm">' . + ' <label>' . t('Search for') . ':</label> [' . check_plain($search) . ']' . + '</div>' + ); + if ($preceded) { + $msg .= ( + '<div class="scanner-confirm">' . + ' <label>' . t('Preceded by') . ':</label> [' . check_plain($preceded) . ']' . + '</div>' + ); + } + if ($followed) { + $msg .= ( + '<div class="scanner-confirm">' . + ' <label>' . t('Followed by') . ':</label> [' . check_plain($followed) . ']' . + '</div>' + ); + } + $msg .= ( + '<div class="scanner-confirm">' . + ' <label>' . t('Replace with') . ':</label> [' . check_plain($replace) . ']' + ); + if ($replace === '') { + $msg .= ' <span class="warning">This will delete any occurences of the search terms!</span>'; + } + $msg .= ( + '</div>' . + '<div class="scanner-confirm">' . + ' <label>' . t('Mode') . ':</label> ' . $modetxt . + '</div>' + ); + if ($wholeword) { + $msg .= ( + '<div class="scanner-confirm">' . + ' <label>' . t('Match whole word') . ':</label> ' . t('Yes') . + '</div>' + ); + } + if ($regex) { + $msg .= ( + '<div class="scanner-confirm">' . + ' <label>' . t('Use regular expressions') . ':</label> ' . t('Yes') . + '</div>' + ); + } + + $form['warning'] = array( + '#type' => 'item', + '#markup' => $msg, + ); + $form['confirm'] = array( + '#type' => 'submit', + '#value' => t('Yes, Continue'), + '#prefix' => '<div class="scanner-buttons">', //see suffix in cancel button element + ); + $form['cancel'] = array( + '#type' => 'submit', + '#value' => t('No, Cancel'), + '#suffix' => '</div>', //see prefix in confirm button element + ); + + return $form; +} + +/** + * Form submission handler for scanner_confirm_form(). + * + * @see scanner_confirm_form() + */ +function scanner_confirm_form_submit($form, &$form_state) { + /* TODO The 'op' element in the form values is deprecated. + Each button can have #validate and #submit functions associated with it. + Thus, there should be one button that submits the form and which invokes + the normal form_id_validate and form_id_submit handlers. Any additional + buttons which need to invoke different validate or submit functionality + should have button-specific functions. */ + if ($form_state['values']['op'] == t('Yes, Continue')) { + $_SESSION['scanner_status'] = SCANNER_STATUS_GO_REPLACE; + } + else { + unset($_SESSION['scanner_status']); + } + + $form_state['redirect'] = 'admin/content/scanner'; +} + +/** + * Page callback to display table of executed replace actions with undo/redo operation. + */ +function scanner_undo_page() { + $header = array(t('Date'), t('Searched'), t('Replaced'), t('Count'), t('Operation')); + + $undoQuery = db_select('scanner', 's'); + $undoQuery->fields('s', array('undo_id', 'time', 'searched', 'replaced', 'count', 'undone')) + ->orderBy('undo_id', 'DESC'); + + $sandrs = $undoQuery->execute(); + + $rows = array(); + + foreach ($sandrs as $sandr) { + + if ($sandr->undone) { + $operation = l(t('Redo'), 'admin/content/scanner/undo/confirm', array('query' => array('undo_id' => $sandr->undo_id))); + } + else { + $operation = l(t('Undo'), 'admin/content/scanner/undo/confirm', array('query' => array('undo_id' => $sandr->undo_id))); + } + + $rows[] = array( + format_date($sandr->time), + check_plain($sandr->searched), + check_plain($sandr->replaced), + $sandr->count, + $operation, + ); + } + + return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => NULL, 'caption' => 'Prior Search and Replace Events')); +} + +/** + * Form constructor for the undo confirmation form. + * + * @see scanner_undo_confirm_form_submit() + * @ingroup forms + */ +function scanner_undo_confirm_form($form, &$form_state) { + + $undo_id = $_GET['undo_id']; + + if ($undo_id > 0) { + + $query = db_select('scanner', 's'); + $query->fields('s', array('undo_id', 'searched', 'replaced')) + ->condition('undo_id', $undo_id, '='); + + $result = $query->execute(); + + foreach ($result as $undo) { + $undo = $undo; + } + + } + + if ($undo->undo_id > 0) { + $form['info'] = array( + '#markup' => '<h2>' . t('Do you want to undo:') . '</h2>' . + '<h3>' . t('Searched for:') . '</h3>' . + '<p>[<em>' . check_plain($undo->searched) . '</em>]</p>' . + '<h3>' . t('Replaced with:') . '</h3>' . + '<p>[<em>' . check_plain($undo->replaced) . '</em>]</p>', + ); + + $form['undo_id'] = array( + '#type' => 'hidden', + '#value' => $undo->undo_id, + ); + + $form['confirm'] = array( + '#type' => 'submit', + '#value' => t('Yes, Continue'), + ); + + $form['cancel'] = array( + '#type' => 'submit', + '#value' => t('No, Cancel'), + ); + + } + else { + $form['info'] = array( + '#value' => '<h2>' . t('No undo event was found') . '</h2>', + ); + } + + return $form; +} + +/** + * Form submission handler for scanner_undo_confirm_form(). + * + * @see scanner_undo_confirm_form() + */ +function scanner_undo_confirm_form_submit($form, &$form_state) { + /* TODO The 'op' element in the form values is deprecated. + Each button can have #validate and #submit functions associated with it. + Thus, there should be one button that submits the form and which invokes + the normal form_id_validate and form_id_submit handlers. Any additional + buttons which need to invoke different validate or submit functionality + should have button-specific functions. */ + if ($form_state['values']['op'] == t('Yes, Continue')) { + $query = db_select('scanner', 's'); + $query->fields('s', array('undo_data', 'undone')) + ->condition('undo_id', $form_state['values']['undo_id'], '='); + + $results = $query->execute(); + + foreach ($results as $undo) { + $undo = $undo; + } + + $undos = unserialize($undo->undo_data); + $count = NULL; + + foreach ($undos as $nid => $sandr_event) { + if ($undo->undone == 0) { + $vid = $sandr_event['old_vid']; + $undone = 1; + } + else { + $vid = $sandr_event['new_vid']; + $undone = 0; + } + + $node = node_load($nid, $vid); + $node->revision = TRUE; + $node->log = t('Copy of the revision from %date via Search and Replace Undo', array('%date' => format_date($node->revision_timestamp))); + node_save($node); + ++$count; + } + + drupal_set_message($count . ' ' . t('Nodes reverted')); + // TODO Please review the conversion of this statement to the D7 database API syntax. + + db_update('scanner') + ->fields(array( + 'undone' => $undone, + )) + ->condition('undo_id', $form_state['values']['undo_id']) + ->execute(); + } + else { + drupal_set_message(t('Undo / Redo canceled')); + } + + $form_state['redirect'] = 'admin/content/scanner/undo'; + $form_state['nid'] = $node->nid; +} + +/** + * Handles the actual search and replace. + * + * @param str $searchtype - either 'search', or 'replace' + * @return The themed results. + */ +function scanner_execute($searchtype = 'search') { + global $user; + + // Variables to monitor possible timeout. + $max_execution_time = ini_get('max_execution_time'); + $start_time = REQUEST_TIME; + $expanded = FALSE; + + // Get process and undo data if saved from timeout. + $processed = variable_get('scanner_partially_processed_' . $user->uid, array()); + $undo_data = variable_get('scanner_partial_undo_' . $user->uid, array()); + + unset($_SESSION['scanner_status']); + $search = $_SESSION['scanner_search']; + $replace = $_SESSION['scanner_replace']; + $preceded = $_SESSION['scanner_preceded']; + $followed = $_SESSION['scanner_followed']; + $mode = $_SESSION['scanner_mode']; + $flag = $mode ? NULL : 'i'; // Case sensitivity flag for use in php preg_search and preg_replace + $wholeword = $_SESSION['scanner_wholeword']; + $regex = $_SESSION['scanner_regex']; + $published = $_SESSION['scanner_published']; + $pathauto = $_SESSION['scanner_pathauto']; + $terms = isset($_SESSION['scanner_terms']) ? $_SESSION['scanner_terms'] : NULL; + $results = NULL; + + if ($searchtype == 'search') { + drupal_set_message(t('Searching for: [%search] ...', array('%search' => $search))); + } + else { //searchtype == 'replace' + drupal_set_message(t('Replacing [%search] with [%replace] ...', array('%search' => $search, '%replace' => $replace))); + } + + $preceded_php = ''; + if (!empty($preceded)) { + if (!$regex) { + $preceded = addcslashes($preceded, SCANNER_REGEX_CHARS); + } + $preceded_php = '(?<=' . $preceded . ')'; + } + $followed_php = ''; + if (!empty($followed)) { + if (!$regex) { + $followed = addcslashes($followed, SCANNER_REGEX_CHARS); + } + $followed_php = '(?=' . $followed . ')'; + } + + // Case 1. + if ($wholeword && $regex) { + $where = "[[:<:]]" . $preceded . $search . $followed . "[[:>:]]"; + $search_php = '\b' . $preceded_php . $search . $followed_php . '\b'; + } + // Case 2. + elseif ($wholeword && !$regex) { + $where = "[[:<:]]" . $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed . "[[:>:]]"; + $search_php = '\b' . $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php . '\b'; + } + // Case 3. + elseif (!$wholeword && $regex) { + $where = $preceded . $search . $followed; + $search_php = $preceded_php . $search . $followed_php; + } + // Case 4. + else { //!wholeword and !regex: + $where = $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed; + $search_php = $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php; + } + + // If terms selected, then put together extra join and where clause. + $join = ''; + if (is_array($terms) && count($terms)) { + $terms_where = array(); + $terms_params = array(); + foreach ($terms as $term) { + $terms_where[] = 'tn.tid = %d'; + $terms_params[] = $term; + } + $join = 'INNER JOIN {taxonomy_term_node} tn ON t.nid = tn.nid'; + $where .= ' AND (' . implode(' OR ', $terms_where) . ')'; + } + + // Examine each field instance as chosen in settings. + foreach (_scanner_get_selected_tables_map() as $map) { + $table = $map['table']; + $field = $map['field']; + $field_summary = NULL; + $type = $map['type']; + $field_collection = isset($map['field_collection']) ? $map['field_collection'] : NULL; + $field_collection_table = isset($map['field_collection_table']) ? $map['field_collection_table'] : NULL; + + $query = db_select($table, 't'); + if ($table == 'node_revision') { + $vid = 'vid'; + } + else { + if (db_field_exists($table, $field . '_summary')) { + $field_summary = $field . '_summary'; + } + $field = $field . '_value'; + $vid = 'revision_id'; + } + if (isset($field_collection_table)) { + $query->join($field_collection_table, 'fc', 't.entity_id = fc.' . $field_collection . '_value AND t.revision_id = fc.' . $field_collection . '_value'); + $query->join('node', 'n', 'fc.entity_id = n.nid AND fc.revision_id = n.vid'); + } + else { + // Must use vid and revision_id here. Make sure it saves as new revision. + $query->join('node', 'n', 't.' . $vid . ' = n.vid'); + } + if (is_array($terms) && count($terms)) { + $db_or = db_or(); + $query->join('taxonomy_index', 'tx', 'n.nid = tx.nid'); + foreach ($terms as $term) { + $db_or->condition('tx.tid', $term); + } + $query->condition($db_or); + } + $query->addField('t', $field, 'content'); + if (isset($field_summary)) { + $query->addField('t', $field_summary, 'summary'); + } + $query->fields('n', array('nid', 'title')); + $query->condition('n.type', $type, '='); + + $or = db_or(); + $binary = $mode ? ' BINARY' : ''; + $or->condition('t.' . $field, $where, 'REGEXP' . $binary); + if (isset($field_summary)) { + $or->condition('t.' . $field_summary, $where, 'REGEXP' . $binary); + } + $query->condition($or); + + if ($published) { + $query->condition('n.status', '1', '='); + } + $result = $query->execute(); + + $shutting_down = FALSE; + + // Perform the search or replace on each hit for the current field instance. + foreach ($result as $row) { + $content = $row->content; + $summary = isset($row->summary) ? $row->summary : ''; + $matches = array(); + $text = ''; + + // Checking for possible timeout. + // If within 5 seconds of timeout, attempt to expand environment. + if (REQUEST_TIME >= ($start_time + $max_execution_time - 5)) { + if (!$expanded) { + if ($user->uid > 0) { + $verbose = TRUE; + } + else { + $verbose = FALSE; + } + if (_scanner_change_env('max_execution_time', '600', $verbose)) { + drupal_set_message(t('Default max_execution_time too small and changed to 10 minutes.'), 'error'); + $max_execution_time = 600; + } + $expanded = TRUE; + } + // If expanded environment still running out of time, shutdown process. + else { + $shutting_down = TRUE; + variable_set('scanner_partially_processed_' . $user->uid, $processed); + variable_set('scanner_partial_undo_' . $user->uid, $undo_data); + if ($searchtype == 'search') { + drupal_set_message(t('Did not have enough time to complete search.'), 'error'); + } + else { + drupal_set_message(t('Did not have enough time to complete. Please re-submit replace'), 'error'); + } + break 2; + } + } + + $node = node_load($row->nid); + + // Search. + if ($searchtype == 'search') { + $regexstr = "/$search_php/$flag"; + $matches = array('0' => array()); + + // Assign matches in the base text field to $matches[0]. + $hits = preg_match_all($regexstr, $content, $matches, PREG_OFFSET_CAPTURE); + // Assign summary matches to $matches[1]. + $hits += preg_match_all($regexstr, $summary, $matches_summary, PREG_OFFSET_CAPTURE); + + $matches = array_merge($matches, $matches_summary); + + if ($hits > 0) { + $context_length = 70; + $text .= '<ul>'; + foreach ($matches as $key => $item) { + $string = $key == 0 ? $content : $summary; + foreach ($item as $match) { + $text .= '<li>'; + if ($key == 1) { + $text .= '<i>Summary:</i> '; + } + + // Don't want substr to wrap. + $start = $match[1]-$context_length > 0 ? $match[1]-$context_length : 0; + // If the match is close to the beginning of the string, need less context. + $length = $match[1] >= $context_length ? $context_length : $match[1]; + + if ($prepend = substr($string, $start, $length)) { + if ($length == $context_length) { + $text .= '...'; + } + $text .= htmlentities($prepend, ENT_COMPAT, 'UTF-8'); + } + + $text .= '<strong>' . htmlentities($match[0], ENT_COMPAT, 'UTF-8') . '</strong>'; + + if ($append = substr($string, $match[1]+strlen($match[0]), $context_length)) { + $text .= htmlentities($append, ENT_COMPAT, 'UTF-8'); + if (strlen($string) - ($match[1]+strlen($match[0])) > $context_length) { + $text .= '...'; + } + } + $text .= '</li>'; + } + } + $text .= '</ul>'; + } + else { + $text = '<div class="messages warning"><h2 class="element-invisible">Warning message</h2>' . + t("Can't display search result due to conflict between search term and internal preg_match_all function.") . + '</div>'; + } + + $results[] = array( + 'title' => $row->title, + 'type' => $type, + 'count' => $hits, + 'field' => $field, + 'nid' => $row->nid, + 'text' => $text, + ); + } + // Replace (and check to see if already processed). + elseif (!isset($processed[$field][$row->nid])) { + // Check first if pathauto_persist, a newer version of pathauto, or some + // other module has already set $node->path['pathauto']. If not, set it + // to false (to prevent pathauto from touching the node during node_save()) + // if a custom alias exists that doesn't follow pathauto rules. + if (!isset($node->path['pathauto']) && module_exists('pathauto') && $pathauto) { + list($id, , $bundle) = entity_extract_ids('node', $node); + if (!empty($id)) { + module_load_include('inc', 'pathauto'); + $uri = entity_uri('node', $node); + $path = drupal_get_path_alias($uri['path']); + $pathauto_alias = pathauto_create_alias('node', 'return', $uri['path'], array('node' => $node), $bundle); + $node->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias); + } + } + + $hits = 0; + $content_new = preg_replace("/$search_php/$flag", $replace, $content, -1, $hits); + preg_match('/(.+)_value$/', $field, $matches); + if (!empty($matches[0])) { + // Text Field or Text Area. + $node->{$matches[1]}[$node->language][0]['value'] = $content_new; + // Summary. + if (isset($node->{$matches[1]}[$node->language][0]['summary'])) { + $summary = $node->{$matches[1]}[$node->language][0]['summary']; + $node->{$matches[1]}[$node->language][0]['summary'] = preg_replace("/$search_php/$flag", $replace, $summary, -1, $hits_summary); + $hits += $hits_summary; + } + } + else { + // Other type such as a Title. + $node->$field = $content_new; + } + + // A revision only created for the first change of the node. + // Subsequent changes of the same node do not generate additional revisions. + if (!isset($undo_data[$node->nid]['new_vid'])) { + $node->revision = TRUE; + $node->log = t('@name replaced %search with %replace via Scanner Search and Replace module.', array('@name' => $user->name, '%search' => $search, '%replace' => $replace)); + $undo_data[$node->nid]['old_vid'] = $node->vid; + } + + node_save($node); + + // Array to log completed fields in case of shutdown. + $processed[$field][$row->nid] = TRUE; + + // Undo data construction. + $undo_data[$node->nid]['new_vid'] = $node->vid; // Now set to updated vid after node_save(). + $results[] = array( + 'title' => $node->title, + 'type' => $node->type, + 'count' => $hits, + 'field' => $field, + 'nid' => $node->nid, + ); + } + } // end foreach + } // end foreach + + // If completed. + if (isset($shutting_down) && !$shutting_down) { + variable_del('scanner_partially_processed_' . $user->uid); + variable_del('scanner_partial_undo_' . $user->uid); + } + + if ($searchtype == 'search') { + return theme('scanner_results', array('results' => $results)); + } + else { // searchtype == 'replace' + if (count($undo_data) && !$shutting_down) { + $id = db_insert('scanner') + ->fields(array( + 'undo_data' => serialize($undo_data), + 'undone' => 0, + 'searched' => $search, + 'replaced' => $replace, + 'count' => count($undo_data), + 'time' => REQUEST_TIME, + )) + ->execute(); + } + + return theme('scanner_replace_results', array('results' => $results)); + } +} + +// *************************************************************************** +// Settings ****************************************************************** +// *************************************************************************** + +/** + * Search and Replace Settings form. + * + * @return $form + */ +function scanner_admin_form($node, &$form_state) { + $form['settings'] = array( + '#type' => 'fieldset', + '#title' => t('Default Options'), + ); + $form['settings']['scanner_mode'] = array( + '#type' => 'checkbox', + '#title' => t('Default: Case Sensitive Search Mode'), + '#default_value' => variable_get('scanner_mode', 0), + ); + $form['settings']['scanner_wholeword'] = array( + '#type' => 'checkbox', + '#title' => t('Default: Match Whole Word'), + '#default_value' => variable_get('scanner_wholeword', 0), + ); + $form['settings']['scanner_regex'] = array( + '#type' => 'checkbox', + '#title' => t('Default: Regular Expression Search'), + '#default_value' => variable_get('scanner_regex', 0), + ); + $form['settings']['scanner_published'] = array( + '#type' => 'checkbox', + '#title' => t('Default: Search Published Nodes Only'), + '#default_value' => variable_get('scanner_published', 1), + ); + $form['settings']['scanner_pathauto'] = array( + '#type' => 'checkbox', + '#title' => t('Default: Maintain Custom Aliases'), + '#default_value' => variable_get('scanner_pathauto', 1), + ); + + if (module_exists('taxonomy')) { + $vocabularies = taxonomy_get_vocabularies(); + + if (count($vocabularies)) { + $options = array(); + foreach ($vocabularies as $vocabulary) { + $options[$vocabulary->vid] = $vocabulary->name; + } + + $form['taxonomy'] = array( + '#type' => 'fieldset', + '#title' => t('Allow restrictions by terms in a vocabulary'), + '#description' => t('Adds search option to only match nodes tagged with specified terms from these vocabularies.'), + ); + $form['taxonomy']['scanner_vocabulary'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => variable_get('scanner_vocabulary', array()), + ); + } + } + + $form['tables'] = array( + '#type' => 'fieldset', + '#title' => t('Fields that will be searched'), + '#description' => t('Fields are listed in [nodetype: fieldname] order:'), + ); + + $table_map = _scanner_get_all_tables_map(); + sort($table_map); + foreach ($table_map as $item) { + $key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type']; + $form['tables'][$key] = array( + '#type' => 'checkbox', + '#title' => filter_xss('<strong>' . $item['type'] . ':</strong> ' . $item['field'], $allowed_values = array('strong', 'p')), + '#default_value' => variable_get($key, TRUE), + ); + } + + return system_settings_form($form); +} + +// *************************************************************************** +// Internal Utility Functions ************************************************ +// *************************************************************************** + +/** + * Get all text fields. + * + * @return map of fields and tables. + */ +function _scanner_get_all_tables_map() { + // Each array in the multidim array that is returned should be in the + // following order: type, field, table. + // This ensures that we can use the sort() function to easily sort the array + // based on the nodetype. + + // Build list of title, body, teaser fields for all node types. + $ntypes = node_type_get_types(); + foreach ($ntypes as $type) { + if ($type->has_title) { + $tables_map[] = array( + 'type' => $type->type, + 'field' => 'title', + 'table' => 'node_revision', + ); + } + } + + $all_field_records = array(); + + if (module_exists('field')) { + $query = db_select('field_config_instance', 'fci'); + $query->join('field_config', 'fc', 'fci.field_name = fc.field_name'); + $query->fields('fci', array('field_name', 'bundle')); + $query->condition('fci.entity_type', 'node'); + $query->condition('fc.module', 'text', '='); + $query->orderBy('bundle', 'ASC'); + + $result = $query->execute(); + + foreach ($result as $record) { + $all_field_records[] = $record; + } + } + + if (module_exists('field_collection')) { + // TODO Handle nested field_collections - i.e. field_collections inside field_collections + $query = db_select('field_config_instance', 'fci_fc'); + $query->join('field_config', 'fc_fc', 'fci_fc.field_name = fc_fc.field_name'); + $query->join('field_config_instance', 'fci_field', 'fci_field.bundle = fc_fc.field_name'); + $query->join('field_config', 'fc_field', 'fci_field.field_name = fc_field.field_name'); + $query->fields('fci_field', array('field_name')); + $query->fields('fci_fc', array('bundle')); + $query->addField('fc_fc', 'field_name', 'field_collection_name'); + $query->condition('fci_field.entity_type', 'field_collection_item'); + $query->condition('fci_fc.entity_type', 'node'); + $query->condition('fc_field.module', 'text', '='); + $query->orderBy('bundle', 'ASC'); + + $result = $query->execute(); + + foreach ($result as $record) { + $all_field_records[] = $record; + } + } + + foreach ($all_field_records as $record) { + $tables_map[] = array( + 'type' => $record->bundle, + 'field' => $record->field_name, + 'table' => 'field_revision_' . $record->field_name, + 'field_collection' => isset($record->field_collection_name) ? $record->field_collection_name : NULL, + 'field_collection_table' => isset($record->field_collection_name) ? 'field_revision_' . $record->field_collection_name : NULL + ); + } + + return $tables_map; +} + +/** + * Get the fields that have been selected for scanning. + * + * @return map of selected fields and tables. + */ +function _scanner_get_selected_tables_map() { + $tables_map = _scanner_get_all_tables_map(); + foreach ($tables_map as $i => $item) { + $key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type']; + if (!variable_get($key, TRUE)) { + unset($tables_map[$i]); + } + } + + return $tables_map; +} + +/** + * Attempt to stretch the amount of time available for processing so + * that timeouts don't interrupt search and replace actions. + * + * This only works in hosting environments where changing PHP and + * Apache settings on the fly is allowed. + */ +function _scanner_change_env($setting, $value, $verbose) { + $old_value = ini_get($setting); + + if ($old_value != $value && $old_value != 0) { + if (ini_set($setting, $value)) { + if ($verbose) { + drupal_set_message(t('%setting changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value))); + } + return TRUE; + } + else { + if ($verbose) { + drupal_set_message(t('%setting could not be changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value)), 'error'); + } + return FALSE; + } + } +} diff --git a/sites/all/modules/scanner/scanner.theme.inc b/sites/all/modules/scanner/scanner.theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..2ffe9512b768e6d1993c4e765632373c9cbfb765 --- /dev/null +++ b/sites/all/modules/scanner/scanner.theme.inc @@ -0,0 +1,85 @@ +<?php + +/** + * @file + * Theme callbacks for the scanner module. + */ + +/** + * Theme the search results. + */ +function theme_scanner_results($variables) { + $results = $variables['results']; + $output = NULL; + if (is_array($results)) { + $total = count($results); + + drupal_set_message(filter_xss('Found matches in ' . $total . ' fields. <a href="#results">See below</a> for details.', $allowed_tags = array('a'))); + $output = '<p>Found matches in ' . $total . ' fields:</p>'; + $output .= '<ol class="scanner-results scanner-search-results">'; + foreach ($results as $item) { + $output .= theme('scanner_item', array('item' => $item)); + } + $output .= '</ol>'; + // TODO: use pager to split up results + } + else { + drupal_set_message(t('No matches found. Check the !url for fields that can be searched.', array('!url' => l(t('settings'), 'admin/content/scanner/settings'))), 'warning'); + } + + return $output; +} + +/** + * Theme each search result hit. + */ +function theme_scanner_item($variables) { + $output = ''; + $item = $variables['item']; + $item['count'] = $item['count'] > 0 ? $item['count'] : 'One or more'; + $output .= '<li class="scanner-result">'; + $output .= '<span class="scanner-title">' . l($item['title'], 'node/' . $item['nid']) . '</span><br />'; + $output .= '<span class="scanner-info">[' . $item['count'] . ' matches in ' . $item['type'] . ' ' . $item['field'] . ' field:]</span><br />'; + $output .= '<span class="scanner-text">' . $item['text'] . '</span>'; + $output .= '</li>'; + + return $output; +} + +/** + * Theme the replace results. + */ +function theme_scanner_replace_results($variables) { + $results = $variables['results']; + $output = ''; + if (is_array($results)) { + drupal_set_message(filter_xss('Replaced items in ' . count($results) . ' fields. <a href="#results">See below</a> for details.', $allowed_tags = array('a'))); + $output = '<p>Replaced items in ' . count($results) . ' fields:</p>'; + $output .= '<ol class="scanner-results scanner-replace-results">'; + foreach ($results as $item) { + $output .= theme('scanner_replace_item', array('item' => $item)); + } + $output .= '</ol>'; + // TODO: use pager to split up results + } + else { + drupal_set_message(t('No matches found. Check the !url for fields that can be searched.', array('!url' => l(t('settings'), 'admin/content/scanner/settings'))), 'warning'); + } + + return $output; +} + +/** + * Theme each replace result hit. + */ +function theme_scanner_replace_item($variables) { + $output = ''; + $item = $variables['item']; + $item['count'] = $item['count'] > 0 ? $item['count'] : 'One or more'; + $output .= '<li class="scanner-result">'; + $output .= '<span class="scanner-title">' . l($item['title'], 'node/' . $item['nid']) . '</span><br />'; + $output .= '<span class="scanner-info">[' . $item['count'] . ' replacements in ' . $item['type'] . ' ' . $item['field'] . ' field]</span>'; + $output .= '</li>'; + + return $output; +} diff --git a/sites/all/modules/token/tests/token_test.info b/sites/all/modules/token/tests/token_test.info index a344aa29bd899a1049cb917cd3805f1dea474581..141ed5cfcb339ccb1d25dc3fc7156229e8687eaa 100644 --- a/sites/all/modules/token/tests/token_test.info +++ b/sites/all/modules/token/tests/token_test.info @@ -5,9 +5,9 @@ core = 7.x files[] = token_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2012-09-24 -version = "7.x-1.4" +; Information added by drupal.org packaging script on 2013-02-24 +version = "7.x-1.5" core = "7.x" project = "token" -datestamp = "1348497279" +datestamp = "1361665026" diff --git a/sites/all/modules/token/token.drush.inc b/sites/all/modules/token/token.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..fc3235730d216f335682feef6749fbe1ef47d0de --- /dev/null +++ b/sites/all/modules/token/token.drush.inc @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Drush integration for the Token module. + */ + +/** + * Implements hook_drush_cache_clear(). + */ +function token_drush_cache_clear(&$types) { + if (function_exists('module_exists') && module_exists('token')) { + $types['token'] = 'drush_token_cache_clear_token_info'; + } +} + +/** + * Clear caches internal to Token module. + */ +function drush_token_cache_clear_token_info() { + token_clear_cache(); +} diff --git a/sites/all/modules/token/token.info b/sites/all/modules/token/token.info index 8d14814aca2c3bd773606c2d749be8d076bd8a2f..43fced193354ae6462affbd5fe79215aec4eee65 100644 --- a/sites/all/modules/token/token.info +++ b/sites/all/modules/token/token.info @@ -1,15 +1,11 @@ name = Token description = Provides a user interface for the Token API and some missing core tokens. core = 7.x -files[] = token.module -files[] = token.install -files[] = token.tokens.inc -files[] = token.pages.inc files[] = token.test -; Information added by drupal.org packaging script on 2012-09-24 -version = "7.x-1.4" +; Information added by drupal.org packaging script on 2013-02-24 +version = "7.x-1.5" core = "7.x" project = "token" -datestamp = "1348497279" +datestamp = "1361665026" diff --git a/sites/all/modules/token/token.js b/sites/all/modules/token/token.js index 88a75c1017e2e29cc8c0da42d73a989e43890f29..98d1ac3a5af68e585cdef59d75ed56bba34a694f 100644 --- a/sites/all/modules/token/token.js +++ b/sites/all/modules/token/token.js @@ -14,6 +14,12 @@ Drupal.behaviors.tokenDialog = { $('a.token-dialog', context).once('token-dialog').click(function() { var url = $(this).attr('href'); var dialog = $('<div style="display: none" class="loading">' + Drupal.t('Loading token browser...') + '</div>').appendTo('body'); + + // Emulate the AJAX data sent normally so that we get the same theme. + var data = {}; + data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme; + data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token; + dialog.dialog({ title: $(this).attr('title') || Drupal.t('Available tokens'), width: 700, @@ -24,7 +30,7 @@ Drupal.behaviors.tokenDialog = { // Load the token tree using AJAX. dialog.load( url, - {}, + data, function (responseText, textStatus, XMLHttpRequest) { dialog.removeClass('loading'); } diff --git a/sites/all/modules/token/token.module b/sites/all/modules/token/token.module index 7bba8a7427b71731961bbf3dd1a151af76781698..88bcc6093a476deda374beb4753021129b8d3acb 100644 --- a/sites/all/modules/token/token.module +++ b/sites/all/modules/token/token.module @@ -78,6 +78,7 @@ function token_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, 'file' => 'token.pages.inc', + 'theme callback' => 'ajax_base_page_theme', ); // Devel token pages. @@ -265,7 +266,7 @@ function token_form_block_admin_configure_alter(&$form, $form_state) { $form['settings']['title']['#description'] .= ' ' . t('This field supports tokens.'); // @todo Figure out why this token validation does not seem to be working here. $form['settings']['title']['#element_validate'][] = 'token_element_validate'; - $form['settings']['title']['#token_types'] = array(); + $form['settings']['title'] += array('#token_types' => array()); } /** @@ -377,23 +378,26 @@ function token_clear_cache() { * @see token_entity_info_alter() * @see http://drupal.org/node/737726 */ -function token_get_entity_mapping($value_type = 'token', $value = NULL) { +function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallback = FALSE) { $mapping = &drupal_static(__FUNCTION__, array()); if (empty($mapping)) { foreach (entity_get_info() as $entity_type => $info) { $mapping[$entity_type] = !empty($info['token type']) ? $info['token type'] : $entity_type; } + // Allow modules to alter the mapping array. + drupal_alter('token_entity_mapping', $mapping); } if (!isset($value)) { - return $mapping; + return $value_type == 'token' ? array_flip($mapping) : $mapping; } elseif ($value_type == 'token') { - return array_search($value, $mapping); + $return = array_search($value, $mapping); + return $return !== FALSE ? $return : ($fallback ? $value : FALSE); } elseif ($value_type == 'entity') { - return isset($mapping[$value]) ? $mapping[$value] : FALSE; + return isset($mapping[$value]) ? $mapping[$value] : ($fallback ? $value : FALSE); } } @@ -739,24 +743,28 @@ function token_element_validate_token_context(&$element, &$form_state) { * Implements hook_form_FORM_ID_alter(). */ function token_form_field_ui_field_edit_form_alter(&$form, $form_state) { - if (!isset($form['instance'])) { + if (!isset($form['instance']) || !empty($form['#field']['locked'])) { return; } if (($form['#field']['type'] == 'file' || $form['#field']['type'] == 'image') && isset($form['instance']['settings']['file_directory']) && !module_exists('filefield_paths')) { // GAH! We can only support global tokens in the upload file directory path. $form['instance']['settings']['file_directory']['#element_validate'][] = 'token_element_validate'; - $form['instance']['settings']['file_directory']['#token_types'] = array(); - $form['instance']['settings']['token_tree'] = array( - '#theme' => 'token_tree', - '#token_types' => array(), - '#weight' => $form['instance']['settings']['file_directory']['#weight'] + 0.5, - ); + $form['instance']['settings']['file_directory'] += array('#token_types' => array()); $form['instance']['settings']['file_directory']['#description'] .= ' ' . t('This field supports tokens.'); } // Note that the description is tokenized via token_field_widget_form_alter(). $form['instance']['description']['#description'] .= '<br />' . t('This field supports tokens.'); + $form['instance']['description']['#element_validate'][] = 'token_element_validate'; + $form['instance']['description'] += array('#token_types' => array()); + + $form['instance']['settings']['token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array(), + '#dialog' => TRUE, + '#weight' => $form['instance']['description']['#weight'] + 0.5, + ); } /** @@ -775,6 +783,7 @@ function token_form_system_actions_configure_alter(&$form, $form_state) { $form['token_tree'] = array( '#theme' => 'token_tree', '#token_types' => 'all', + '#dialog' => TRUE, '#weight' => 100, ); // @todo Add token validation to the action fields that can use tokens. @@ -836,10 +845,11 @@ function token_form_user_admin_settings_alter(&$form, &$form_state) { } // Add the token tree UI. - $form['token_tree'] = array( + $form['email']['token_tree'] = array( '#theme' => 'token_tree', '#token_types' => array('user'), '#show_restricted' => TRUE, + '#dialog' => TRUE, '#weight' => 90, ); } diff --git a/sites/all/modules/token/token.pages.inc b/sites/all/modules/token/token.pages.inc index 905943ca074a3392a8e4a5c1de9d5f79da80d821..2341a9f981c6dc6c1ff88dff2d626a4bef2a734f 100644 --- a/sites/all/modules/token/token.pages.inc +++ b/sites/all/modules/token/token.pages.inc @@ -57,7 +57,7 @@ function token_page_output_tree() { $options['dialog'] = FALSE; $output = theme('token_tree', $options); - print '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head>'; + print '<html><head>' . drupal_get_css() . drupal_get_js() . '</head>'; print '<body class="token-tree">' . $output . '</body></html>'; drupal_exit(); } @@ -231,7 +231,7 @@ function _token_clean_css_identifier($id) { /** * Menu callback; prints the available tokens and values for an object. */ -function token_devel_token_object($entity_type, $entity) { +function token_devel_token_object($entity_type, $entity, $token_type = NULL) { $header = array( t('Token'), t('Value'), @@ -243,7 +243,10 @@ function token_devel_token_object($entity_type, $entity) { 'values' => TRUE, 'data' => array($entity_type => $entity), ); - $tree = token_build_tree($entity_type, $options); + if (!isset($token_type)) { + $token_type = $entity_type; + } + $tree = token_build_tree($token_type, $options); foreach ($tree as $token => $token_info) { if (!empty($token_info['restricted'])) { continue; diff --git a/sites/all/modules/token/token.test b/sites/all/modules/token/token.test index b7e9582425595cee521d51ff5593161fd542817e..59fa6457b5e937186a520d7e26833675c9061def 100644 --- a/sites/all/modules/token/token.test +++ b/sites/all/modules/token/token.test @@ -676,10 +676,12 @@ class TokenEntityTestCase extends TokenTestHelper { $this->assertIdentical(token_get_entity_mapping('token', 'term'), 'taxonomy_term'); $this->assertIdentical(token_get_entity_mapping('token', 'vocabulary'), 'taxonomy_vocabulary'); $this->assertIdentical(token_get_entity_mapping('token', 'invalid'), FALSE); + $this->assertIdentical(token_get_entity_mapping('token', 'invalid', TRUE), 'invalid'); $this->assertIdentical(token_get_entity_mapping('entity', 'node'), 'node'); $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_term'), 'term'); $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_vocabulary'), 'vocabulary'); $this->assertIdentical(token_get_entity_mapping('entity', 'invalid'), FALSE); + $this->assertIdentical(token_get_entity_mapping('entity', 'invalid', TRUE), 'invalid'); // Test that when we send the mis-matched entity type into token_replace() // that we still get the tokens replaced. diff --git a/sites/all/modules/token/token.tokens.inc b/sites/all/modules/token/token.tokens.inc index 3dc2d3b4f210a70f5c9da7507bad25f11f743042..e0c0b5e91305c98e549b199573779bd777cbf8c5 100644 --- a/sites/all/modules/token/token.tokens.inc +++ b/sites/all/modules/token/token.tokens.inc @@ -80,7 +80,7 @@ function token_token_info_alter(&$info) { foreach ($date_format_types as $date_format_type => $date_format_type_info) { if (!isset($info['tokens']['date'][$date_format_type])) { $info['tokens']['date'][$date_format_type] = array( - 'name' => $date_format_type_info['title'], + 'name' => check_plain($date_format_type_info['title']), 'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))), 'module' => 'token', ); diff --git a/sites/all/modules/trailing_slash/LICENSE.txt b/sites/all/modules/trailing_slash/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/trailing_slash/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/trailing_slash/README.txt b/sites/all/modules/trailing_slash/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..32f5c106975ceda0aeb569a3c27f00fcac954f7d --- /dev/null +++ b/sites/all/modules/trailing_slash/README.txt @@ -0,0 +1,53 @@ +TRAILING SLASH DRUPAL MODULE +---------------------------- + +What is it? +----------- +Adds trailing slashes to all Drupal generated clean URLs. +For example: example.com/user/. + +How do I install it? +-------------------- +1. Install and enable this module using one of the following methods: +http://drupal.org/documentation/install/modules-themes/modules-7 + +2. Add a redirect for your website that enforces trailing slashes using one of the following methods (not having duplicate pages is good for SEO): + +Apache mod_rewrite example (in .htaccess): + +RewriteEngine On +RewriteBase / +RewriteCond %{REQUEST_METHOD} !=post [NC] +RewriteRule ^(.*(?:^|/)[^/\.]+)$ $1/ [L,R=301] + +IIS URL Rewrite example (in web.config): + +<configuration> + <system.webServer> + <rewrite> + <rules> + <rule name="Redirect to Trailing Slashes" enabled="true" stopProcessing="true"> + <match url="^(.*(?:^|/)[^/\.]+)$" /> + <conditions logicalGrouping="MatchAll" trackAllCaptures="false"> + <add input="{REQUEST_METHOD}" pattern="post" negate="true" /> + </conditions> + <action type="Redirect" url="{R:1}/" /> + </rule> + </rules> + </rewrite> + </system.webServer> +</configuration> + +GlobalRedirect module [http://drupal.org/project/globalredirect] +Installing this module is a good way to perform global redirects if you can't or don't want to use web server configured redirects. + +3. Go to Administration > Configuration > Search and metadata > Clean URLs in +Drupal and ensure that Enable trailing slashes is checked. Easy as that!! + +Known Issues/Bugs +----------------- +None. + +Sponsors +-------- +Development of this module was sponsored by the Australian War Memorial [http://www.awm.gov.au/]. diff --git a/sites/all/modules/trailing_slash/trailing_slash.info b/sites/all/modules/trailing_slash/trailing_slash.info new file mode 100644 index 0000000000000000000000000000000000000000..a9b4cd5ed0625a7ba76e0f022fc088fe370852f1 --- /dev/null +++ b/sites/all/modules/trailing_slash/trailing_slash.info @@ -0,0 +1,11 @@ +name = Trailing Slash +description = Adds trailing slashes to all Drupal generated URLs. +core = 7.x +configure = admin/config/search/clean-urls + +; Information added by drupal.org packaging script on 2012-07-03 +version = "7.x-1.0" +core = "7.x" +project = "trailing_slash" +datestamp = "1341302749" + diff --git a/sites/all/modules/trailing_slash/trailing_slash.install b/sites/all/modules/trailing_slash/trailing_slash.install new file mode 100644 index 0000000000000000000000000000000000000000..8ddd85acd40faa78803c0cfe3e4a972ba666a2f9 --- /dev/null +++ b/sites/all/modules/trailing_slash/trailing_slash.install @@ -0,0 +1,13 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the Trailing Slash module. + */ + +/** + * Implements hook_uninstall(). + */ +function trailing_slash_uninstall() { + variable_del('trailing_slash'); +} diff --git a/sites/all/modules/trailing_slash/trailing_slash.module b/sites/all/modules/trailing_slash/trailing_slash.module new file mode 100644 index 0000000000000000000000000000000000000000..38dd83d9c962e6eb025e2e07e4dec3de896aab32 --- /dev/null +++ b/sites/all/modules/trailing_slash/trailing_slash.module @@ -0,0 +1,154 @@ +<?php + +/** + * @file + * Adds checkbox to the Clean URLs settings form and alters outbound URLs. + */ + +/** + * Function to add a trailing slash to a path if there isn't one already. + */ +function trailing_slash_add(&$path) { + $path = preg_replace('/((?:^|\\/)[^\\/\\.]+?)$/isD', '$1/', $path); +} + +/** + * Implements hook_url_outbound_alter(). + */ +function trailing_slash_url_outbound_alter(&$path, &$options, $original_path) { + // If Clean URLs and Trailing Slashes aren't enabled, don't run. + if (empty($GLOBALS['conf']['clean_url']) || !variable_get('trailing_slash', TRUE)) { + return; + } + + // If URL is empty but has a prefix, set the path to the prefix. + if (($path == '<front>' || empty($path)) && isset($options['prefix']) && $options['prefix'] != '') { + $path = $options['prefix']; + $options['prefix'] = ''; + } + + // If the URL is external, the front page, or an empty path, don't run. + if ($options['external'] || $path == '<front>' || empty($path)) { + return; + } + + // If Global Redirect is installed but not being used to enforce trailing + // slashes, we need to not add a trailing slash when it checks to see if a + // page needs to be redirected. + if (module_exists('globalredirect') && ($settings = _globalredirect_get_settings()) && _globalredirect_is_active($settings) && !$settings['deslash']) { + // Use backtrace to find out if the globalredirect_init() function called + // this function. + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + if (isset($backtrace[2]) && isset($backtrace[2]['function']) && strtolower($backtrace[2]['function']) == 'globalredirect_init') { + return; + } + unset($backtrace); + } + + // Pre-alias if not already an alias. The only available hook is before + // aliasing is done, so we need to pre-alias to modify the final URL path. + if (!$options['alias']) { + // This is a copy of Drupal core code. + $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; + $alias = drupal_get_path_alias($original_path, $language); + if ($alias != $original_path) { + $path = $alias; + } + // This is where the copy of Drupal core code ends. + + // Mark the path as an alias so the alias code isn't needlessly re-run. + $options['alias'] = TRUE; + } + + // Add a trailing slash. + trailing_slash_add($path); + +} + +/** + * Implements hook_admin_paths_alter(). + * + * Adds trailing slash versions of admin paths. + */ +function trailing_slash_admin_paths_alter(&$paths) { + $new_paths = array(); + foreach ($paths as $path => $enabled) { + // Ignore paths that end in a wildcard (*) as these will already + // work with trailing slashes. + if (!preg_match('/\\*$/isD', $path)) { + $new_path = $path; + trailing_slash_add($new_path); + + // Only add a new admin path if the trailing slash version is + // different. + if ($new_path != $path) { + $new_paths[$new_path] = $enabled; + } + } + } + + $paths = array_merge($new_paths, $paths); +} + +/** + * Implements hook_boot(). + * + * Cleanup $_GET['q'] on bootstrap. + * + * Some modules might not cater for URLs that end in a trailing slash and might + * accidentally concatenate two or more slashes together in a URL path. This + * replaces any occurences of multiple slashes in $_GET['q'] with only one so + * that such URLs are still routed by Drupal properly. + */ +function trailing_slash_boot() { + $_GET['q'] = preg_replace('/\/{2,}/', '/', $_GET['q']); +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function trailing_slash_form_system_clean_url_settings_alter(&$form, &$form_state, $form_id) { + // If Clean URLs can be enabled, add checkbox to enable/disable trailing + // slashes. + if (isset($form['clean_url'])) { + $form['trailing_slash'] = array( + '#type' => 'checkbox', + '#title' => t('Enable trailing slashes'), + '#default_value' => variable_get('trailing_slash', TRUE), + '#description' => t('Add a trailing slash to URLs eg. <code>example.com/user/</code>.'), + '#states' => array( + 'invisible' => array( + 'input[name="clean_url"]' => array('checked' => FALSE), + ), + ), + ); + } +} + +/** + * Implements custom_url_rewrite_outbound(). + * + * This is for Drupal 6.x support and any Drupal 7.x modules that may use it + * (eg. GlobalRedirect). + */ +function custom_url_rewrite_outbound(&$path, $options, $original_path) { + // If Clean URLs and Trailing Slashes are enabled and not external, front + // page, or empty path, add a trailing slash. + if (!empty($GLOBALS['conf']['clean_url']) && variable_get('trailing_slash', TRUE) && !$options['external'] && $path != '<front>' && !empty($path)) { + trailing_slash_add($path); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Change GlobalRedirect settings form to allow enforcement of trailing slashes. + */ +function trailing_slash_form_globalredirect_settings_alter(&$form, &$form_state, $form_id) { + if (variable_get('trailing_slash', TRUE)) { + $form['settings']['deslash'] = array_merge($form['settings']['deslash'], array( + '#title' => t('Enforce trailing slash'), + '#description' => t('If enabled, this option will enforce the trailing slash in requests. This stops requests such as example.com/node/1/ and example.com/node/1 causing duplicate content. On the other hand, if you require certain requests to not have a trailing slash, this feature can cause problems so may need to be disabled.'), + )); + } +} diff --git a/sites/all/modules/unl/includes/common.php b/sites/all/modules/unl/includes/common.php index 22dc7cc8702fda4c0ddbdf23e7e9738f2febff59..a00e4fb588807c7fa4928045028a8a36e90c6949 100644 --- a/sites/all/modules/unl/includes/common.php +++ b/sites/all/modules/unl/includes/common.php @@ -204,7 +204,7 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) { unl_load_zend_framework(); if (!Zend_Uri::check($url)) { - drupal_set_message('A non-url was passed to ' . __FUNCTION__ . '().', 'warning'); + watchdog('unl', 'A non-url was passed to %func().', array('%func' => __FUNCTION__), WATCHDOG_WARNING); return FALSE; } @@ -217,16 +217,29 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) // If cached in the static array, return it. if (array_key_exists($url, $static)) { $headers = $static[$url]['headers']; + + // Don't let this page be cached since it contains uncacheable content. + $GLOBALS['conf']['cache'] = FALSE; + return $static[$url]['body']; } - // If cached in the drupla cache, return it. + // If cached in the drupal cache, return it. $data = cache_get(__FUNCTION__ . $url); if ($data && time() < $data->data['expires']) { $headers = $data->data['headers']; + + // Don't let this page be cached any longer than the retrieved content. + $GLOBALS['conf']['page_cache_maximum_age'] = min(variable_get('page_cache_maximum_age', 0), $data->data['expires'] - time()); + return $data->data['body']; } + if (!$context) { + // Set a 5 second timeout + $context = stream_context_create(array('http' => array('timeout' => 5))); + } + // Make the request $http_response_header = array(); $body = file_get_contents($url, NULL, $context); @@ -256,7 +269,13 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) $matches = array(); if (preg_match('/max-age=([0-9]+)/', $cacheControl, $matches)) { $expires = time() + $matches[1]; - $cacheable = TRUE; + if (array_key_exists('age', $lowercaseHeaders)) { + $expires -= $lowercaseHeaders['age']; + } + + if ($expires > time()) { + $cacheable = TRUE; + } } if (strpos($cacheControl, 'private') !== FALSE) { $cacheable = FALSE; @@ -279,6 +298,9 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) 'expires' => $expires, ); cache_set(__FUNCTION__ . $url, $data, 'cache', $expires); + + // Don't let this page be cached any longer than the retrieved content. + $GLOBALS['conf']['page_cache_maximum_age'] = min(variable_get('page_cache_maximum_age', 0), $expires - time()); } // Otherwise just save to the static per-request cache else { @@ -286,6 +308,9 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) 'body' => $body, 'headers' => $headers, ); + + // Don't let this page be cached since it contains uncacheable content. + $GLOBALS['conf']['cache'] = FALSE; } return $body; diff --git a/sites/all/modules/unl/unl.module b/sites/all/modules/unl/unl.module index f478cc2b7bee6a3eb9569a662be614d99a0e63d6..0e8f018c7b859ddc29a284d65c6d74b4e456be17 100644 --- a/sites/all/modules/unl/unl.module +++ b/sites/all/modules/unl/unl.module @@ -7,8 +7,14 @@ require_once dirname(__FILE__) . '/includes/common.php'; */ function unl_help($path, $arg) { switch ($path) { + case 'admin/modules/uninstall': + if ((module_exists('unl_multisite') && conf_path() == 'sites/default') || unl_table_is_shared('role_permission')) { + return '<h1>Be careful! Uninstalling a module clears its permissions which are shared globally between sites!</h1>'; + } case 'admin/people/permissions': - return '<h1>Be careful! Permissions are shared globally between sites on UNLcms!</h1>'; + if ((module_exists('unl_multisite') && conf_path() == 'sites/default') || unl_table_is_shared('role_permission')) { + return '<h1>Be careful! Permissions are shared globally between sites!</h1>'; + } case 'user/unl/technical_feedback': return '<p>Consider visiting the Web Developer Network <a href="http://wdn.unl.edu/help/irc.shtml" target="_blank">chat room</a> as well.</p>'; } @@ -573,6 +579,7 @@ function unl_form_alter(&$form, $form_state, $form_id) { '#type' => 'checkbox', '#title' => 'SSL Enabled', '#default_value' => variable_get('https', FALSE), + '#disabled' => !unl_user_is_administrator(), ); if (conf_path() != 'sites/default') { @@ -586,6 +593,7 @@ function unl_form_alter(&$form, $form_state, $form_id) { '#type' => 'select', '#options' => $base_urls, '#default_value' => variable_get('unl_primary_base_url'), + '#disabled' => !unl_user_is_administrator(), ); $form['#submit'][] = 'unl_system_settings_form_submit'; } @@ -896,7 +904,7 @@ function unl_init() { $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); $path = substr(urldecode($request_path), $base_path_len + 1); - drupal_goto(url($primary_base_url . $path, array('query' => drupal_get_query_parameters()))); + drupal_goto(url($primary_base_url . $path, array('query' => drupal_get_query_parameters())), array(), 301); } } @@ -1025,7 +1033,10 @@ function unl_query_alter(QueryAlterableInterface $query) { // Join it with the users_roles tables so that only users with roles are seleceted. $query->join('users_roles', 'unl_distinct_prefix_r', $usersTableAlias . '.uid = unl_distinct_prefix_r.uid'); if (!unl_user_is_administrator()) { + // Hide users that are only in the administrator role $query->where('unl_distinct_prefix_r.rid != ' . unl_shared_variable_get('user_admin_role', -1)); + // Hide the default admin user. + $query->where($usersTableAlias . '.uid != 1'); } } } @@ -1103,13 +1114,19 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac // Break down the URL target then rebuild it as absolute. $url = substr($match, 1, -1); + $url = html_entity_decode($url); $parts = parse_url($url); if (!isset($parts['scheme'])) { - $parts['scheme'] = $_SERVER['HTTPS'] ? 'https' : 'http'; + $parts['scheme'] = isset($_SERVER['HTTPS']) ? 'https' : 'http'; } if (!isset($parts['host'])) { - $parts['host'] = $_SERVER['HTTP_HOST']; + $parts['host'] = $_SERVER['SERVER_NAME']; } + /* We can't do this on production because we're running on port 8080. + if (!isset($parts['port']) && !in_array($_SERVER['SERVER_PORT'], array(80, 443))) { + $parts['port'] = $_SERVER['SERVER_PORT']; + } + */ if (isset($parts['path']) && substr($parts['path'], 0, 1) != '/') { if (variable_get('unl_use_base_tag')) { $parts['path'] = $GLOBALS['base_path'] . $parts['path']; @@ -1120,8 +1137,10 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac if (!isset($parts['path'])) { $parts['path'] = '/'; } - $url = $parts['scheme'] . '://' . $parts['host'] . $parts['path']; - + $url = $parts['scheme'] + . '://' . $parts['host'] + . (isset($parts['port']) ? ':' . $parts['port'] : '') + . $parts['path']; // If this is a request to another UNL site, add format=partial to the query. if (substr($parts['host'], -7) == 'unl.edu') { @@ -1141,31 +1160,19 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac $url .= '#' . $parts['fragment']; } - $ssiDepth = 0; - if (array_key_exists('HTTP_X_UNL_SSI_DEPTH', $_SERVER)) { - $ssiDepth = $_SERVER['HTTP_X_UNL_SSI_DEPTH']; - } - $ssiDepth++; - - $context = stream_context_create(array( - 'http' => array( - 'header' => "x-unl-ssi-depth: $ssiDepth\r\n", - ), - )); - - if ($ssiDepth > 3) { - watchdog('unl', 'Server Side Include: Recursion depth limit reached.', array(), WATCHDOG_ERROR); - drupal_add_http_header('x-unl-ssi-error', 'Too deep!'); - $content = '<!-- Error: Too many recursive includes! Content from ' . $url . ' was not included! -->'; + // If the varnish module is enabled, and the SSI is for a URL on our server, do an ESI. + if (module_exists('varnish') && + isset($_SERVER['HTTP_X_VARNISH']) && + gethostbyname($parts['host']) == gethostbyname($_SERVER['SERVER_NAME']) && + $parts['scheme'] == 'http' + ) { + $content = _unl_ssi_to_esi($url); } + // Otherwise, emulate the SSI in drupal. else { - $headers = array(); - $content = unl_url_get_contents($url, $context, $headers); - if (array_key_exists('x-unl-ssi-error', $headers)) { - watchdog('unl', 'Server Side Include: An included URL reached the depth limit.', array(), WATCHDOG_WARNING); - drupal_add_http_header('x-unl-ssi-error', 'The included URL caused recursion that was too deep!'); - } + $content = _unl_ssi_emulate($url); } + $replacements[$full_match] = PHP_EOL . '<!-- Begin content from ' . $url . ' -->' . PHP_EOL . $content . PHP_EOL @@ -1178,3 +1185,49 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac return $text; } + +/** + * Used by unl_filter_ssi_process() to emulate the SSI process inside drupal. + * @param $url + * @return string + */ +function _unl_ssi_emulate($url) { + $ssiDepth = 0; + if (array_key_exists('HTTP_X_UNL_SSI_DEPTH', $_SERVER)) { + $ssiDepth = $_SERVER['HTTP_X_UNL_SSI_DEPTH']; + } + $ssiDepth++; + + $context = stream_context_create(array( + 'http' => array( + 'header' => "x-unl-ssi-depth: $ssiDepth\r\n", + ), + )); + + if ($ssiDepth > 3) { + watchdog('unl', 'Server Side Include: Recursion depth limit reached.', array(), WATCHDOG_ERROR); + drupal_add_http_header('x-unl-ssi-error', 'Too deep!'); + $content = '<!-- Error: Too many recursive includes! Content from ' . $url . ' was not included! -->'; + } + else { + $headers = array(); + $content = unl_url_get_contents($url, $context, $headers); + if (array_key_exists('x-unl-ssi-error', $headers)) { + watchdog('unl', 'Server Side Include: An included URL reached the depth limit.', array(), WATCHDOG_WARNING); + drupal_add_http_header('x-unl-ssi-error', 'The included URL caused recursion that was too deep!'); + } + } + + return $content; +} + +/** + * Used by unl_filter_ssi_process() to change the SSI into an ESI + * @param $url + * @return string + */ +function _unl_ssi_to_esi($url) { + // Set a header so that Varnish knows to do ESI processing on this response. + drupal_add_http_header('X-ESI', 'yes'); + return '<esi:include src="' . check_plain($url) . '"/>'; +} diff --git a/sites/all/modules/unl_cas/unl_cas.admin.inc b/sites/all/modules/unl_cas/unl_cas.admin.inc index 3a5347b5483da74997e925f450213934867d8a3b..b8e7157861ab5d1eeffee49d6e60fc1570c695f1 100644 --- a/sites/all/modules/unl_cas/unl_cas.admin.inc +++ b/sites/all/modules/unl_cas/unl_cas.admin.inc @@ -22,7 +22,7 @@ function unl_cas_user_import($form, &$form_state) { '#type' => 'submit', '#value' => 'Search', '#submit' => array('unl_cas_user_import_search'), -# '#validate' => array('unl_cas_user_validate'), + '#validate' => array('unl_cas_user_import_validate_search'), ); if (isset($form_state['values']['name'])) { @@ -93,22 +93,33 @@ function unl_cas_user_import($form, &$form_state) { '#type' => 'submit', '#value' => 'Add Selected User', '#submit' => array('unl_cas_user_import_submit'), + '#validate' => array('unl_cas_user_import_validate_add'), ); } return $form; } function unl_cas_user_import_search($form, &$form_state) { + if (!$form_state['values']['name']) { + form_set_error('username', 'Please enter a search string.'); + } // if only one result is returned should we instead create the user? $form_state['rebuild'] = TRUE; } -function unl_cas_user_import_submit($form, &$form_state) { +function unl_cas_user_import_validate_search($form, &$form_state) { + if (!$form_state['values']['name']) { + form_set_error('username', 'Please enter a search term.'); + } +} + +function unl_cas_user_import_validate_add($form, &$form_state) { if (!$form_state['values']['username']) { - drupal_set_message('Please select a user.', 'error'); - $form_state['rebuild'] = TRUE; - return; + form_set_error('username', 'Please select a user.'); } +} + +function unl_cas_user_import_submit($form, &$form_state) { $user = unl_cas_import_user($form_state['values']['username']); diff --git a/sites/all/modules/unl_cas/unl_cas.module b/sites/all/modules/unl_cas/unl_cas.module index 3bde41045e53b008eb2a039b02ad025977c04018..c133b3d8468214af49f754dfe665c41896b7b996 100644 --- a/sites/all/modules/unl_cas/unl_cas.module +++ b/sites/all/modules/unl_cas/unl_cas.module @@ -207,7 +207,7 @@ function unl_cas_user_logout($account) { function unl_cas_import_user($username) { unl_load_zend_framework(); $user = array(); - + $result = array(); // First, try getting the info from LDAP. try { $ldap = new Unl_Ldap(unl_cas_get_setting('ldap_uri')); @@ -215,42 +215,50 @@ function unl_cas_import_user($username) { $results = $ldap->search('dc=unl,dc=edu', 'uid=' . $username); if (count($results) > 0) { $result = $results[0]; - - $user['firstName'] = $result['givenname'][0]; - $user['lastName'] = $result['sn'][0]; - $user['email'] = $result['mail'][0]; - $user['displayName'] = $result['displayname'][0]; } } catch (Exception $e) { - // don't do anything, just go on to try the peoplefinder method + // don't do anything, just go on to try the PeopleFinder method } - // Next, if LDAP didn't work, try peoplefinder/directory service. - if (!isset($user['email'])) { - $xml = @file_get_contents('http://directory.unl.edu/service.php?format=xml&uid=' . $username); - if ($xml) { - $dom = new DOMDocument(); - $dom->loadXML($xml); - $user['firstName'] = $dom->getElementsByTagName('givenName')->item(0)->textContent; - $user['lastName'] = $dom->getElementsByTagName('sn')->item(0)->textContent; - $user['email'] = $dom->getElementsByTagName('mail')->item(0)->textContent; - $user['displayName'] = $dom->getElementsByTagName('displayName')->item(0)->textContent; + // Next, if LDAP didn't work, try PeopleFinder service. + if (!$result) { + $json = unl_url_get_contents('http://directory.unl.edu/service.php?format=json&uid=' . $username); + if ($json) { + $result = json_decode($json, TRUE); } } - // Finally, if peoplefinder didn't work either, just guess. - if (!isset($user['email'])) { - $user['email'] = $username . '@unl.edu'; - } - + // Create the fields we will be using, and make an initial guess at the email address. $userData = array( 'name' => $username, - 'mail' => $user['email'], + 'mail' => $username . '@unl.edu', 'status' => 1, - 'timezone' => variable_get('date_default_timezone', @date_default_timezone_get()), + 'timezone' => variable_get('date_default_timezone', date_default_timezone_get()), + 'data' => array('unl' => array( + 'fullName' => '', + 'affiliations' => '', + 'primaryAffiliation' => '', + 'department' => '', + 'major' => '', + 'studentStatus' => array(), + )), ); + // If either LDAP or PeopleFinder found data, use it. + if ($result) { + $result = array_change_key_case($result, CASE_LOWER); + $userData['mail'] = $result['mail'][0]; + $userData['data']['unl'] = array( + 'fullName' => (isset($result['edupersonnickname']) ? $result['edupersonnickname'][0] : $result['givenname'][0]) . ' ' . $result['sn'][0], + 'affiliations' => $result['edupersonaffiliation'], + 'primaryAffiliation' => $result['edupersonprimaryaffiliation'][0], + 'department' => (isset($result['unlhrprimarydepartment']) ? $result['unlhrprimarydepartment'][0] : ''), + 'major' => (isset($result['unlsismajor']) ? $result['unlsismajor'][0] : ''), + 'studentStatus' => (isset($result['unlsisstudentstatus']) ? $result['unlsisstudentstatus'] : array()), + ); + } + $account = user_load_by_name($username); return user_save($account, $userData); diff --git a/sites/all/modules/unl_migration/unl_migration.php b/sites/all/modules/unl_migration/unl_migration.php index 62d4d61312f143018866d3bd58e9fa1559afa323..f87a9be3ae6d0a08848bca32955222cb55001f92 100755 --- a/sites/all/modules/unl_migration/unl_migration.php +++ b/sites/all/modules/unl_migration/unl_migration.php @@ -168,16 +168,17 @@ class Unl_Migration_Tool private $_liferaySubsites = array( 'cropwatch.unl.edu' => array('corn', 'drybeans', 'forages', 'organic', 'potato', 'sorghum', 'soybeans', 'wheat', 'bioenergy', 'insect', 'economics', 'ssm', 'soils', 'tillage', 'weed', 'varietytest', 'biotechnology', 'farmresearch', 'cropwatch-youth', 'militaryresources', 'gaps', 'sugarbeets'), - '4h.unl.edu' => array('extension-4-h-horse', '4hcamps', '4hcurriclum'), - 'animalscience.unl.edu' => array('fernando-lab', 'anscgenomics', 'rprb-lab', 'ruminutrition-lab'), + '4h.unl.edu' => array('extension-4-h-horse'), + 'animalscience.unl.edu' => array('fernando-lab', 'anscgenomics', 'rprb-lab', 'ruminutrition-lab', 'pre-vet-program'), 'beef.unl.edu' => array('cattleproduction'), 'biochem.unl.edu' => array('barycki', 'bailey', 'becker', 'adamec', 'wilson', 'biochem-fatttlab', 'simpson'), 'bse.unl.edu' => array('p2guidelines'), 'edmedia.unl.edu' => array('techtraining'), 'food.unl.edu' => array('localfoods', 'allergy', 'fnh', 'preservation', 'fpc', 'safety', 'meatproducts', 'youth'), - 'ianrhome.unl.edu' => array('ianrinternational'), + 'ianrhome.unl.edu' => array('ianrinternational', 'liaison'), 'water.unl.edu' => array('crops', 'cropswater', 'drinkingwater', 'drought', 'wildlife', 'hydrology', 'lakes', 'landscapes', 'landscapewater', 'laweconomics', 'manure', 'propertydesign', 'research', 'sewage', 'students', 'watershed', 'wells', 'wetlands'), 'westcentral.unl.edu' => array('wcentomology', 'wcacreage'), + 'agecon.unl.edu' => array('policy'), ); /** @@ -1020,13 +1021,17 @@ class Unl_Migration_Tool $pathParts = explode('/', ltrim($urlParts['path'], '/')); $siteNameMap = array( - 'extension' => 'www.extension.unl.edu', - 'webster' => 'www.webster.unl.edu', + 'anisci' => 'animalscience.unl.edu', + 'extension' => 'www.extension.unl.edu', + 'ianr' => 'ianrhome.unl.edu', + 'webster' => 'www.webster.unl.edu', + 'vetscience' => 'vbms.unl.edu', ); if ( count($pathParts) >= 2 && $pathParts[0] == 'web' && !(in_array($urlParts['host'], array_keys($this->_liferaySubsites)) && in_array($pathParts[1], $this->_liferaySubsites[$urlParts['host']])) + && substr(parse_url($this->_baseUrl, PHP_URL_PATH), 0, 5) != '/web/' ) { // If the site name is "special" look it up in the map. Otherwise, just add .unl.edu diff --git a/sites/all/modules/unl_multisite/unl_multisite.module b/sites/all/modules/unl_multisite/unl_multisite.module index 58ca98ea026ed33f092cca660c5ec53fb5f96c4f..cebc40201e68e7bbef50808d3ab7312fbf3d20cb 100644 --- a/sites/all/modules/unl_multisite/unl_multisite.module +++ b/sites/all/modules/unl_multisite/unl_multisite.module @@ -46,6 +46,17 @@ function unl_multisite_block_view_my_sites() { return $block; } +/** + * Implements hook_help(). + */ +function unl_multisite_help($path, $arg) { + switch ($path) { + case 'admin/sites/unl/aliases': + case 'admin/sites/unl/%/aliases': + return '<p>Be careful when deleting aliases. While long, ugly paths may not appear to be needed they may still be in use - for instance in a ProxyPass setup on another server.</p>'; + } +} + /** * Implementation of hook_menu(). */ diff --git a/sites/all/modules/unl_multisite/unl_site_creation.php b/sites/all/modules/unl_multisite/unl_site_creation.php index 0518d9028143c938a76e5d46d9ea50a54d552b82..ffbcfbba1520cad47edfab901d3b4425faa943c9 100644 --- a/sites/all/modules/unl_multisite/unl_site_creation.php +++ b/sites/all/modules/unl_multisite/unl_site_creation.php @@ -663,6 +663,26 @@ function unl_site_alias_create_validate($form, &$form_state) { if (substr($form_state['values']['path'], 0, 1) == '/') { $form_state['values']['path'] = substr($form_state['values']['path'], 1); } + + // Check that the alias does not already exist. + $query = db_select('unl_sites_aliases', 'a'); + $query->fields('a', array('base_uri', 'path')); + + $db_or = db_or(); + $db_or->condition('a.path', $form_state['values']['path'], '='); + // Also consider legacy aliases that do not have a trailing slash. + $db_or->condition('a.path', substr($form_state['values']['path'], 0, -1), '='); + + $db_and = db_and(); + $db_and->condition('a.base_uri', $form_state['values']['base_uri'], '='); + $db_and->condition($db_or); + + $query->condition($db_and); + $result = $query->execute()->fetchAssoc(); + + if ($result) { + form_set_error('alias_path', t('Site alias already exists.')); + } } /** @@ -787,16 +807,16 @@ function unl_page_alias_create($form, &$form_state) { ); $form['root']['from_uri'] = array( '#type' => 'textfield', - '#title' => t('From URL'), - '#description' => t('The URL that users will visit.'), - '#default_value' => url('from/url', array('https' => FALSE)), + '#title' => t('From URI'), + '#description' => t('The URI that users will visit.'), + '#default_value' => url('from/uri', array('https' => FALSE)), '#required' => TRUE, ); $form['root']['to_uri'] = array( '#type' => 'textfield', - '#title' => t('To URL'), - '#description' => t('The URL users will be redirected to.'), - '#default_value' => url('to/url', array('https' => FALSE)), + '#title' => t('To URI'), + '#description' => t('The URI users will be redirected to.'), + '#default_value' => url('to/uri', array('https' => FALSE)), '#required' => TRUE, ); $form['root']['submit'] = array( @@ -820,12 +840,23 @@ function unl_page_alias_create_validate($form, &$form_state) { if (parse_url($from, PHP_URL_HOST) == parse_url($to, PHP_URL_HOST) && parse_url($from, PHP_URL_PATH) == parse_url($to, PHP_URL_PATH) && parse_url($from, PHP_URL_QUERY) == parse_url($to, PHP_URL_QUERY)) { - form_set_error('to_uri', 'From URL cannot equal To URL.'); + form_set_error('to_uri', 'From URI cannot equal To URI.'); } if (parse_url($from, PHP_URL_HOST) == parse_url($root, PHP_URL_HOST) && parse_url($from, PHP_URL_PATH) == parse_url($root, PHP_URL_PATH) && parse_url($from, PHP_URL_QUERY) == parse_url($root, PHP_URL_QUERY)) { - form_set_error('from_uri', 'From URL cannot be the root of the default site.'); + form_set_error('from_uri', 'From URI cannot be the root of the default site.'); + } + + // Check that the alias from_uri does not already exist. + $query = db_select('unl_page_aliases', 'a'); + $query->fields('a', array('from_uri', 'to_uri')); + + $query->condition('a.from_uri', $form_state['values']['from_uri'], '='); + $result = $query->execute()->fetchAssoc(); + + if ($result) { + form_set_error('alias_path', t('Page alias From URI already exists.')); } } @@ -856,7 +887,7 @@ function unl_page_alias_list($form, &$form_state) { 'data' => t('Status'), 'field' => 'a.installed', ), - 'remove' => t('Remove (can not undo!)'), + 'remove' => t('Remove'), ); $query = db_select('unl_page_aliases', 'a') @@ -1144,7 +1175,7 @@ function unl_get_site_user_map($search_by, $username_or_role, $list_empty_sites ); } catch (Exception $e) { // Either the site has no settings.php or the db_prefix is wrong. - drupal_set_message('Error querying database for site ' . $site->uri, 'warning'); + watchdog('unl_multisite', 'Error querying database for site %uri', array('%uri' => $site->uri), WATCHDOG_WARNING); } } diff --git a/sites/all/modules/unl_varnish/unl_varnish.module b/sites/all/modules/unl_varnish/unl_varnish.module index 36dcb1e9dcc40cb38bf22d3e613d1cf71b7f86da..d58938ac375eed1f2943d804dea067fcd3084d4e 100644 --- a/sites/all/modules/unl_varnish/unl_varnish.module +++ b/sites/all/modules/unl_varnish/unl_varnish.module @@ -60,7 +60,7 @@ function unl_varnish_purge_submit($form, &$form_state) { */ function unl_varnish_purge_all_sites_submit($form, &$form_state) { $path = $form_state['values']['varnish_path']; - _varnish_terminal_run("purge.url $path"); + varnish_purge('.*', $path); drupal_set_message("Varnish purged paths matching $path.", 'status'); } @@ -150,6 +150,6 @@ function unl_varnish_purge_all_pages() { $host = parse_url($alias, PHP_URL_HOST); $path = parse_url($alias, PHP_URL_PATH); - _varnish_terminal_run("purge req.http.host ~ $host && req.url ~ ^$path"); + varnish_purge($host, $path); } } diff --git a/sites/all/modules/upload_replace/upload_replace.info b/sites/all/modules/upload_replace/upload_replace.info index e14ce8e8e0916b5f78364b4cd9df6c2ed41bf13e..a295b84ba7decbc80ec55b81e4b70c22564f4196 100644 --- a/sites/all/modules/upload_replace/upload_replace.info +++ b/sites/all/modules/upload_replace/upload_replace.info @@ -2,7 +2,9 @@ name = Upload replace description = Make the most recent version of a file always retain its original filename, older file with the same name are renamed by drupal standards "_0, _1, etc" core = 7.x files[] = upload_replace.module -; patched with http://drupal.org/files/upload_replace_error-1115484-13.patch -version = "7.x-1.0-beta1-patched" +; Information added by drupal.org packaging script on 2011-12-15 +version = "7.x-1.0-beta1" core = "7.x" project = "upload_replace" +datestamp = "1323909946" + diff --git a/sites/all/modules/varnish/varnish.admin.inc b/sites/all/modules/varnish/varnish.admin.inc index 6ab28cff4fcb37bd6a527b2559c5dd150bdcf050..66a897b28314a0e890afd1a2aea56ee2e60d7363 100644 --- a/sites/all/modules/varnish/varnish.admin.inc +++ b/sites/all/modules/varnish/varnish.admin.inc @@ -84,6 +84,18 @@ function varnish_admin_settings_form() { $form['varnish_cache_clear']['#description'] .= ' ' . t('Installing the !link will enable "Selective" clearing.', array('!link' => '<a href="http://drupal.org/project/expire" target="_blank">' . t('Expire module') . '</a>')); } + // Allow users to select Varnish ban type to use. + $form['varnish_bantype'] = array( + '#type' => 'select', + '#title' => t('Varnish ban type'), + '#default_value' => variable_get('varnish_bantype', VARNISH_DEFAULT_BANTYPE), + '#description' => t('Select the type of varnish ban you wish to use. Ban lurker support requires you to add beresp.http.x-url and beresp.http.x-host entries to the response in vcl_fetch.'), + '#options' => array( + VARNISH_BANTYPE_NORMAL => 'Normal', + VARNISH_BANTYPE_BANLURKER => 'Ban Lurker', + ), + ); + // Check status $form['varnish_stats'] = array( '#type' => 'item', diff --git a/sites/all/modules/varnish/varnish.cache.inc b/sites/all/modules/varnish/varnish.cache.inc index 5d34cb22659dc724a7235c4cc25d7906e030c4d6..412506d77fb6a50a2bdfd0809d7a264115335d7f 100644 --- a/sites/all/modules/varnish/varnish.cache.inc +++ b/sites/all/modules/varnish/varnish.cache.inc @@ -36,7 +36,7 @@ class VarnishCache implements DrupalCacheInterface { global $user; // Check that we really want to do a cache flush. if (!module_exists('varnish') || - (variable_get('varnish_flush_cron', 0) && lock_may_be_available('cron'))) { + (variable_get('varnish_flush_cron', 0) && lock_may_be_available('cron')) && !variable_get('varnish_cache_clear', 1)) { return; } if (empty($cid) && variable_get('varnish_cache_clear', 1)) { @@ -73,7 +73,7 @@ class VarnishCache implements DrupalCacheInterface { $host = _varnish_get_host(); $base = base_path(); $purge = $cid . '(.*)'; - _varnish_terminal_run(array('purge req.http.host ~ ' . $host . ' && req.url ~ "^' . $base . $purge . '$"')); + varnish_purge($host, '^' . $base . $purge . '$'); } } elseif (is_array($cid)) { diff --git a/sites/all/modules/varnish/varnish.info b/sites/all/modules/varnish/varnish.info index 3418e279c0bff56a5f5b96762c2716947c543f92..0d3dfdd4924f8f6cf305cce08fc6c968fe29ac00 100644 --- a/sites/all/modules/varnish/varnish.info +++ b/sites/all/modules/varnish/varnish.info @@ -7,9 +7,9 @@ files[] = varnish.admin.inc files[] = varnish.cache.inc files[] = varnish.test -; Information added by drupal.org packaging script on 2012-01-31 -version = "7.x-1.0-beta1" +; Information added by drupal.org packaging script on 2013-02-20 +version = "7.x-1.0-beta2" core = "7.x" project = "varnish" -datestamp = "1328034066" +datestamp = "1361391598" diff --git a/sites/all/modules/varnish/varnish.module b/sites/all/modules/varnish/varnish.module index eab8f6f27b26e9d5ac414a505ea1f1fc7fd942a1..80e7af284795f498c2ce5743ea493c2efb207923 100644 --- a/sites/all/modules/varnish/varnish.module +++ b/sites/all/modules/varnish/varnish.module @@ -7,7 +7,9 @@ define('VARNISH_SELECTIVE_CLEAR', 2); // Requires Expire.module to be enabled. define('VARNISH_DEFAULT_TIMEOUT', 100); define('VARNISH_SERVER_STATUS_DOWN', 0); define('VARNISH_SERVER_STATUS_UP', 1); - +define('VARNISH_BANTYPE_NORMAL', 0); +define('VARNISH_BANTYPE_BANLURKER', 1); +define('VARNISH_DEFAULT_BANTYPE', VARNISH_BANTYPE_NORMAL); /** * @file @@ -73,6 +75,7 @@ function varnish_permission() { 'administer varnish' => array( 'title' => t('Administer Varnish'), 'description' => t('Perform administration tasks for varnish.'), + 'restrict access' => TRUE, ), ); } @@ -142,7 +145,18 @@ function varnish_purge($host, $pattern) { // need to use ban instead of purge. $version = floatval(variable_get('varnish_version', 2.1)); $command = $version >= 3 ? "ban" : "purge"; - _varnish_terminal_run(array("$command req.http.host ~ $host && req.url ~ \"$pattern\"")); + $bantype = variable_get('varnish_bantype', VARNISH_DEFAULT_BANTYPE); + switch ($bantype) { + case VARNISH_BANTYPE_NORMAL: + _varnish_terminal_run(array("$command req.http.host ~ $host && req.url ~ \"$pattern\"")); + break; + case VARNISH_BANTYPE_BANLURKER: + _varnish_terminal_run(array("$command obj.http.x-host ~ $host && obj.http.x-url ~ \"$pattern\"")); + break; + default: + // We really should NEVER get here. Log WATCHDOG_ERROR. I can only see this happening if a user switches between different versions of the module where we remove a ban type. + watchdog('varnish', 'Varnish ban type is out of range.', array(), WATCHDOG_ERROR); + } } /** @@ -177,7 +191,7 @@ function theme_varnish_status($status) { foreach ($status as $terminal => $state) { list($server, $port) = explode(':', $terminal); if ($state == VARNISH_SERVER_STATUS_UP) { - $icon = theme('image', array('path' => 'misc/watchdog-ok.png', 'alt' => "Server OK: {$server}:{$port}", "{$server}:{$port}")); + $icon = theme('image', array('path' => 'misc/watchdog-ok.png', 'alt' => t("Server OK: @server:@port", array('@server' => $server, '@port' => $port)), 'title' => "{$server}:{$port}")); $version = floatval(variable_get('varnish_version', 2.1)); if ($version < 3) { $items[] = t('!status_icon Varnish running. Observe more detailed statistics !link.', @@ -188,8 +202,8 @@ function theme_varnish_status($status) { } } else { - $icon = theme('image', array('path' => 'misc/watchdog-error.png', 'alt' => 'Server down: {$server}:{$port}', "{$server}:{$port}")); - $items[] = t('!status_icon The Varnish control terminal is not responding at %server on port %port.', array('!status_icon' => $icon, '%server' => $server, '%port' => $port)); + $icon = theme('image', array('path' => 'misc/watchdog-error.png', 'alt' => t("Server down: @server:@port", array('@server' => $server, '@port' => $port)), 'title' => "{$server}:{$port}")); + $items[] = t('!status_icon The Varnish control terminal is not responding at @server on port @port.', array('!status_icon' => $icon, '@server' => $server, '@port' => $port)); } } return theme('item_list', array('items' => $items)); @@ -230,9 +244,9 @@ function _varnish_terminal_run($commands) { socket_set_option($client, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $seconds, 'usec' => $microseconds)); socket_set_option($client, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $seconds, 'usec' => $microseconds)); if (@!socket_connect($client, $server, $port)) { - watchdog('varnish', 'Unable to connect to server socket !server:!port: %error', array( - '!server' => $server, - '!port' => $port, + watchdog('varnish', 'Unable to connect to server socket @server:@port: %error', array( + '@server' => $server, + '@port' => $port, '%error' => socket_strerror(socket_last_error($client)) ), WATCHDOG_ERROR); $ret[$terminal] = FALSE; @@ -269,7 +283,7 @@ function _varnish_execute_command($client, $command) { $result = socket_write($client, "$command\n"); $status = _varnish_read_socket($client); if ($status['code'] != 200) { - watchdog('varnish', 'Recieved status code !code running %command. Full response text: !error', array('!code' => $status['code'], '%command' => $command, '!error' => $status['msg']), WATCHDOG_ERROR); + watchdog('varnish', 'Recieved status code @code running %command. Full response text: @error', array('@code' => $status['code'], '%command' => $command, '@error' => $status['msg']), WATCHDOG_ERROR); return FALSE; } else { @@ -297,7 +311,7 @@ function _varnish_read_socket($client, $retry = 2) { return _varnish_read_socket($client, $retry-1); } else { - watchdog('varnish', 'Socket error: !error', array('!error' => socket_strerror($error)), WATCHDOG_ERROR); + watchdog('varnish', 'Socket error: @error', array('@error' => socket_strerror($error)), WATCHDOG_ERROR); return array( 'code' => $error, 'msg' => socket_strerror($error), diff --git a/sites/all/modules/views/css/views-admin.ctools.css b/sites/all/modules/views/css/views-admin.ctools.css index 52c89522a18b24a7355083ea5ec7a8cf6f89dcf2..b1f0e299e882790f41f420ee3033fc66924f2769 100644 --- a/sites/all/modules/views/css/views-admin.ctools.css +++ b/sites/all/modules/views/css/views-admin.ctools.css @@ -8,11 +8,6 @@ padding-top: 2px; } -.ctools-button-processed, -.ctools-button-processed input { - text-transform: lowercase; -} - .ctools-button-processed:hover { border-color: #b8b8b8; } diff --git a/sites/all/modules/views/handlers/views_handler_area_text.inc b/sites/all/modules/views/handlers/views_handler_area_text.inc index d7727866dd1ec69da15d592394bba095769e0f90..edb282f3204bb88e3c1560810311d3ccd262784a 100644 --- a/sites/all/modules/views/handlers/views_handler_area_text.inc +++ b/sites/all/modules/views/handlers/views_handler_area_text.inc @@ -56,7 +56,7 @@ class views_handler_area_text extends views_handler_area { if (!empty($options[$type])) { $items = array(); foreach ($options[$type] as $key => $value) { - $items[] = $key . ' == ' . $value; + $items[] = $key . ' == ' . check_plain($value); } $output .= theme('item_list', array( diff --git a/sites/all/modules/views/handlers/views_handler_field.inc b/sites/all/modules/views/handlers/views_handler_field.inc index c687575725c9c54f998074382b1d573d0ab33560..65210d9f6d9b463807fea93e8324ee1d587e2713 100644 --- a/sites/all/modules/views/handlers/views_handler_field.inc +++ b/sites/all/modules/views/handlers/views_handler_field.inc @@ -410,7 +410,7 @@ class views_handler_field extends views_handler { 'link_class' => array('default' => ''), 'prefix' => array('default' => '', 'translatable' => TRUE), 'suffix' => array('default' => '', 'translatable' => TRUE), - 'target' => array('default' => '', 'translatable' => TRUE), + 'target' => array('default' => ''), 'nl2br' => array('default' => FALSE, 'bool' => TRUE), 'max_length' => array('default' => ''), 'word_boundary' => array('default' => TRUE, 'bool' => TRUE), @@ -814,7 +814,7 @@ If you would like to have the characters \'[\' and \']\' please use the html ent if (!empty($options[$type])) { $items = array(); foreach ($options[$type] as $key => $value) { - $items[] = $key . ' == ' . $value; + $items[] = $key . ' == ' . check_plain($value); } $output .= theme('item_list', array( @@ -965,7 +965,7 @@ If you would like to have the characters \'[\' and \']\' please use the html ent '#type' => 'textarea', '#title' => t('No results text'), '#default_value' => $this->options['empty'], - '#description' => t('Provide text to display if this field returns no results. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section below.'), + '#description' => t('Provide text to display if this field contains an empty result. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section below.'), '#fieldset' => 'empty_field_behavior', ); diff --git a/sites/all/modules/views/handlers/views_handler_field_boolean.inc b/sites/all/modules/views/handlers/views_handler_field_boolean.inc index 13fff078738ee94f4b34682976ea6af94332499a..8acfb32cc0c26fb86792710944280395996925c6 100644 --- a/sites/all/modules/views/handlers/views_handler_field_boolean.inc +++ b/sites/all/modules/views/handlers/views_handler_field_boolean.inc @@ -25,6 +25,8 @@ class views_handler_field_boolean extends views_handler_field { function option_definition() { $options = parent::option_definition(); $options['type'] = array('default' => 'yes-no'); + $options['type_custom_true'] = array('default' => '', 'translatable' => TRUE); + $options['type_custom_false'] = array('default' => '', 'translatable' => TRUE); $options['not'] = array('definition bool' => 'reverse'); return $options; @@ -38,10 +40,12 @@ class views_handler_field_boolean extends views_handler_field { 'true-false' => array(t('True'), t('False')), 'on-off' => array(t('On'), t('Off')), 'enabled-disabled' => array(t('Enabled'), t('Disabled')), + 'boolean' => array(1, 0), 'unicode-yes-no' => array('✔', '✖'), ); $output_formats = isset($this->definition['output formats']) ? $this->definition['output formats'] : array(); - $this->formats = array_merge($default_formats, $output_formats); + $custom_format = array('custom' => array(t('Custom'))); + $this->formats = array_merge($default_formats, $output_formats, $custom_format); } function options_form(&$form, &$form_state) { @@ -55,6 +59,29 @@ class views_handler_field_boolean extends views_handler_field { '#options' => $options, '#default_value' => $this->options['type'], ); + + $form['type_custom_true'] = array( + '#type' => 'textfield', + '#title' => t('Custom output for TRUE'), + '#default_value' => $this->options['type_custom_true'], + '#states' => array( + 'visible' => array( + 'select[name="options[type]"]' => array('value' => 'custom'), + ), + ), + ); + + $form['type_custom_false'] = array( + '#type' => 'textfield', + '#title' => t('Custom output for FALSE'), + '#default_value' => $this->options['type_custom_false'], + '#states' => array( + 'visible' => array( + 'select[name="options[type]"]' => array('value' => 'custom'), + ), + ), + ); + $form['not'] = array( '#type' => 'checkbox', '#title' => t('Reverse'), @@ -70,7 +97,10 @@ class views_handler_field_boolean extends views_handler_field { $value = !$value; } - if (isset($this->formats[$this->options['type']])) { + if ($this->options['type'] == 'custom') { + return $value ? filter_xss_admin($this->options['type_custom_true']) : filter_xss_admin($this->options['type_custom_false']); + } + else if (isset($this->formats[$this->options['type']])) { return $value ? $this->formats[$this->options['type']][0] : $this->formats[$this->options['type']][1]; } else { diff --git a/sites/all/modules/views/handlers/views_handler_filter_combine.inc b/sites/all/modules/views/handlers/views_handler_filter_combine.inc index c9def53494391fd100dfb5dd4e9eb0efb22ae9e7..915924b8f3148c4743b8ab25a62350c9b74e803a 100644 --- a/sites/all/modules/views/handlers/views_handler_filter_combine.inc +++ b/sites/all/modules/views/handlers/views_handler_filter_combine.inc @@ -14,7 +14,7 @@ class views_handler_filter_combine extends views_handler_filter_string { /** * @var views_plugin_query_default */ - var $query; + public $query; function option_definition() { $options = parent::option_definition(); @@ -27,7 +27,7 @@ class views_handler_filter_combine extends views_handler_filter_string { parent::options_form($form, $form_state); $this->view->init_style(); - // Allow to choose all fields as possible + // Allow to choose all fields as possible. if ($this->view->style_plugin->uses_fields()) { $options = array(); foreach ($this->view->display_handler->get_handlers('field') as $name => $field) { @@ -55,7 +55,8 @@ class views_handler_filter_combine extends views_handler_filter_string { // Only add the fields if they have a proper field and table alias. foreach ($this->options['fields'] as $id) { $field = $this->view->field[$id]; - // Always add the table of the selected fields to be sure a table alias exists. + // Always add the table of the selected fields to be sure a table alias + // exists. $field->ensure_my_table(); if (!empty($field->field_alias) && !empty($field->field_alias)) { $fields[] = "$field->table_alias.$field->real_field"; @@ -63,14 +64,14 @@ class views_handler_filter_combine extends views_handler_filter_string { } if ($fields) { $count = count($fields); - $seperated_fields = array(); + $separated_fields = array(); foreach ($fields as $key => $field) { - $seperated_fields[] = $field; - if ($key < $count-1) { - $seperated_fields[] = "' '"; + $separated_fields[] = $field; + if ($key < $count - 1) { + $separated_fields[] = "' '"; } } - $expression = implode(', ', $seperated_fields); + $expression = implode(', ', $separated_fields); $expression = "CONCAT_WS(' ', $expression)"; $info = $this->operators(); @@ -82,7 +83,6 @@ class views_handler_filter_combine extends views_handler_filter_string { // By default things like op_equal uses add_where, that doesn't support // complex expressions, so override all operators. - function op_equal($field) { $placeholder = $this->placeholder(); $operator = $this->operator(); @@ -94,6 +94,39 @@ class views_handler_filter_combine extends views_handler_filter_string { $this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => '%' . db_like($this->value) . '%')); } + function op_word($field) { + $where = $this->operator == 'word' ? db_or() : db_and(); + + // Don't filter on empty strings. + if (empty($this->value)) { + return; + } + + preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $phrase = FALSE; + // Strip off phrase quotes. + if ($match[2]{0} == '"') { + $match[2] = substr($match[2], 1, -1); + $phrase = TRUE; + } + $words = trim($match[2], ',?!();:-'); + $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY); + $placeholder = $this->placeholder(); + foreach ($words as $word) { + $where->where($field . " LIKE $placeholder", array($placeholder => '%' . db_like(trim($word, " ,!?")) . '%')); + } + } + + if (!$where) { + return; + } + + // Previously this was a call_user_func_array() but that's unnecessary + // as views will unpack an array that is a single arg. + $this->query->add_where($this->options['group'], $where); + } + function op_starts($field) { $placeholder = $this->placeholder(); $this->query->add_where_expression($this->options['group'], "$field LIKE $placeholder", array($placeholder => db_like($this->value) . '%')); diff --git a/sites/all/modules/views/handlers/views_handler_filter_numeric.inc b/sites/all/modules/views/handlers/views_handler_filter_numeric.inc index 982abd876febde67f47bd35b58b49e75abbabd58..03384f685ad424209794add6c4985db986f15c0a 100644 --- a/sites/all/modules/views/handlers/views_handler_filter_numeric.inc +++ b/sites/all/modules/views/handlers/views_handler_filter_numeric.inc @@ -258,7 +258,7 @@ class views_handler_filter_numeric extends views_handler_filter { } function op_regex($field) { - $this->query->add_where($this->options['group'], $field, $this->value, 'RLIKE'); + $this->query->add_where($this->options['group'], $field, $this->value['value'], 'RLIKE'); } function admin_summary() { diff --git a/sites/all/modules/views/includes/admin.inc b/sites/all/modules/views/includes/admin.inc index ff4a99fd6f71b91affde99fba395f615ef195ef3..160a61a14265583fd4399eedb7c84ba0a5face78 100644 --- a/sites/all/modules/views/includes/admin.inc +++ b/sites/all/modules/views/includes/admin.inc @@ -91,10 +91,10 @@ function views_ui_check_advanced_help() { $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'", 0, 1) ->fetchField(); if ($filename && file_exists($filename)) { - drupal_set_message(t('If you <a href="@modules">enable the advanced help module</a>, Views will provide more and better help. <a href="@hide">Hide this message.</a>', array('@modules' => url('admin/modules'),'@hide' => url('admin/structure/views/settings')))); + drupal_set_message(t('If you <a href="@modules">enable the advanced help module</a>, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('@modules' => url('admin/modules'),'@hide' => url('admin/structure/views/settings')))); } else { - drupal_set_message(t('If you install the advanced help module from !href, Views will provide more and better help. <a href="@hide">Hide this message.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/structure/views/settings')))); + drupal_set_message(t('If you install the advanced help module from !href, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/structure/views/settings')))); } } } @@ -835,10 +835,10 @@ function theme_views_ui_view_info($variables) { } $output = ''; - $output .= '<div class="views-ui-view-title">' . $title . "</div>\n"; + $output .= '<div class="views-ui-view-title">' . check_plain($title) . "</div>\n"; $output .= '<div class="views-ui-view-displays">' . $displays . "</div>\n"; $output .= '<div class="views-ui-view-storage">' . $type . "</div>\n"; - $output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . $variables['base']. "</div>\n"; + $output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . check_plain($variables['base']). "</div>\n"; return $output; } @@ -3038,7 +3038,7 @@ function views_ui_reorder_displays_form($form, &$form_state) { foreach ($view->display as $display) { $form[$display->id] = array( - 'title' => array('#markup' => $display->display_title), + 'title' => array('#markup' => check_plain($display->display_title)), 'weight' => array( '#type' => 'weight', '#value' => $display->position, @@ -4122,8 +4122,8 @@ function views_ui_add_item_form($form, &$form_state) { $zebra_class = ($zebra % 2) ? 'odd' : 'even'; $form['options']['name'][$key] = array( '#type' => 'checkbox', - '#title' => t('!group: !field', array('!group' => $option['group'], '!field' => $option['title'])), - '#description' => $option['help'], + '#title' => t('!group: !field', array('!group' => check_plain($option['group']), '!field' => check_plain($option['title']))), + '#description' => filter_xss_admin($option['help']), '#return_value' => $key, '#prefix' => "<div class='$zebra_class filterable-option'>", '#suffix' => '</div>', @@ -5047,7 +5047,7 @@ function views_ui_autocomplete_tag($string = '') { $views = views_get_all_views(); foreach ($views as $view) { if (!empty($view->tag) && strpos($view->tag, $string) === 0) { - $matches[$view->tag] = $view->tag; + $matches[$view->tag] = check_plain($view->tag); if (count($matches) >= 10) { break; } @@ -5267,7 +5267,7 @@ function theme_views_ui_style_plugin_table($variables) { $rows = array(); foreach (element_children($form['columns']) as $id) { $row = array(); - $row[] = drupal_render($form['info'][$id]['name']); + $row[] = check_plain(drupal_render($form['info'][$id]['name'])); $row[] = drupal_render($form['columns'][$id]); $row[] = drupal_render($form['info'][$id]['align']); $row[] = drupal_render($form['info'][$id]['separator']); diff --git a/sites/all/modules/views/includes/base.inc b/sites/all/modules/views/includes/base.inc index 0e2a4078fe5bfd16657c953ecb95a74b8ca37cd7..217bbec4cb8b477d554b3656cd141e8952e1290d 100644 --- a/sites/all/modules/views/includes/base.inc +++ b/sites/all/modules/views/includes/base.inc @@ -346,8 +346,8 @@ class views_object { $value = $options; // Build source data and add to the array $format = NULL; - if (isset($definition['format_key']) && isset($options[$definition['format_key']])) { - $format = $options[$definition['format_key']]; + if (isset($definition['format_key']) && isset($storage[$definition['format_key']])) { + $format = $storage[$definition['format_key']]; } $translatable[] = array( 'value' => $value, diff --git a/sites/all/modules/views/includes/cache.inc b/sites/all/modules/views/includes/cache.inc index c655c21cf8dcb64074a3326b8ccd1fbb77a425f4..59c1733d42f91e46d26aa86a893bfb32b6907f8b 100644 --- a/sites/all/modules/views/includes/cache.inc +++ b/sites/all/modules/views/includes/cache.inc @@ -15,53 +15,81 @@ function _views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) { $cache = &drupal_static(__FUNCTION__ . '_cache'); $recursion_protection = &drupal_static(__FUNCTION__ . '_recursion_protected'); - if (!isset($cache) || $reset) { - $start = microtime(TRUE); - // NOTE: This happens whether we retrieve them from cache or otherwise. - - $data = views_cache_get('views_data', TRUE); - if (!empty($data->data)) { - $cache = $data->data; + $fully_loaded = &drupal_static(__FUNCTION__ . '_fully_loaded'); + if ($reset) { + $cache = NULL; + $fully_loaded = FALSE; + } + if ($table) { + if (!isset($cache[$table])) { + $cid = 'views_data:' . $table; + $data = views_cache_get($cid, TRUE); + if (!empty($data->data)) { + $cache[$table] = $data->data; + } + else { + if (!$fully_loaded) { + // No cache entry, rebuild. + $cache = _views_fetch_data_build(); + $fully_loaded = TRUE; + } + } } - - if (empty($cache)) { - views_include_handlers(); - $cache = module_invoke_all('views_data'); - foreach (module_implements('views_data_alter') as $module) { - $function = $module . '_views_data_alter'; - $function($cache); + if (isset($cache[$table])) { + if (isset($cache[$table]['moved to']) && $move) { + $moved_table = $cache[$table]['moved to']; + if (!empty($recursion_protection[$table])) { + // recursion detected! + return NULL; + } + $recursion_protection[$table] = TRUE; + $data = _views_fetch_data($moved_table); + $recursion_protection = array(); + return $data; } - _views_data_process_entity_types($cache); - - views_cache_set('views_data', $cache, TRUE); + return $cache[$table]; } } + else { + if (!$fully_loaded) { + $data = views_cache_get('views_data', TRUE); + if (!empty($data->data)) { + $cache = $data->data; + } - if (!$table) { - return $cache; - } - if (isset($cache[$table])) { - // Support old views_data entries conversion. - if (isset($cache[$table]['moved to']) && $move) { - $moved_table = $cache[$table]['moved to']; - if (!empty($recursion_protection[$table])) { - // recursion detected! - return NULL; + if (empty($cache)) { + $cache = _views_fetch_data_build(); } - $recursion_protection[$table] = TRUE; - $data = _views_fetch_data($moved_table); - $recursion_protection = array(); - return $data; - } - else { - return $cache[$table]; + $fully_loaded = TRUE; } + return $cache; } - // Return an empty array if there is no match. return array(); } +/** + * Build and set the views data cache if empty. + */ +function _views_fetch_data_build() { + views_include_handlers(); + $cache = module_invoke_all('views_data'); + foreach (module_implements('views_data_alter') as $module) { + $function = $module . '_views_data_alter'; + $function($cache); + } + _views_data_process_entity_types($cache); + + // Keep a record with all data. + views_cache_set('views_data', $cache, TRUE); + // Save data in seperate cache entries. + foreach ($cache as $key => $data) { + $cid = 'views_data:' . $key; + views_cache_set($cid, $data, TRUE); + } + return $cache; +} + /** * Links tables having an 'entity type' specified to the respective generic entity-type tables. */ diff --git a/sites/all/modules/views/includes/handlers.inc b/sites/all/modules/views/includes/handlers.inc index a79aa5c1d022054522a14c9bb5ec23d365b30229..2a33d5d54ddefad750354d64485637097e5c8cf7 100644 --- a/sites/all/modules/views/includes/handlers.inc +++ b/sites/all/modules/views/includes/handlers.inc @@ -751,6 +751,13 @@ class views_handler extends views_object { * */ class views_many_to_one_helper { + /** + * Contains possible existing placeholders used by the query. + * + * @var array + */ + public $placeholders = array(); + function views_many_to_one_helper(&$handler) { $this->handler = &$handler; } @@ -1423,7 +1430,7 @@ function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set * // PHP 4 doesn't call constructors of the base class automatically from a * // constructor of a derived class. It is your responsibility to propagate * // the call to constructors upstream where appropriate. - * function construct($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') { + * function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') { * parent::construct($table, $left_table, $left_field, $field, $extra, $type); * } * diff --git a/sites/all/modules/views/includes/view.inc b/sites/all/modules/views/includes/view.inc index 90dd3a22f64f7f057466dded02cc7e298ff6195c..d8c0c1f5e92587236e78eb50c9e66a95c93c49d5 100644 --- a/sites/all/modules/views/includes/view.inc +++ b/sites/all/modules/views/includes/view.inc @@ -960,8 +960,16 @@ class view extends views_db_object { if ($this->display_handler->uses_exposed()) { $exposed_form = $this->display_handler->get_plugin('exposed_form'); + // (1) Record the errors before rendering the exposed form widgets. + $errors_before = form_set_error(); $this->exposed_widgets = $exposed_form->render_exposed_form(); - if (form_set_error() || !empty($this->build_info['abort'])) { + // (2) Record the errors after rendering the exposed form widgets. + $errors_after = form_set_error(); + // Find out if the validation of any of the elements in the exposed form + // has failed by comparing (1) and (2) above. Don't mess with the view + // otherwise. + $exposed_errors = count($errors_after) > count($errors_before); + if ($exposed_errors || !empty($this->build_info['abort'])) { $this->built = TRUE; // Don't execute the query, but rendering will still be executed to display the empty text. $this->executed = TRUE; @@ -1258,6 +1266,7 @@ class view extends views_db_object { $cache->cache_set('output'); } } + $this->render_time = microtime(TRUE) - $start; $exposed_form->post_render($this->display_handler->output); @@ -1286,7 +1295,6 @@ class view extends views_db_object { if (!empty($this->live_preview) && variable_get('views_show_additional_queries', FALSE)) { $this->end_query_capture(); } - $this->render_time = microtime(TRUE) - $start; return $this->display_handler->output; } diff --git a/sites/all/modules/views/js/ajax_view.js b/sites/all/modules/views/js/ajax_view.js index 2a4012f8e8252246d525cfa9c55f6ef3a514267b..e3bc821345ae913d45af68aa1921890d31971ada 100644 --- a/sites/all/modules/views/js/ajax_view.js +++ b/sites/all/modules/views/js/ajax_view.js @@ -69,7 +69,7 @@ Drupal.views.ajaxView = function(settings) { }; Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() { - var button = $('input[type=submit], input[type=image]', this.$exposed_form); + var button = $('input[type=submit], button[type=submit], input[type=image]', this.$exposed_form); button = button[0]; this.exposedFormAjax = new Drupal.ajax($(button).attr('id'), button, this.element_settings); diff --git a/sites/all/modules/views/js/views-admin.js b/sites/all/modules/views/js/views-admin.js index 1eb389787532936d06ee2c04fead528b317fd7ad..2b4ccf339411fadd6298c91a39ed14f71c86f607 100644 --- a/sites/all/modules/views/js/views-admin.js +++ b/sites/all/modules/views/js/views-admin.js @@ -209,7 +209,7 @@ Drupal.viewsUi.addItemForm.prototype.handleCheck = function (event) { */ Drupal.viewsUi.addItemForm.prototype.refreshCheckedItems = function() { // Perhaps we should precache the text div, too. - this.$selected_div.find('.views-selected-options').html(this.checkedItems.join(', ')); + this.$selected_div.find('.views-selected-options').html(Drupal.checkPlain(this.checkedItems.join(', '))); Drupal.viewsUi.resizeModal('', true); } @@ -255,10 +255,11 @@ Drupal.behaviors.viewsUiRenderAddViewButton.attach = function (context, settings // away from the item. We use mouseleave instead of mouseout because // the user is going to trigger mouseout when she moves from the trigger // link to the sub menu items. - // We use the live binder because the open class on this item will be + // + // We use the 'li.add' selector because the open class on this item will be // toggled on and off and we want the handler to take effect in the cases // that the class is present, but not when it isn't. - $('li.add', $menu).live('mouseleave', function (event) { + $menu.delegate('li.add', 'mouseleave', function (event) { var $this = $(this); var $trigger = $this.children('a[href="#"]'); if ($this.children('.action-list').is(':visible')) { diff --git a/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc index d959b0424c40dd8bc5e94c9cff6246188e5cf883..4c7824ec783f05f137c5237cd06f66463a16f400 100644 --- a/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc +++ b/sites/all/modules/views/modules/aggregator/views_handler_argument_aggregator_iid.inc @@ -18,9 +18,9 @@ class views_handler_argument_aggregator_iid extends views_handler_argument_numer $titles = array(); $placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d')); - $result = db_select('aggregator_item') + $result = db_select('aggregator_item', 'ai') ->condition('iid', $this->value, 'IN') - ->fields(array('title')) + ->fields('ai', array('title')) ->execute(); foreach ($result as $term) { $titles[] = check_plain($term->title); diff --git a/sites/all/modules/views/modules/field.views.inc b/sites/all/modules/views/modules/field.views.inc index fe1968bb9763629aad44de9b1810ac98335acc5b..873153ea5ca7a8682f9eedd35e86920f714d077c 100644 --- a/sites/all/modules/views/modules/field.views.inc +++ b/sites/all/modules/views/modules/field.views.inc @@ -396,6 +396,84 @@ function field_views_field_default_views_data($field) { 'field_name' => $field['field_name'], ); } + + // Expose additional language column for translatable fields. + if (!empty($field['translatable'])) { + $title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field['field_name'])); + $title_short_language = t('@label:language', array('@label' => $label)); + + $data[$table]['language'] = array( + 'group' => $group, + 'title' => $title_language, + 'title short' => $title_short_language, + 'help' => t('Language - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), + ); + $data[$table]['language']['field'] = array( + 'handler' => 'views_handler_field_locale_language', + ); + $data[$table]['language']['argument'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_argument_locale_language', + 'additional fields' => $additional_fields, + 'empty field name' => t('<No value>'), + 'field_name' => $field['field_name'], + ); + $data[$table]['language']['filter'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_filter_locale_language', + 'additional fields' => $additional_fields, + 'field_name' => $field['field_name'], + 'allow empty' => TRUE, + ); + $data[$table]['language']['sort'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_sort', + 'additional fields' => $additional_fields, + 'field_name' => $field['field_name'], + ); + } + + // Expose additional language column for translatable fields. + if (!empty($field['translatable'])) { + $title_language = t('@label (!name:language)', array('@label' => $label, '!name' => $field['field_name'])); + $title_short_language = t('@label:language', array('@label' => $label)); + + $data[$table]['language'] = array( + 'group' => $group, + 'title' => $title_language, + 'title short' => $title_short_language, + 'help' => t('Language - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), + ); + $data[$table]['language']['field'] = array( + 'handler' => 'views_handler_field_locale_language', + ); + $data[$table]['language']['argument'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_argument_locale_language', + 'additional fields' => $additional_fields, + 'empty field name' => t('<No value>'), + 'field_name' => $field['field_name'], + ); + $data[$table]['language']['filter'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_filter_locale_language', + 'additional fields' => $additional_fields, + 'field_name' => $field['field_name'], + 'allow empty' => TRUE, + ); + $data[$table]['language']['sort'] = array( + 'field' => 'language', + 'table' => $table, + 'handler' => 'views_handler_sort', + 'additional fields' => $additional_fields, + 'field_name' => $field['field_name'], + ); + } } } diff --git a/sites/all/modules/views/modules/node.views.inc b/sites/all/modules/views/modules/node.views.inc index ebba3d3e177cb9fbcf8a3548ce11ea7db6363dbb..71a102373f221821516639c42e586aa4a6c183e1 100644 --- a/sites/all/modules/views/modules/node.views.inc +++ b/sites/all/modules/views/modules/node.views.inc @@ -439,7 +439,7 @@ function node_views_data() { $data['node_revision']['table']['default_relationship'] = array( 'node' => array( 'table' => 'node', - 'field' => 'vid', + 'field' => 'nid', ), ); @@ -456,24 +456,57 @@ function node_views_data() { ); // nid + $data['node_revision']['nid'] = array( + 'title' => t('Nid'), + // The help that appears on the UI. + 'help' => t('The revision NID of the content revision.'), + // Information for displaying the nid. + 'field' => array( + 'click sortable' => TRUE, + ), + // Information for accepting a nid as an argument. + 'argument' => array( + 'handler' => 'views_handler_argument_node_nid', + 'click sortable' => TRUE, + 'numeric' => TRUE, + ), + // Information for accepting a nid as a filter. + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + // Information for sorting on a nid. + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'base' => 'node', + 'base field' => 'nid', + 'title' => t('Content'), + 'label' => t('Get the actual content from a content revision.'), + ), + ); + + // vid $data['node_revision']['vid'] = array( 'title' => t('Vid'), - 'help' => t('The revision ID of the content revision.'), // The help that appears on the UI, - // Information for displaying the nid + // The help that appears on the UI. + 'help' => t('The revision ID of the content revision.'), + // Information for displaying the vid. 'field' => array( 'click sortable' => TRUE, ), - // Information for accepting a nid as an argument + // Information for accepting a vid as an argument. 'argument' => array( 'handler' => 'views_handler_argument_node_vid', 'click sortable' => TRUE, 'numeric' => TRUE, ), - // Information for accepting a nid as a filter + // Information for accepting a vid as a filter. 'filter' => array( 'handler' => 'views_handler_filter_numeric', ), - // Information for sorting on a nid. + // Information for sorting on a vid. 'sort' => array( 'handler' => 'views_handler_sort', ), diff --git a/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc b/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc index c04693a815528ad0ddc88ad436e92a8b342e3c54..d29b07087c450254001be3d1584dd5ab1d812925 100644 --- a/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc +++ b/sites/all/modules/views/modules/node/views_handler_field_node_revision.inc @@ -54,7 +54,10 @@ class views_handler_field_node_revision extends views_handler_field_node { $this->options['alter']['make_link'] = TRUE; $nid = $this->get_value($values, 'nid'); $vid = $this->get_value($values, 'vid'); - $this->options['alter']['path'] = "node/" . $nid . '/revisions/' . $vid . '/view'; + $this->options['alter']['path'] = 'node/' . $nid; + if ($nid != $vid) { + $this->options['alter']['path'] .= "/revisions/$vid/view"; + } if (module_exists('translation')) { $language = $this->get_value($values, 'language'); $languages = language_list(); diff --git a/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc b/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc index a9ee85cd83510ded47ae6cc2b937d81e50306249..a29b13f423213796a4fe2a7824a0e74fcb04621a 100644 --- a/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc +++ b/sites/all/modules/views/modules/node/views_handler_filter_node_access.inc @@ -21,7 +21,7 @@ class views_handler_filter_node_access extends views_handler_filter { * See _node_access_where_sql() for a non-views query based implementation. */ function query() { - if (!user_access('administer nodes')) { + if (!user_access('administer nodes') && module_implements('node_grants')) { $table = $this->ensure_my_table(); $grants = db_or(); foreach (node_access_grants('view') as $realm => $gids) { diff --git a/sites/all/modules/views/modules/search/views_handler_filter_search.inc b/sites/all/modules/views/modules/search/views_handler_filter_search.inc index 7430494e5cf58313d35c531db8390e884b82f4dc..16515a769b2e88a4e126065db399fe122c3c7b7f 100644 --- a/sites/all/modules/views/modules/search/views_handler_filter_search.inc +++ b/sites/all/modules/views/modules/search/views_handler_filter_search.inc @@ -29,10 +29,28 @@ class views_handler_filter_search extends views_handler_filter { $options = parent::option_definition(); $options['operator']['default'] = 'optional'; + $options['remove_score'] = array('default' => FALSE, 'bool' => TRUE); return $options; } + /** + * Overrides views_handler_filter::options_form(). + * + * Add an option to remove search scores from the query. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $form['remove_score'] = array( + '#type' => 'checkbox', + '#title' => t('Remove search score'), + '#description' => t('Check this box to remove the search score from the query. This can help reduce help reduce duplicate search results when using this filter.'), + '#default_value' => $this->options['remove_score'], + ); + } + + /** * Provide simple equality operator */ @@ -126,12 +144,14 @@ class views_handler_filter_search extends views_handler_filter { $search_condition = db_and(); - // Create a new join to relate the 'serach_total' table to our current 'search_index' table. - $join = new views_join; - $join->construct('search_total', $search_index, 'word', 'word'); - $search_total = $this->query->add_relationship('search_total', $join, $search_index); + if (!$this->options['remove_score']) { + // Create a new join to relate the 'serach_total' table to our current 'search_index' table. + $join = new views_join; + $join->construct('search_total', $search_index, 'word', 'word'); + $search_total = $this->query->add_relationship('search_total', $join, $search_index); - $this->search_score = $this->query->add_field('', "SUM($search_index.score * $search_total.count)", 'score', array('aggregate' => TRUE)); + $this->search_score = $this->query->add_field('', "SUM($search_index.score * $search_total.count)", 'score', array('aggregate' => TRUE)); + } if (empty($this->query->relationships[$this->relationship])) { $base_table = $this->query->base_table; diff --git a/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc index 75ab0f8efaecd3c57a9b3e22a4e510b667b2a9f0..2efb4a653750dfd3197ca107a8c36945619d42fe 100644 --- a/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc +++ b/sites/all/modules/views/modules/taxonomy/views_handler_field_term_link_edit.inc @@ -44,16 +44,19 @@ class views_handler_field_term_link_edit extends views_handler_field { } function render($values) { - // Mock a term object for taxonomy_term_edit_access(). Use machine name and - // vid to ensure compatibility with vid based and machine name based - // access checks. See http://drupal.org/node/995156 - $term = new stdClass(); - $term->vid = $values->{$this->aliases['vid']}; - $term->vocabulary_machine_name = $values->{$this->aliases['vocabulary_machine_name']}; - if (taxonomy_term_edit_access($term)) { - $text = !empty($this->options['text']) ? $this->options['text'] : t('edit'); - $tid = $this->get_value($values, 'tid'); - return l($text, 'taxonomy/term/'. $tid . '/edit', array('query' => drupal_get_destination())); + // Check there is an actual value, as on a relationship there may not be. + if ($tid = $this->get_value($values, 'tid')) { + // Mock a term object for taxonomy_term_edit_access(). Use machine name and + // vid to ensure compatibility with vid based and machine name based + // access checks. See http://drupal.org/node/995156 + $term = new stdClass(); + $term->vid = $values->{$this->aliases['vid']}; + $term->vocabulary_machine_name = $values->{$this->aliases['vocabulary_machine_name']}; + if (taxonomy_term_edit_access($term)) { + $text = !empty($this->options['text']) ? $this->options['text'] : t('edit'); + $tid = $this->get_value($values, 'tid'); + return l($text, 'taxonomy/term/'. $tid . '/edit', array('query' => drupal_get_destination())); + } } } } diff --git a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc index 3a8819984df1b94bf286729a062e27391ad1cc10..435db0ddff84be02ffc55ddc6277a67cd5d369f7 100644 --- a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc +++ b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc @@ -96,15 +96,15 @@ class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument $query = db_select('taxonomy_term_data', 'td'); $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid'); $query->fields('td'); - $query->fields('tv', array('machine_name')); $query->condition('td.tid', $argument); $query->addTag('term_access'); $term = $query->execute()->fetchObject(); if (!$term) { return FALSE; } - $this->argument->validated_title = check_plain($term->name); - return empty($vocabularies) || !empty($vocabularies[$term->machine_name]); + $term = taxonomy_term_load($term->tid); + $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term)); + return empty($vocabularies) || !empty($vocabularies[$term->vocabulary_machine_name]); case 'tids': // An empty argument is not a term so doesn't pass. @@ -151,8 +151,8 @@ class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument $validated_cache[$term->tid] = FALSE; return FALSE; } - - $titles[] = $validated_cache[$term->tid] = check_plain($term->name); + $term = taxonomy_term_load($term->tid); + $titles[] = $validated_cache[$term->tid] = check_plain(entity_label('taxonomy_term', $term)); unset($test[$term->tid]); } } @@ -185,7 +185,8 @@ class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument if ($type == 'convert') { $this->argument->argument = $term->tid; } - $this->argument->validated_title = check_plain($term->name); + $term = taxonomy_term_load($term->tid); + $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term)); return TRUE; } return FALSE; diff --git a/sites/all/modules/views/modules/tracker.views.inc b/sites/all/modules/views/modules/tracker.views.inc new file mode 100644 index 0000000000000000000000000000000000000000..ee14589d4c64ef593f7b3d7211a5d9e5f90d878b --- /dev/null +++ b/sites/all/modules/views/modules/tracker.views.inc @@ -0,0 +1,183 @@ +<?php + +/** + * @file + * Provide views data and handlers for tracker.module. + * + * @ingroup views_module_handlers + */ +/** + * Implementation of hook_views_data(). + */ +function tracker_views_data() { + $data = array(); + + $data['tracker_node']['table']['group'] = t('Tracker'); + $data['tracker_node']['table']['join'] = array( + 'node' => array( + 'type' => 'INNER', + 'left_field' => 'nid', + 'field' => 'nid', + ), + ); + $data['tracker_node']['nid'] = array( + 'title' => t('Nid'), + 'help' => t('The node ID of the node.'), + 'field' => array( + 'handler' => 'views_handler_field_node', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'handler' => 'views_handler_argument_node_nid', + 'name field' => 'title', + 'numeric' => TRUE, + 'validate type' => 'nid', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + $data['tracker_node']['changed'] = array( + 'title' => t('Updated date'), + 'help' => t('The date the node was last updated.'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_date', + ), + ); + $data['tracker_node']['published'] = array( + 'title' => t('Published'), + 'help' => t('Whether or not the node is published.'), + 'field' => array( + 'handler' => 'views_handler_field_boolean', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_tracker_boolean_operator', + 'label' => t('Published'), + 'type' => 'yes-no', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['tracker_user']['table']['group'] = t('Tracker - User'); + $data['tracker_user']['table']['join'] = array( + 'node' => array( + 'type' => 'INNER', + 'left_field' => 'nid', + 'field' => 'nid', + ), + 'user' => array( + 'type' => 'INNER', + 'left_field' => 'uid', + 'field' => 'uid', + ), + ); + $data['tracker_user']['nid'] = array( + 'title' => t('Nid'), + 'help' => t('The node ID of the node a user created or commented on. You must use an argument or filter on UID or you will get misleading results using this field.'), + 'field' => array( + 'handler' => 'views_handler_field_node', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'handler' => 'views_handler_argument_node_nid', + 'name field' => 'title', + 'numeric' => TRUE, + 'validate type' => 'nid', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + $data['tracker_user']['uid'] = array( + 'title' => t('Uid'), + 'help' => t('The user ID of a user who touched the node (either created or commented on it).'), + 'field' => array( + 'handler' => 'views_handler_field_user', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'handler' => 'views_handler_argument_user_uid', + 'name field' => 'name', + ), + 'filter' => array( + 'title' => t('Name'), + 'handler' => 'views_handler_filter_user_name', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + $data['tracker_user']['changed'] = array( + 'title' => t('Updated date'), + 'help' => t('The date the node was last updated or commented on. You must use an argument or filter on UID or you will get misleading results using this field.'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_date', + ), + ); + $data['tracker_user']['published'] = array( + 'title' => t('Published'), + 'help' => t('Whether or not the node is published. You must use an argument or filter on UID or you will get misleading results using this field.'), + 'field' => array( + 'handler' => 'views_handler_field_boolean', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_tracker_boolean_operator', + 'label' => t('Published'), + 'type' => 'yes-no', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + return $data; +} + +/** + * Implementation of hook_views_data_alter(). + */ +function tracker_views_data_alter(&$data) { + // Provide additional uid_touch handlers which are handled by tracker + $data['node']['uid_touch_tracker'] = array( + 'group' => t('Tracker - User'), + 'title' => t('User posted or commented'), + 'help' => t('Display nodes only if a user posted the node or commented on the node.'), + 'argument' => array( + 'field' => 'uid', + 'name table' => 'users', + 'name field' => 'name', + 'handler' => 'views_handler_argument_tracker_comment_user_uid', + 'no group by' => TRUE, + ), + 'filter' => array( + 'field' => 'uid', + 'name table' => 'users', + 'name field' => 'name', + 'handler' => 'views_handler_filter_tracker_comment_user_uid' + ), + ); +} diff --git a/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc b/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc new file mode 100644 index 0000000000000000000000000000000000000000..e614482d403a848681944007a5a143581d7f8249 --- /dev/null +++ b/sites/all/modules/views/modules/tracker/views_handler_argument_tracker_comment_user_uid.inc @@ -0,0 +1,26 @@ +<?php + +/** + * @file + * Contains views_handler_argument_tracker_comment_user_uid + */ + +/** + * UID argument to check for nodes that user posted or commented on. + * + * @ingroup views_argument_handlers + */ +class views_handler_argument_tracker_comment_user_uid extends views_handler_argument_comment_user_uid { + + /** + * Overrides views_handler_argument_comment_user_uid::query(). + */ + function query($group_by = FALSE) { + // Because this handler thinks it's an argument for a field on the {node} + // table, we need to make sure {tracker_user} is JOINed and use its alias + // for the WHERE clause. + $tracker_user_alias = $this->query->ensure_table('tracker_user'); + $this->query->add_where(0, "$tracker_user_alias.uid", $this->argument); + } + +} diff --git a/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc new file mode 100644 index 0000000000000000000000000000000000000000..455e824205ba1ccf68a93a13e6947bca685df010 --- /dev/null +++ b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_boolean_operator.inc @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Contains views_handler_filter_tracker_boolean_operator + */ + + /** + * Filter handler for boolean values to use = 1 instead of <> 0. + */ +class views_handler_filter_tracker_boolean_operator extends views_handler_filter_boolean_operator { + + /** + * Overrides views_handler_filter_boolean_operator::query(). + */ + function query() { + $this->ensure_my_table(); + $where = "$this->table_alias.$this->real_field "; + if (empty($this->value)) { + $where .= '= 0'; + if ($this->accept_null) { + $where = '(' . $where . " OR $this->table_alias.$this->real_field IS NULL)"; + } + } + else { + $where .= '= 1'; + } + $this->query->add_where_expression($this->options['group'], $where); + } + +} diff --git a/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc new file mode 100644 index 0000000000000000000000000000000000000000..da6f65bc30377c523dcfb5217a314c595ddea570 --- /dev/null +++ b/sites/all/modules/views/modules/tracker/views_handler_filter_tracker_comment_user_uid.inc @@ -0,0 +1,23 @@ +<?php +/** + * @file + * Contains views_handler_filter_tracker_comment_user_uid + */ + +/** + * UID filter to check for nodes that user posted or commented on. + */ +class views_handler_filter_tracker_comment_user_uid extends views_handler_filter_comment_user_uid { + + /** + * Overrides views_handler_filter_comment_user_uid::query() + */ + function query() { + // Because this handler thinks it's an argument for a field on the {node} + // table, we need to make sure {tracker_user} is JOINed and use its alias + // for the WHERE clause. + $tracker_user_alias = $this->query->ensure_table('tracker_user'); + $this->query->add_where(0, "$tracker_user_alias.uid", $this->value); + } + +} diff --git a/sites/all/modules/views/plugins/export_ui/views_ui.class.php b/sites/all/modules/views/plugins/export_ui/views_ui.class.php index 22b65cb028f18ce3186e89a8fec7a6924dbd3db3..9d8013828002ca2b7afc10518d71c068e16dcf23 100644 --- a/sites/all/modules/views/plugins/export_ui/views_ui.class.php +++ b/sites/all/modules/views/plugins/export_ui/views_ui.class.php @@ -376,6 +376,11 @@ class views_ui extends ctools_export_ui { } function list_page($js, $input) { + // Remove filters values from session if filters are hidden. + if (!variable_get('views_ui_show_listing_filters', FALSE) && isset($_SESSION['ctools_export_ui'][$this->plugin['name']])) { + unset($_SESSION['ctools_export_ui'][$this->plugin['name']]); + } + // wrap output in a div for CSS $output = parent::list_page($js, $input); if (is_string($output)) { diff --git a/sites/all/modules/views/plugins/views_plugin_display.inc b/sites/all/modules/views/plugins/views_plugin_display.inc index 6be3dcc65f47b7d0ea72e22683ef0e8d6d386f3b..75a32c513add4989b9e61b7e380ad7442d7e0bf0 100644 --- a/sites/all/modules/views/plugins/views_plugin_display.inc +++ b/sites/all/modules/views/plugins/views_plugin_display.inc @@ -476,7 +476,7 @@ class views_plugin_display extends views_plugin { 'display_description' => FALSE, 'use_ajax' => TRUE, 'hide_attachment_summary' => TRUE, - 'hide_admin_links' => FALSE, + 'hide_admin_links' => TRUE, 'pager' => TRUE, 'pager_options' => TRUE, 'use_more' => TRUE, @@ -783,6 +783,10 @@ class views_plugin_display extends views_plugin { if ($display_id && !empty($this->view->display[$display_id]) && is_object($this->view->display[$display_id]->handler)) { return $this->view->display[$display_id]->handler->get_path(); } + + if ($this->get_option('link_display') == 'custom_url' && $link_url = $this->get_option('link_url')) { + return $link_url; + } } function get_url() { diff --git a/sites/all/modules/views/plugins/views_plugin_display_feed.inc b/sites/all/modules/views/plugins/views_plugin_display_feed.inc index 37d0ae4e291fd48f1cf9ab538719bad418e2fe54..bfd220c5d7c586c3895ea9ec40f49387b3a57d6b 100644 --- a/sites/all/modules/views/plugins/views_plugin_display_feed.inc +++ b/sites/all/modules/views/plugins/views_plugin_display_feed.inc @@ -139,7 +139,6 @@ class views_plugin_display_feed extends views_plugin_display_page { * Provide the default form for setting options. */ function options_form(&$form, &$form_state) { - parent::options_form($form, $form_state); // It is very important to call the parent function here. parent::options_form($form, $form_state); diff --git a/sites/all/modules/views/plugins/views_plugin_display_page.inc b/sites/all/modules/views/plugins/views_plugin_display_page.inc index cd7fe377ad0760c7ab2cac1518ceac25d747fc5d..7ca4bf7fe9057887750e1a09026c23833c751a8a 100644 --- a/sites/all/modules/views/plugins/views_plugin_display_page.inc +++ b/sites/all/modules/views/plugins/views_plugin_display_page.inc @@ -30,6 +30,7 @@ class views_plugin_display_page extends views_plugin_display { 'weight' => array('default' => 0), 'name' => array('default' => variable_get('menu_default_node_menu', 'navigation')), 'context' => array('default' => ''), + 'context_only_inline' => array('default' => FALSE), ), ); $options['tab_options'] = array( @@ -153,7 +154,7 @@ class views_plugin_display_page extends views_plugin_display { // Add context for contextual links. // @see menu_contextual_links() if (!empty($menu['context'])) { - $items[$path]['context'] = MENU_CONTEXT_INLINE; + $items[$path]['context'] = !empty($menu['context_only_inline']) ? MENU_CONTEXT_INLINE : (MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE); } // If this is a 'default' tab, check to see if we have to create teh @@ -251,9 +252,12 @@ class views_plugin_display_page extends views_plugin_display { ), ); - $path = strip_tags('/' . $this->get_option('path')); + $path = strip_tags($this->get_option('path')); if (empty($path)) { - $path = t('None'); + $path = t('No path is set'); + } + else { + $path = '/' . $path; } $options['path'] = array( @@ -297,7 +301,6 @@ class views_plugin_display_page extends views_plugin_display { * Provide the default form for setting options. */ function options_form(&$form, &$form_state) { - parent::options_form($form, $form_state); // It is very important to call the parent function here: parent::options_form($form, $form_state); @@ -384,12 +387,23 @@ class views_plugin_display_page extends views_plugin_display { ); $form['menu']['context'] = array( '#title' => t('Context'), - '#suffix' => '</div>', '#type' => 'checkbox', '#default_value' => !empty($menu['context']), '#description' => t('Displays the link in contextual links'), '#dependency' => array('radio:menu[type]' => array('tab')), ); + $form['menu']['context_only_inline'] = array( + '#title' => t('Hide menu tab'), + '#suffix' => '</div>', + '#type' => 'checkbox', + '#default_value' => !empty($menu['context_only_inline']), + '#description' => t('Only display menu item entry in contextual links. Menu tab should not be displayed.'), + '#dependency' => array( + 'radio:menu[type]' => array('tab'), + 'edit-menu-context' => array(1), + ), + '#dependency_count' => 2, + ); break; case 'tab_options': $form['#title'] .= t('Default tab options'); diff --git a/sites/all/modules/views/plugins/views_plugin_exposed_form.inc b/sites/all/modules/views/plugins/views_plugin_exposed_form.inc index 833f44b694ad6e8ed8b33cde86b8d212c05c9c86..343eee8161628d194dee87992406e09a4def56f8 100644 --- a/sites/all/modules/views/plugins/views_plugin_exposed_form.inc +++ b/sites/all/modules/views/plugins/views_plugin_exposed_form.inc @@ -41,6 +41,7 @@ class views_plugin_exposed_form extends views_plugin { $options['reset_button'] = array('default' => FALSE, 'bool' => TRUE); $options['reset_button_label'] = array('default' => 'Reset', 'translatable' => TRUE); $options['exposed_sorts_label'] = array('default' => 'Sort by', 'translatable' => TRUE); + $options['expose_sort_order'] = array('default' => TRUE, 'bool' => TRUE); $options['sort_asc_label'] = array('default' => 'Asc', 'translatable' => TRUE); $options['sort_desc_label'] = array('default' => 'Desc', 'translatable' => TRUE); $options['autosubmit'] = array('default' => FALSE, 'bool' => TRUE); @@ -84,12 +85,20 @@ class views_plugin_exposed_form extends views_plugin { '#required' => TRUE, ); + $form['expose_sort_order'] = array( + '#type' => 'checkbox', + '#title' => t('Expose sort order'), + '#description' => t('Allow the user to choose the sort order. If sort order is not exposed, the sort criteria settings for each sort will determine its order.'), + '#default_value' => $this->options['expose_sort_order'], + ); + $form['sort_asc_label'] = array( '#type' => 'textfield', '#title' => t('Ascending'), '#description' => t('Text to use when exposed sort is ordered ascending.'), '#default_value' => $this->options['sort_asc_label'], '#required' => TRUE, + '#dependency' => array('edit-exposed-form-options-expose-sort-order' => array(TRUE)), ); $form['sort_desc_label'] = array( @@ -98,6 +107,7 @@ class views_plugin_exposed_form extends views_plugin { '#description' => t('Text to use when exposed sort is ordered descending.'), '#default_value' => $this->options['sort_desc_label'], '#required' => TRUE, + '#dependency' => array('edit-exposed-form-options-expose-sort-order' => array(TRUE)), ); $form['autosubmit'] = array( @@ -231,12 +241,14 @@ class views_plugin_exposed_form extends views_plugin { $form_state['input']['sort_by'] = array_shift($keys); } - $form['sort_order'] = array( - '#type' => 'select', - '#options' => $sort_order, - '#title' => t('Order'), - '#default_value' => $default_sort_order, - ); + if ($this->options['expose_sort_order']) { + $form['sort_order'] = array( + '#type' => 'select', + '#options' => $sort_order, + '#title' => t('Order'), + '#default_value' => $default_sort_order, + ); + } $form['submit']['#weight'] = 10; if (isset($form['reset'])) { $form['reset']['#weight'] = 10; @@ -312,6 +324,7 @@ class views_plugin_exposed_form extends views_plugin { $this->view->exposed_data = array(); } + $form_state['redirect'] = current_path(); $form_state['values'] = array(); } } diff --git a/sites/all/modules/views/plugins/views_plugin_pager_mini.inc b/sites/all/modules/views/plugins/views_plugin_pager_mini.inc index 2daea99950081fc67dedb997b62082b2cd1c766d..bec48c892745bc42b7a3d9ef8aa4bd8ff7a8ea9e 100644 --- a/sites/all/modules/views/plugins/views_plugin_pager_mini.inc +++ b/sites/all/modules/views/plugins/views_plugin_pager_mini.inc @@ -6,7 +6,7 @@ */ /** - * The plugin to handle full pager. + * The plugin to handle mini pager. * * @ingroup views_pager_plugins */ @@ -18,9 +18,53 @@ class views_plugin_pager_mini extends views_plugin_pager_full { return format_plural($this->options['items_per_page'], 'Mini pager, @count item', 'Mini pager, @count items', array('@count' => $this->options['items_per_page'])); } + /** + * Overrides views_plugin_pager_full::option_definition(). + * + * Overrides the full pager options form by deleting unused settings. + */ + function option_definition() { + $options = parent::option_definition(); + + unset($options['quantity']); + unset($options['tags']['first']); + unset($options['tags']['last']); + $options['tags']['previous']['default'] = '‹‹'; + $options['tags']['next']['default'] = '››'; + + return $options; + } + + /** + * Overrides views_plugin_pager_full::options_form(). + * + * Overrides the full pager options form by deleting unused settings. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + unset($form['quantity']); + unset($form['tags']['first']); + unset($form['tags']['last']); + } + + /** + * Overrides views_plugin_pager_full::render(). + * + * Overrides the full pager renderer by changing the theme function + * and leaving out variables that are not used in the mini pager. + */ function render($input) { $pager_theme = views_theme_functions('views_mini_pager', $this->view, $this->display); + // The 1, 3 index are correct. + // @see theme_pager(). + $tags = array( + 1 => $this->options['tags']['previous'], + 3 => $this->options['tags']['next'], + ); return theme($pager_theme, array( - 'parameters' => $input, 'element' => $this->options['id'])); + 'tags' => $tags, + 'element' => $this->options['id'], + 'parameters' => $input, + )); } } diff --git a/sites/all/modules/views/plugins/views_plugin_query_default.inc b/sites/all/modules/views/plugins/views_plugin_query_default.inc index c6c764998b23c661a71af4c38e75acc402a2ceca..030c5ea00324ee4c3cf23a3abc787543c067b6e8 100644 --- a/sites/all/modules/views/plugins/views_plugin_query_default.inc +++ b/sites/all/modules/views/plugins/views_plugin_query_default.inc @@ -1578,7 +1578,7 @@ class views_plugin_query_default extends views_plugin_query { ), ), 'stddev_pop' => array( - 'title' => t('Standard derivation'), + 'title' => t('Standard deviation'), 'method' => 'views_query_default_aggregation_method_simple', 'handler' => array( 'argument' => 'views_handler_argument_group_by_numeric', diff --git a/sites/all/modules/views/plugins/views_plugin_style_grid.inc b/sites/all/modules/views/plugins/views_plugin_style_grid.inc index 9be7ee1e8f4605cc78f8c253bde334966dc80a18..a2e43755962d18b7bfa11fc11089797934ff189a 100644 --- a/sites/all/modules/views/plugins/views_plugin_style_grid.inc +++ b/sites/all/modules/views/plugins/views_plugin_style_grid.inc @@ -21,6 +21,7 @@ class views_plugin_style_grid extends views_plugin_style { $options['alignment'] = array('default' => 'horizontal'); $options['fill_single_line'] = array('default' => TRUE, 'bool' => TRUE); $options['summary'] = array('default' => ''); + $options['caption'] = array('default' => ''); return $options; } @@ -52,10 +53,17 @@ class views_plugin_style_grid extends views_plugin_style { '#default_value' => !empty($this->options['fill_single_line']), ); + $form['caption'] = array( + '#type' => 'textfield', + '#title' => t('Short description of table'), + '#description' => t('Include a caption for better accessibility of your table.'), + '#default_value' => $this->options['caption'], + ); + $form['summary'] = array( '#type' => 'textfield', '#title' => t('Table summary'), - '#description' => t('This value will be displayed as table-summary attribute in the html. Set this for better accessiblity of your site.'), + '#description' => t('This value will be displayed as table-summary attribute in the html. Use this to give a summary of complex tables.'), '#default_value' => $this->options['summary'], ); } diff --git a/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc b/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc index f571f6225751052fc9a5a26b4a417cda518c33c1..16b0aef76e0e8de5c14178c83380817ea02b5bcc 100644 --- a/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc +++ b/sites/all/modules/views/plugins/views_plugin_style_jump_menu.inc @@ -17,7 +17,9 @@ class views_plugin_style_jump_menu extends views_plugin_style { $options['hide'] = array('default' => FALSE, 'bool' => TRUE); $options['path'] = array('default' => ''); $options['text'] = array('default' => 'Go', 'translatable' => TRUE); + $options['label'] = array('default' => '', 'translatable' => TRUE); $options['choose'] = array('default' => '- Choose -', 'translatable' => TRUE); + $options['inline'] = array('default' => TRUE, 'bool' => TRUE); $options['default_value'] = array('default' => FALSE, 'bool' => TRUE); return $options; @@ -68,6 +70,13 @@ class views_plugin_style_jump_menu extends views_plugin_style { '#default_value' => $this->options['text'], ); + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Selector label'), + '#default_value' => $this->options['label'], + '#description' => t('The text that will appear as the the label of the selector element. If blank no label tag will be used.'), + ); + $form['choose'] = array( '#type' => 'textfield', '#title' => t('Choose text'), @@ -75,6 +84,12 @@ class views_plugin_style_jump_menu extends views_plugin_style { '#description' => t('The text that will appear as the selected option in the jump menu.'), ); + $form['inline'] = array( + '#type' => 'checkbox', + '#title' => t('Set this field to display inline'), + '#default_value' => !empty($this->options['inline']), + ); + $form['default_value'] = array( '#type' => 'checkbox', '#title' => t('Select the current contextual filter value'), @@ -135,7 +150,9 @@ class views_plugin_style_jump_menu extends views_plugin_style { $settings = array( 'hide' => $this->options['hide'], 'button' => $this->options['text'], + 'title' => $this->options['label'], 'choose' => $this->options['choose'], + 'inline' => $this->options['inline'], 'default_value' => $default_value, ); diff --git a/sites/all/modules/views/plugins/views_plugin_style_mapping.inc b/sites/all/modules/views/plugins/views_plugin_style_mapping.inc new file mode 100644 index 0000000000000000000000000000000000000000..fb60a03f279d6eaa703f6ef396c3d5316a7bda37 --- /dev/null +++ b/sites/all/modules/views/plugins/views_plugin_style_mapping.inc @@ -0,0 +1,125 @@ +<?php + +/** + * @file + * Definition of views_plugin_style_mapping. + */ + +/** + * Allows fields to be mapped to specific use cases. + */ +abstract class views_plugin_style_mapping extends views_plugin_style { + + /** + * Builds the list of field mappings. + * + * @return array + * An associative array, keyed by the field name, containing the following + * key-value pairs: + * - #title: The human-readable label for this field. + * - #default_value: The default value for this field. If not provided, an + * empty string will be used. + * - #description: A description of this field. + * - #required: Whether this field is required. + * - #filter: (optional) A method on the plugin to filter field options. + * - #toggle: (optional) If this select should be toggled by a checkbox. + */ + abstract protected function define_mapping(); + + /** + * Overrides views_plugin_style::option_definition(). + */ + function option_definition() { + $options = parent::option_definition(); + + // Parse the mapping and add a default for each. + foreach ($this->define_mapping() as $key => $value) { + $default = !empty($value['#multiple']) ? array() : ''; + $options['mapping']['contains'][$key] = array( + 'default' => isset($value['#default_value']) ? $value['#default_value'] : $default, + ); + if (!empty($value['#toggle'])) { + $options['mapping']['contains']["toggle_$key"] = array( + 'default' => FALSE, + 'bool' => TRUE, + ); + } + } + + return $options; + } + + /** + * Overrides views_plugin_style::options_form(). + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // Get the mapping. + $mapping = $this->define_mapping(); + + // Restrict the list of defaults to the mapping, in case they have changed. + $options = array_intersect_key($this->options['mapping'], $mapping); + + // Get the labels of the fields added to this display. + $field_labels = $this->display->handler->get_field_labels(); + + // Provide some default values. + $defaults = array( + '#type' => 'select', + '#required' => FALSE, + '#multiple' => FALSE, + ); + + // For each mapping, add a select element to the form. + foreach ($options as $key => $value) { + // If the field is optional, add a 'None' value to the top of the options. + $field_options = array(); + $required = !empty($mapping[$key]['#required']); + if (!$required && empty($mapping[$key]['#multiple'])) { + $field_options = array('' => t('- None -')); + } + $field_options += $field_labels; + + // Optionally filter the available fields. + if (isset($mapping[$key]['#filter'])) { + $this->view->init_handlers(); + $this::$mapping[$key]['#filter']($field_options); + unset($mapping[$key]['#filter']); + } + + // These values must always be set. + $overrides = array( + '#options' => $field_options, + '#default_value' => $options[$key], + ); + + // Optionally allow the select to be toggleable. + if (!empty($mapping[$key]['#toggle'])) { + $form['mapping']["toggle_$key"] = array( + '#type' => 'checkbox', + '#title' => t('Use a custom %field_name', array('%field_name' => strtolower($mapping[$key]['#title']))), + '#default_value' => $this->options['mapping']["toggle_$key"], + ); + $overrides['#states']['visible'][':input[name="style_options[mapping][' . "toggle_$key" . ']"]'] = array('checked' => TRUE); + } + + $form['mapping'][$key] = $overrides + $mapping[$key] + $defaults; + } + } + + /** + * Overrides views_plugin_style::render(). + * + * Provides the mapping definition as an available variable. + */ + function render() { + return theme($this->theme_functions(), array( + 'view' => $this->view, + 'options' => $this->options, + 'rows' => $this->view->result, + 'mapping' => $this->define_mapping(), + )); + } + +} diff --git a/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc b/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc index 5b02163d85b7c2676b51ab21adca8aa4c1eaafe3..a16a84b1a1d25d5f2763d51772f5a179d9bdb11c 100644 --- a/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc +++ b/sites/all/modules/views/plugins/views_plugin_style_summary_jump_menu.inc @@ -18,7 +18,9 @@ class views_plugin_style_summary_jump_menu extends views_plugin_style { $options['count'] = array('default' => TRUE, 'bool' => TRUE); $options['hide'] = array('default' => FALSE, 'bool' => TRUE); $options['text'] = array('default' => 'Go', 'translatable' => TRUE); + $options['label'] = array('default' => '', 'translatable' => TRUE); $options['choose'] = array('default' => '- Choose -', 'translatable' => TRUE); + $options['inline'] = array('default' => TRUE, 'bool' => TRUE); $options['default_value'] = array('default' => FALSE, 'bool' => TRUE); return $options; @@ -63,6 +65,13 @@ class views_plugin_style_summary_jump_menu extends views_plugin_style { '#default_value' => $this->options['text'], ); + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Selector label'), + '#default_value' => $this->options['label'], + '#description' => t('The text that will appear as the the label of the selector element. If blank no label tag will be used.'), + ); + $form['choose'] = array( '#type' => 'textfield', '#title' => t('Choose text'), @@ -70,6 +79,12 @@ class views_plugin_style_summary_jump_menu extends views_plugin_style { '#description' => t('The text that will appear as the selected option in the jump menu.'), ); + $form['inline'] = array( + '#type' => 'checkbox', + '#title' => t('Set this field to display inline'), + '#default_value' => !empty($this->options['inline']), + ); + $form['default_value'] = array( '#type' => 'checkbox', '#title' => t('Select the current contextual filter value'), @@ -119,7 +134,9 @@ class views_plugin_style_summary_jump_menu extends views_plugin_style { $settings = array( 'hide' => $this->options['hide'], 'button' => $this->options['text'], + 'title' => $this->options['label'], 'choose' => $this->options['choose'], + 'inline' => $this->options['inline'], 'default_value' => $default_value, ); diff --git a/sites/all/modules/views/plugins/views_plugin_style_table.inc b/sites/all/modules/views/plugins/views_plugin_style_table.inc index 98919ab4017c64d41094299988c8f553a4ab640d..45ed97634f566c8b8ee824cbafd7dad99397a6f6 100644 --- a/sites/all/modules/views/plugins/views_plugin_style_table.inc +++ b/sites/all/modules/views/plugins/views_plugin_style_table.inc @@ -33,6 +33,7 @@ class views_plugin_style_table extends views_plugin_style { $options['override'] = array('default' => TRUE, 'bool' => TRUE); $options['sticky'] = array('default' => FALSE, 'bool' => TRUE); $options['order'] = array('default' => 'asc'); + $options['caption'] = array('default' => '', 'translatable' => TRUE); $options['summary'] = array('default' => '', 'translatable' => TRUE); $options['empty_table'] = array('default' => FALSE, 'bool' => TRUE); @@ -179,10 +180,18 @@ class views_plugin_style_table extends views_plugin_style { '#description' => t('(Sticky header effects will not be active for preview below, only on live output.)'), ); + $form['caption'] = array( + '#type' => 'textfield', + '#title' => t('Short description of table'), + '#description' => t('Include a caption for better accessibility of your table.'), + '#default_value' => $this->options['caption'], + '#maxlength' => 255, + ); + $form['summary'] = array( '#type' => 'textfield', '#title' => t('Table summary'), - '#description' => t('This value will be displayed as table-summary attribute in the html. Set this for better accessiblity of your site.'), + '#description' => t('This value will be displayed as table-summary attribute in the html. Use this to give a summary of complex tables.'), '#default_value' => $this->options['summary'], '#maxlength' => 255, ); diff --git a/sites/all/modules/views/tests/field/views_fieldapi.test b/sites/all/modules/views/tests/field/views_fieldapi.test index c5393b5ba9837feb5f02164cbccb667355464b9a..da4c27b379ffaf3540d2e3da2b953a3222902351 100644 --- a/sites/all/modules/views/tests/field/views_fieldapi.test +++ b/sites/all/modules/views/tests/field/views_fieldapi.test @@ -88,10 +88,9 @@ class ViewsFieldApiTestHelper extends ViewsSqlTest { */ function clearViewsCaches() { // Reset views data cache. - $cache = &drupal_static('_views_fetch_data' . '_cache'); - $recursion_protection = &drupal_static('_views_fetch_data' . '_recursion_protected'); - $cache = NULL; - $recursion_protection = NULL; + drupal_static_reset('_views_fetch_data_cache'); + drupal_static_reset('_views_fetch_data_recursion_protected'); + drupal_static_reset('_views_fetch_data_fully_loaded'); } } @@ -169,10 +168,7 @@ class viewsFieldApiDataTest extends ViewsFieldApiTestHelper { } // Reset views data cache. - $cache = &drupal_static('_views_fetch_data' . '_cache'); - $recursion_protection = &drupal_static('_views_fetch_data' . '_recursion_protected'); - $cache = NULL; - $recursion_protection = NULL; + $this->clearViewsCaches(); } /** diff --git a/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test b/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test index 92ec7a51da61aabf7aed71a8f1fe94f4a2ba2ad2..286b9425ce77b5f6f685697a9865bc7aaf888134 100644 --- a/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test +++ b/sites/all/modules/views/tests/handlers/views_handler_field_boolean.test @@ -66,11 +66,43 @@ class ViewsHandlerFieldBooleanTest extends ViewsSqlTest { $this->assertEqual('✖', $view->field['age']->advanced_render($view->result[0])); $this->assertEqual('✔', $view->field['age']->advanced_render($view->result[1])); - // Set a custom output format. + // Set a custom output format programmatically. $view->field['age']->formats['test'] = array(t('Test-True'), t('Test-False')); $view->field['age']->options['type'] = 'test'; $this->assertEqual(t('Test-False'), $view->field['age']->advanced_render($view->result[0])); $this->assertEqual(t('Test-True'), $view->field['age']->advanced_render($view->result[1])); + // Set a custom output format through the UI using plain-text inputs. + $view->field['age']->options['type'] = 'custom'; + $values = array( + 'false' => 'Nay', + 'true' => 'Yay', + ); + $view->field['age']->options['type_custom_false'] = $values['false']; + $view->field['age']->options['type_custom_true'] = $values['true']; + $this->assertEqual($values['false'], $view->field['age']->advanced_render($view->result[0])); + $this->assertEqual($values['true'], $view->field['age']->advanced_render($view->result[1])); + + // Set a custom output format through the UI using valid HTML inputs. + $view->field['age']->options['type'] = 'custom'; + $values = array( + 'false' => '<div class="bar">Nay</div>', + 'true' => '<div class="foo">Yay</div>', + ); + $view->field['age']->options['type_custom_false'] = $values['false']; + $view->field['age']->options['type_custom_true'] = $values['true']; + $this->assertEqual($values['false'], $view->field['age']->advanced_render($view->result[0])); + $this->assertEqual($values['true'], $view->field['age']->advanced_render($view->result[1])); + + // Set a custom output format through the UI using unsafe inputs. + $view->field['age']->options['type'] = 'custom'; + $values = array( + 'false' => '<script>alert("Nay");</script>', + 'true' => '<script>alert("Yay");</script>', + ); + $view->field['age']->options['type_custom_false'] = $values['false']; + $view->field['age']->options['type_custom_true'] = $values['true']; + $this->assertNotEqual($values['false'], $view->field['age']->advanced_render($view->result[0])); + $this->assertNotEqual($values['true'], $view->field['age']->advanced_render($view->result[1])); } } diff --git a/sites/all/modules/views/tests/node/views_node_revision_relations.test b/sites/all/modules/views/tests/node/views_node_revision_relations.test new file mode 100644 index 0000000000000000000000000000000000000000..6b38396f89dd062f544115813bda809dd8cded94 --- /dev/null +++ b/sites/all/modules/views/tests/node/views_node_revision_relations.test @@ -0,0 +1,177 @@ +<?php + +/** + * @file + * Definition of ViewsNodeRevisionRelationsTestCase. + */ + +/** + * Tests basic node_revision table integration into views. + */ +class ViewsNodeRevisionRelationsTestCase extends ViewsSqlTest { + + public static function getInfo() { + return array( + 'name' => 'Tests basic node_revision integration', + 'description' => 'Tests the integration of node_revision table of node module', + 'group' => 'Views Modules', + ); + } + + /** + * Create a node with revision and rest result count for both views. + */ + public function testNodeRevisionRelationship() { + $node = $this->drupalCreateNode(); + // Create revision of the node. + $node_revision = clone $node; + $node_revision->revision = 1; + node_save($node_revision); + $column_map = array( + 'vid' => 'vid', + 'node_revision_nid' => 'node_revision_nid', + 'node_node_revision_nid' => 'node_node_revision_nid', + ); + + // Here should be two rows. + $view_nid = $this->test_view_node_revision_nid(); + $this->executeView($view_nid, array($node->nid)); + $resultset_nid = array( + array( + 'vid' => '1', + 'node_revision_nid' => '1', + 'node_node_revision_nid' => '1', + ), + array( + 'vid' => '2', + 'node_revision_nid' => '1', + 'node_node_revision_nid' => '1', + ), + ); + $this->assertIdenticalResultset($view_nid, $resultset_nid, $column_map); + + // There should be only one row with active revision 2. + $view_vid = $this->test_view_node_revision_vid(); + $this->executeView($view_vid, array($node->nid)); + $resultset_vid = array( + array( + 'vid' => '2', + 'node_revision_nid' => '1', + 'node_node_revision_nid' => '1', + ), + ); + $this->assertIdenticalResultset($view_vid, $resultset_vid, $column_map); + } + + /** + * Test view with default join on node.nid. + */ + function test_view_node_revision_nid() { + $view = new view(); + $view->name = 'test_node_revision_nid'; + $view->description = ''; + $view->tag = ''; + $view->base_table = 'node_revision'; + $view->human_name = 'Test node revision nid'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'view revisions'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'fields'; + /* Relationship: Content revision: Content */ + $handler->display->display_options['relationships']['nid']['id'] = 'nid'; + $handler->display->display_options['relationships']['nid']['table'] = 'node_revision'; + $handler->display->display_options['relationships']['nid']['field'] = 'nid'; + $handler->display->display_options['relationships']['nid']['label'] = 'NID'; + $handler->display->display_options['relationships']['nid']['required'] = TRUE; + /* Field: Content revision: Vid */ + $handler->display->display_options['fields']['vid']['id'] = 'vid'; + $handler->display->display_options['fields']['vid']['table'] = 'node_revision'; + $handler->display->display_options['fields']['vid']['field'] = 'vid'; + /* Field: Content revision: Nid */ + $handler->display->display_options['fields']['nid_1']['id'] = 'nid_1'; + $handler->display->display_options['fields']['nid_1']['table'] = 'node_revision'; + $handler->display->display_options['fields']['nid_1']['field'] = 'nid'; + /* Field: Content: Nid */ + $handler->display->display_options['fields']['nid']['id'] = 'nid'; + $handler->display->display_options['fields']['nid']['table'] = 'node'; + $handler->display->display_options['fields']['nid']['field'] = 'nid'; + $handler->display->display_options['fields']['nid']['relationship'] = 'nid'; + /* Contextual filter: Content revision: Nid */ + $handler->display->display_options['arguments']['nid']['id'] = 'nid'; + $handler->display->display_options['arguments']['nid']['table'] = 'node_revision'; + $handler->display->display_options['arguments']['nid']['field'] = 'nid'; + $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed'; + $handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0'; + $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary'; + $handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25'; + + return $view; + } + + /** + * Test view with default join on node.vid. + */ + function test_view_node_revision_vid() { + $view = new view(); + $view->name = 'test_node_revision_vid'; + $view->description = ''; + $view->tag = ''; + $view->base_table = 'node_revision'; + $view->human_name = 'Test node revision vid'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'view revisions'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'fields'; + /* Relationship: Content revision: Content */ + $handler->display->display_options['relationships']['vid']['id'] = 'vid'; + $handler->display->display_options['relationships']['vid']['table'] = 'node_revision'; + $handler->display->display_options['relationships']['vid']['field'] = 'vid'; + $handler->display->display_options['relationships']['vid']['label'] = 'VID'; + $handler->display->display_options['relationships']['vid']['required'] = TRUE; + /* Field: Content revision: Vid */ + $handler->display->display_options['fields']['vid']['id'] = 'vid'; + $handler->display->display_options['fields']['vid']['table'] = 'node_revision'; + $handler->display->display_options['fields']['vid']['field'] = 'vid'; + /* Field: Content revision: Nid */ + $handler->display->display_options['fields']['nid_1']['id'] = 'nid_1'; + $handler->display->display_options['fields']['nid_1']['table'] = 'node_revision'; + $handler->display->display_options['fields']['nid_1']['field'] = 'nid'; + /* Field: Content: Nid */ + $handler->display->display_options['fields']['nid']['id'] = 'nid'; + $handler->display->display_options['fields']['nid']['table'] = 'node'; + $handler->display->display_options['fields']['nid']['field'] = 'nid'; + $handler->display->display_options['fields']['nid']['relationship'] = 'vid'; + /* Contextual filter: Content revision: Nid */ + $handler->display->display_options['arguments']['nid']['id'] = 'nid'; + $handler->display->display_options['arguments']['nid']['table'] = 'node_revision'; + $handler->display->display_options['arguments']['nid']['field'] = 'nid'; + $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed'; + $handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0'; + $handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary'; + $handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25'; + + return $view; + } +} diff --git a/sites/all/modules/views/tests/styles/views_plugin_style.test b/sites/all/modules/views/tests/styles/views_plugin_style.test index 87f5b7d1021a1e56d16352c9c8bdd226a08c2924..dfc5413acae76ffb2ead7c96066e94e91649f461 100644 --- a/sites/all/modules/views/tests/styles/views_plugin_style.test +++ b/sites/all/modules/views/tests/styles/views_plugin_style.test @@ -8,7 +8,7 @@ /** * Tests some general style plugin related functionality. */ -class ViewsPluginStyleTestCase extends ViewsSqlTest { +class ViewsPluginStyleTestCase extends ViewsPluginStyleTestBase { public static function getInfo() { return array( 'name' => 'Styles', @@ -232,20 +232,6 @@ class ViewsPluginStyleTestCase extends ViewsSqlTest { } } - - /** - * Stores a view output in the elements. - */ - function storeViewPreview($output) { - $htmlDom = new DOMDocument(); - @$htmlDom->loadHTML($output); - if ($htmlDom) { - // It's much easier to work with simplexml than DOM, luckily enough - // we can just simply import our DOM tree. - $this->elements = simplexml_import_dom($htmlDom); - } - } - /** * Tests custom css classes. */ diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_base.test b/sites/all/modules/views/tests/styles/views_plugin_style_base.test new file mode 100644 index 0000000000000000000000000000000000000000..514077dbea44fe95ac87963cfbbef12e3025246f --- /dev/null +++ b/sites/all/modules/views/tests/styles/views_plugin_style_base.test @@ -0,0 +1,33 @@ +<?php + +/** + * @file + * Definition of ViewsPluginStyleTestBase. + */ + +/** + * Provides a base foundation for testing style plugins. + */ +abstract class ViewsPluginStyleTestBase extends ViewsSqlTest { + + /** + * Stores the SimpleXML representation of the output. + * + * @var SimpleXMLElement + */ + protected $elements; + + /** + * Stores a view output in the elements. + */ + function storeViewPreview($output) { + $htmlDom = new DOMDocument(); + @$htmlDom->loadHTML($output); + if ($htmlDom) { + // It's much easier to work with simplexml than DOM, luckily enough + // we can just simply import our DOM tree. + $this->elements = simplexml_import_dom($htmlDom); + } + } + +} diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test b/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test index 9a53bfcf2e8c79b6a39630438857067e41f7e6a2..dd4eca0b37ea1f9e81a002eb6bfb8de639de4879 100644 --- a/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test +++ b/sites/all/modules/views/tests/styles/views_plugin_style_jump_menu.test @@ -9,6 +9,14 @@ * Tests jump menu style functionality. */ class viewsPluginStyleJumpMenuTest extends ViewsSqlTest { + + /** + * Stores all created nodes. + * + * @var array + */ + var $nodes; + public static function getInfo() { return array( 'name' => 'Jump menu', diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test b/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test new file mode 100644 index 0000000000000000000000000000000000000000..5785075b2ad78c91a74203446f291b416dd2cb58 --- /dev/null +++ b/sites/all/modules/views/tests/styles/views_plugin_style_mapping.test @@ -0,0 +1,144 @@ +<?php + +/** + * @file + * Definition of ViewsPluginStyleMappingTest. + */ + +/** + * Tests the default/mapping row style. + */ +class ViewsPluginStyleMappingTest extends ViewsPluginStyleTestBase { + + public static function getInfo() { + return array( + 'name' => 'Style: Mapping', + 'description' => 'Test mapping style functionality.', + 'group' => 'Views Plugins', + ); + } + + public function setUp() { + parent::setUp(); + + // Reset the plugin data. + views_fetch_plugin_data(NULL, NULL, TRUE); + } + + protected function viewsPlugins() { + return array( + 'style' => array( + 'test_mapping' => array( + 'title' => t('Field mapping'), + 'help' => t('Maps specific fields to specific purposes.'), + 'handler' => 'views_test_plugin_style_test_mapping', + 'path' => drupal_get_path('module', 'views_test') . '/test_plugins', + 'theme' => 'views_view_mapping_test', + 'theme path' => drupal_get_path('module', 'views_test'), + 'theme file' => 'views_test.module', + 'uses row plugin' => FALSE, + 'uses fields' => TRUE, + 'uses options' => TRUE, + 'uses grouping' => FALSE, + 'type' => 'normal', + ), + ), + ); + } + + /** + * Overrides ViewsTestCase::getBasicView(). + */ + protected function getBasicView() { + $view = parent::getBasicView(); + $view->display['default']->handler->override_option('style_plugin', 'test_mapping'); + $view->display['default']->handler->override_option('style_options', array( + 'mapping' => array( + 'name_field' => 'name', + 'numeric_field' => array( + 'age', + ), + 'title_field' => 'name', + 'toggle_numeric_field' => TRUE, + 'toggle_title_field' => TRUE, + ), + )); + $view->display['default']->handler->override_option('fields', array( + 'age' => array( + 'id' => 'age', + 'table' => 'views_test', + 'field' => 'age', + 'relationship' => 'none', + ), + 'name' => array( + 'id' => 'name', + 'table' => 'views_test', + 'field' => 'name', + 'relationship' => 'none', + ), + 'job' => array( + 'id' => 'job', + 'table' => 'views_test', + 'field' => 'job', + 'relationship' => 'none', + ), + )); + return $view; + } + + /** + * Verifies that the fields were mapped correctly. + */ + public function testMappedOutput() { + $view = $this->getBasicView(); + $output = $this->mappedOutputHelper($view); + $this->assertTrue(strpos($output, 'job') === FALSE, 'The job field is added to the view but not in the mapping.'); + + $view = $this->getBasicView(); + $view->display['default']->display_options['style_options']['mapping']['name_field'] = 'job'; + $output = $this->mappedOutputHelper($view); + $this->assertTrue(strpos($output, 'job') !== FALSE, 'The job field is added to the view and is in the mapping.'); + } + + /** + * Tests the mapping of fields. + * + * @param view $view + * The view to test. + * + * @return string + * The view rendered as HTML. + */ + protected function mappedOutputHelper($view) { + $rendered_output = $view->preview(); + $this->storeViewPreview($rendered_output); + $rows = $this->elements->body->div->div->div; + $data_set = $this->dataSet(); + + $count = 0; + foreach ($rows as $row) { + $attributes = $row->attributes(); + $class = (string) $attributes['class'][0]; + $this->assertTrue(strpos($class, 'views-row-mapping-test') !== FALSE, 'Make sure that each row has the correct CSS class.'); + + foreach ($row->div as $field) { + // Split up the field-level class, the first part is the mapping name + // and the second is the field ID. + $field_attributes = $field->attributes(); + $name = strtok((string) $field_attributes['class'][0], '-'); + $field_id = strtok('-'); + + // The expected result is the mapping name and the field value, + // separated by ':'. + $expected_result = $name . ':' . $data_set[$count][$field_id]; + $actual_result = (string) $field; + $this->assertIdentical($expected_result, $actual_result, format_string('The fields were mapped successfully: %name => %field_id', array('%name' => $name, '%field_id' => $field_id))); + } + + $count++; + } + + return $rendered_output; + } + +} diff --git a/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test b/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test index 20b7b4d8079929beb0488bbeef5b09ade6ad406d..0c0e882547a70d6696fc0fa40ebd2b2c4ab9aceb 100644 --- a/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test +++ b/sites/all/modules/views/tests/styles/views_plugin_style_unformatted.test @@ -8,14 +8,7 @@ /** * Tests the default/unformatted row style. */ -class ViewsPluginStyleUnformattedTestCase extends ViewsSqlTest { - - /** - * Stores all created nodes. - * - * @var array - */ - var $nodes; +class ViewsPluginStyleUnformattedTestCase extends ViewsPluginStyleTestBase { public static function getInfo() { return array( @@ -25,19 +18,6 @@ class ViewsPluginStyleUnformattedTestCase extends ViewsSqlTest { ); } - /** - * Stores a view output in the elements. - */ - function storeViewPreview($output) { - $htmlDom = new DOMDocument(); - @$htmlDom->loadHTML($output); - if ($htmlDom) { - // It's much easier to work with simplexml than DOM, luckily enough - // we can just simply import our DOM tree. - $this->elements = simplexml_import_dom($htmlDom); - } - } - /** * Take sure that the default css classes works as expected. */ diff --git a/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc b/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc new file mode 100644 index 0000000000000000000000000000000000000000..b92678734686912aadfccd77865695907ed3c5ae --- /dev/null +++ b/sites/all/modules/views/tests/test_plugins/views_test_plugin_style_test_mapping.inc @@ -0,0 +1,52 @@ +<?php + +/** + * @file + * Definition of views_test_plugin_style_test_mapping. + */ + +/** + * Provides a test mapping style plugin. + */ +class views_test_plugin_style_test_mapping extends views_plugin_style_mapping { + + /** + * Overrides views_plugin_style_mapping::define_mapping(). + */ + protected function define_mapping() { + return array( + 'title_field' => array( + '#title' => t('Title field'), + '#description' => t('Choose the field with the custom title.'), + '#toggle' => TRUE, + '#required' => TRUE, + ), + 'name_field' => array( + '#title' => t('Name field'), + '#description' => t('Choose the field with the custom name.'), + ), + 'numeric_field' => array( + '#title' => t('Numeric field'), + '#description' => t('Select one or more numeric fields.'), + '#multiple' => TRUE, + '#toggle' => TRUE, + '#filter' => 'filter_numeric_fields', + '#required' => TRUE, + ), + ); + } + + /** + * Restricts the allowed fields to only numeric fields. + * + * @param array $fields + * An array of field labels, keyed by the field ID. + */ + protected function filter_numeric_fields(&$fields) { + foreach ($this->view->field as $id => $field) { + if (!($field instanceof views_handler_field_numeric)) { + unset($fields[$id]); + } + } + } +} diff --git a/sites/all/modules/views/tests/views_test.info b/sites/all/modules/views/tests/views_test.info index c41ad547de6f6a38fdc82aca3fe5e90a79759400..23bd884fdd4f6730c25dfa3d25debbf5aec037fa 100644 --- a/sites/all/modules/views/tests/views_test.info +++ b/sites/all/modules/views/tests/views_test.info @@ -5,9 +5,9 @@ core = 7.x dependencies[] = views hidden = TRUE -; Information added by drupal.org packaging script on 2012-08-24 -version = "7.x-3.5" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1345829394" +datestamp = "1365499236" diff --git a/sites/all/modules/views/tests/views_test.module b/sites/all/modules/views/tests/views_test.module index f6026b8bc52fe78eb6706084401e32771f4acfee..7adcf4306c7a641abc35de0c1b63308bf68e0a48 100644 --- a/sites/all/modules/views/tests/views_test.module +++ b/sites/all/modules/views/tests/views_test.module @@ -59,3 +59,59 @@ function views_test_views_pre_render(&$view) { $view->pre_render_called = TRUE; } } + +/** + * Implements hook_preprocess_HOOK() for theme_views_view_mapping_test(). + */ +function template_preprocess_views_view_mapping_test(&$variables) { + $variables['element'] = array(); + + foreach ($variables['rows'] as $delta => $row) { + $fields = array(); + foreach ($variables['options']['mapping'] as $type => $field_names) { + if (!is_array($field_names)) { + $field_names = array($field_names); + } + foreach ($field_names as $field_name) { + if ($value = $variables['view']->style_plugin->get_field($delta, $field_name)) { + $fields[$type . '-' . $field_name] = $type . ':' . $value; + } + } + } + + // If there are no fields in this row, skip to the next one. + if (empty($fields)) { + continue; + } + + // Build a container for the row. + $variables['element'][$delta] = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array( + 'views-row-mapping-test', + ), + ), + ); + + // Add each field to the row. + foreach ($fields as $key => $render) { + $variables['element'][$delta][$key] = array( + '#children' => $render, + '#type' => 'container', + '#attributes' => array( + 'class' => array( + $key, + ), + ), + ); + } + } +} + +/** + * Returns HTML for the Mapping Test style. + */ +function theme_views_view_mapping_test($variables) { + return drupal_render($variables['element']); +} diff --git a/sites/all/modules/views/tests/views_translatable.test b/sites/all/modules/views/tests/views_translatable.test index 0cb27e4004cb7ec07b43f76095de39db2bc65c04..983a97e00695088f17952b7ff989bcbdf6981376 100644 --- a/sites/all/modules/views/tests/views_translatable.test +++ b/sites/all/modules/views/tests/views_translatable.test @@ -114,7 +114,6 @@ class ViewsTranslatableTest extends ViewsSqlTest { 'more1' => array('use_more_text'), 'Reset1' => array('exposed_form', 'reset_button_label'), 'Offset1' => array('pager', 'expose', 'offset_label'), - 'Master1' => array('title'), 'title1' => array('title'), 'Tag first1' => array('pager', 'tags', 'first'), 'Tag prev1' => array('pager', 'tags', 'previous'), @@ -124,9 +123,23 @@ class ViewsTranslatableTest extends ViewsSqlTest { 'fieldlabel1' => array('field', 'node', 'nid', 'label'), 'filterlabel1' => array('filter', 'node', 'nid', 'expose', 'label'), '- All -' => array('pager', 'expose', 'items_per_page_options_all_label'), + 'Header1' => array('header', 'views', 'area', 'content'), + ); + + $formats = array( + 'Header1' => 'filtered_html', ); + foreach ($translatables as $translatable) { $this->assertEqual($translatable['keys'], $this->string_keys[$translatable['value']]); + + // Make sure the format is correct. + if (isset($formats[$translatable['value']])) { + $this->assertEqual($translatable['format'], $formats[$translatable['value']]); + } + else { + $this->assertNull($translatable['format'], 'No format defined'); + } } } } @@ -169,6 +182,14 @@ class ViewsTranslatableTest extends ViewsSqlTest { $handler->display->display_options['pager']['options']['expose']['offset_label'] = 'Offset1'; $handler->display->display_options['style_plugin'] = 'default'; $handler->display->display_options['row_plugin'] = 'fields'; + /* Global: Header */ + $handler->display->display_options['header']['area']['id'] = 'area'; + $handler->display->display_options['header']['area']['table'] = 'views'; + $handler->display->display_options['header']['area']['field'] = 'area'; + $handler->display->display_options['header']['area']['empty'] = FALSE; + $handler->display->display_options['header']['area']['content'] = 'Header1'; + $handler->display->display_options['header']['area']['format'] = 'filtered_html'; + $handler->display->display_options['header']['area']['tokenize'] = 0; /* Field: Content: Nid */ $handler->display->display_options['fields']['nid']['id'] = 'nid'; $handler->display->display_options['fields']['nid']['table'] = 'node'; diff --git a/sites/all/modules/views/theme/theme.inc b/sites/all/modules/views/theme/theme.inc index 52598d5eb046d9a578fd290c8d8f658fd98c6500..e7f7a158946b13dbdac58bc2bd613bc44ff085a1 100644 --- a/sites/all/modules/views/theme/theme.inc +++ b/sites/all/modules/views/theme/theme.inc @@ -649,8 +649,17 @@ function template_preprocess_views_view_table(&$vars) { } $vars['classes_array'][] = 'cols-'. count($vars['header']); + // Add the summary to the list if set. if (!empty($handler->options['summary'])) { - $vars['attributes_array'] = array('summary' => $handler->options['summary']); + $vars['attributes_array'] = array('summary' => filter_xss_admin($handler->options['summary'])); + } + + // Add the caption to the list if set. + if (!empty($handler->options['caption'])) { + $vars['caption'] = filter_xss_admin($handler->options['caption']); + } + else { + $vars['caption'] = ''; } } @@ -762,8 +771,18 @@ function template_preprocess_views_view_grid(&$vars) { } $vars['rows'] = $rows; $vars['class'] = 'views-view-grid cols-' . $columns; + + // Add the summary to the list if set. if (!empty($handler->options['summary'])) { - $vars['attributes_array'] = array('summary' => $handler->options['summary']); + $vars['attributes_array'] = array('summary' => filter_xss_admin($handler->options['summary'])); + } + + // Add the caption to the list if set. + if (!empty($handler->options['caption'])) { + $vars['caption'] = filter_xss_admin($handler->options['caption']); + } + else { + $vars['caption'] = ''; } } @@ -1021,19 +1040,13 @@ function theme_views_mini_pager($vars) { $tags = $vars['tags']; $element = $vars['element']; $parameters = $vars['parameters']; - $quantity = $vars['quantity']; - // Calculate various markers within this pager piece: - // Middle is used to "center" pages around the current page. - $pager_middle = ceil($quantity / 2); // current is the page we are currently paged to $pager_current = $pager_page_array[$element] + 1; // max is the maximum page number $pager_max = $pager_total[$element]; // End of marker calculations. - - if ($pager_total[$element] > 1) { $li_previous = theme('pager_previous', diff --git a/sites/all/modules/views/theme/views-view-grid.tpl.php b/sites/all/modules/views/theme/views-view-grid.tpl.php index cd8d39e3d709338c00cbbc2df495e543b1bf4d3f..09f807a7c281819961edc3240fcfa9bc9748c2e4 100644 --- a/sites/all/modules/views/theme/views-view-grid.tpl.php +++ b/sites/all/modules/views/theme/views-view-grid.tpl.php @@ -14,6 +14,10 @@ <h3><?php print $title; ?></h3> <?php endif; ?> <table class="<?php print $class; ?>"<?php print $attributes; ?>> + <?php if (!empty($caption)) : ?> + <caption><?php print $caption; ?></caption> + <?php endif; ?> + <tbody> <?php foreach ($rows as $row_number => $columns): ?> <tr <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .'"'; } ?>> diff --git a/sites/all/modules/views/theme/views-view-table.tpl.php b/sites/all/modules/views/theme/views-view-table.tpl.php index a9abcd39a6d81a6b05a54b94935b086df3e524db..4fec9fa93acfa11ae3afb37ae2e86bcf9e31b93d 100644 --- a/sites/all/modules/views/theme/views-view-table.tpl.php +++ b/sites/all/modules/views/theme/views-view-table.tpl.php @@ -6,6 +6,7 @@ * * - $title : The title of this group of rows. May be empty. * - $header: An array of header labels keyed by field id. + * - $caption: The caption for this table. May be empty. * - $header_classes: An array of header classes keyed by field id. * - $fields: An array of CSS IDs to use for each field id. * - $classes: A class or classes to apply to the table, based on settings. @@ -19,8 +20,8 @@ */ ?> <table <?php if ($classes) { print 'class="'. $classes . '" '; } ?><?php print $attributes; ?>> - <?php if (!empty($title)) : ?> - <caption><?php print $title; ?></caption> + <?php if (!empty($title) || !empty($caption)) : ?> + <caption><?php print $caption . $title; ?></caption> <?php endif; ?> <?php if (!empty($header)) : ?> <thead> diff --git a/sites/all/modules/views/theme/views-view-unformatted.tpl.php b/sites/all/modules/views/theme/views-view-unformatted.tpl.php index 4f4558e09335a3e815b9d4dba7c3d6c69e6ed31b..f1cccb8885b36ec4844a5d2724b53b6ba2d422ba 100644 --- a/sites/all/modules/views/theme/views-view-unformatted.tpl.php +++ b/sites/all/modules/views/theme/views-view-unformatted.tpl.php @@ -11,7 +11,7 @@ <h3><?php print $title; ?></h3> <?php endif; ?> <?php foreach ($rows as $id => $row): ?> - <div <?php if ($classes_array[$id]) { print 'class="' . $classes_array[$id] .'"'; } ?>> + <div<?php if ($classes_array[$id]) { print ' class="' . $classes_array[$id] .'"'; } ?>> <?php print $row; ?> </div> <?php endforeach; ?> diff --git a/sites/all/modules/views/views.api.php b/sites/all/modules/views/views.api.php index f41c565b31d9adbb2b1152002d94dae70572a534..ba9b326f9601172fbbced7f6565be036ee675080 100644 --- a/sites/all/modules/views/views.api.php +++ b/sites/all/modules/views/views.api.php @@ -482,13 +482,15 @@ function hook_views_data_alter(&$data) { $data['users']['example_field'] = array( 'title' => t('Example field'), 'help' => t('Some example content that references a user'), - 'handler' => 'hook_handlers_field_example_field', + 'field' => array( + 'handler' => 'modulename_handler_field_example_field', + ), ); // This example changes the handler of the node title field. // In this handler you could do stuff, like preview of the node when clicking // the node title. - $data['node']['title']['handler'] = 'modulename_handlers_field_node_title'; + $data['node']['title']['field']['handler'] = 'modulename_handler_field_node_title'; // This example adds a relationship to table {foo}, so that 'foo' views can // add this table using a relationship. Because we don't want to write over @@ -663,10 +665,10 @@ function hook_views_api() { * This hook allows modules to provide their own views which can either be used * as-is or as a "starter" for users to build from. * - * This hook should be placed in MODULENAME.views.inc and it will be - * auto-loaded. MODULENAME.views.inc must be in the directory specified by the - * 'path' key returned by MODULENAME_views_api(), or the same directory as the - * .module file, if 'path' is unspecified. + * This hook should be placed in MODULENAME.views_default.inc and it will be + * auto-loaded. MODULENAME.views_default.inc must be in the directory specified + * by the 'path' key returned by MODULENAME_views_api(), or the same directory + * as the .module file, if 'path' is unspecified. * * The $view->disabled boolean flag indicates whether the View should be * enabled (FALSE) or disabled (TRUE) by default. diff --git a/sites/all/modules/views/views.info b/sites/all/modules/views/views.info index 3bd747089ea40cb22ee166aa67904537acd73c66..50d814829ac0b5d2b6ff9ab96f6913835442b4b8 100644 --- a/sites/all/modules/views/views.info +++ b/sites/all/modules/views/views.info @@ -171,6 +171,9 @@ files[] = modules/taxonomy/views_handler_filter_vocabulary_machine_name.inc files[] = modules/taxonomy/views_handler_relationship_node_term_data.inc files[] = modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc files[] = modules/taxonomy/views_plugin_argument_default_taxonomy_tid.inc +files[] = modules/tracker/views_handler_argument_tracker_comment_user_uid.inc +files[] = modules/tracker/views_handler_filter_tracker_comment_user_uid.inc +files[] = modules/tracker/views_handler_filter_tracker_boolean_operator.inc files[] = modules/system/views_handler_filter_system_type.inc files[] = modules/translation/views_handler_argument_node_tnid.inc files[] = modules/translation/views_handler_field_node_link_translate.inc @@ -242,6 +245,7 @@ files[] = plugins/views_plugin_style_default.inc files[] = plugins/views_plugin_style_grid.inc files[] = plugins/views_plugin_style_list.inc files[] = plugins/views_plugin_style_jump_menu.inc +files[] = plugins/views_plugin_style_mapping.inc files[] = plugins/views_plugin_style_rss.inc files[] = plugins/views_plugin_style_summary.inc files[] = plugins/views_plugin_style_summary_jump_menu.inc @@ -272,9 +276,12 @@ files[] = tests/handlers/views_handler_sort_date.test files[] = tests/handlers/views_handler_sort.test files[] = tests/test_plugins/views_test_plugin_access_test_dynamic.inc files[] = tests/test_plugins/views_test_plugin_access_test_static.inc +files[] = tests/test_plugins/views_test_plugin_style_test_mapping.inc files[] = tests/plugins/views_plugin_display.test files[] = tests/styles/views_plugin_style_jump_menu.test files[] = tests/styles/views_plugin_style.test +files[] = tests/styles/views_plugin_style_base.test +files[] = tests/styles/views_plugin_style_mapping.test files[] = tests/styles/views_plugin_style_unformatted.test files[] = tests/views_access.test files[] = tests/views_analyze.test @@ -295,6 +302,7 @@ files[] = tests/views_upgrade.test files[] = tests/views_test.views_default.inc files[] = tests/comment/views_handler_argument_comment_user_uid.test files[] = tests/comment/views_handler_filter_comment_user_uid.test +files[] = tests/node/views_node_revision_relations.test files[] = tests/taxonomy/views_handler_relationship_node_term_data.test files[] = tests/user/views_handler_field_user_name.test files[] = tests/user/views_user_argument_default.test @@ -304,9 +312,9 @@ files[] = tests/views_cache.test files[] = tests/views_view.test files[] = tests/views_ui.test -; Information added by drupal.org packaging script on 2012-08-24 -version = "7.x-3.5" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1345829394" +datestamp = "1365499236" diff --git a/sites/all/modules/views/views.module b/sites/all/modules/views/views.module index 3b9eff10b92f875557b85d237cfede36fc0043f1..fc427f49e33a415c8f50f68c3a33aa724e7b028a 100644 --- a/sites/all/modules/views/views.module +++ b/sites/all/modules/views/views.module @@ -69,7 +69,7 @@ function views_theme($existing, $type, $theme, $path) { // Our extra version of pager from pager.inc $hooks['views_mini_pager'] = $base + array( - 'variables' => array('tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()), + 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array()), 'pattern' => 'views_mini_pager__', ); @@ -341,10 +341,12 @@ function views_permission() { 'administer views' => array( 'title' => t('Administer views'), 'description' => t('Access the views administration pages.'), + 'restrict access' => TRUE, ), 'access all views' => array( 'title' => t('Bypass views access control'), 'description' => t('Bypass access control when accessing views.'), + 'restrict access' => TRUE, ), ); } @@ -373,7 +375,7 @@ function views_menu() { 'page callback' => 'views_ajax_autocomplete_user', 'theme callback' => 'ajax_base_page_theme', 'access callback' => 'user_access', - 'access arguments' => array('access content'), + 'access arguments' => array('access user profiles'), 'type' => MENU_CALLBACK, 'file' => 'includes/ajax.inc', ); @@ -2511,6 +2513,10 @@ if (!function_exists('system_views_api')) { function system_views_api() { return views_views_api(); } } +if (!function_exists('tracker_views_api')) { + function tracker_views_api() { return views_views_api(); } +} + if (!function_exists('taxonomy_views_api')) { function taxonomy_views_api() { return views_views_api(); } } diff --git a/sites/all/modules/views/views_ui.info b/sites/all/modules/views/views_ui.info index 74d9e16119a6ec643b968b9a7640e5ba7368c2e9..95ea014918c8f7f514b09984186194dac0d4185d 100644 --- a/sites/all/modules/views/views_ui.info +++ b/sites/all/modules/views/views_ui.info @@ -7,9 +7,9 @@ dependencies[] = views files[] = views_ui.module files[] = plugins/views_wizard/views_ui_base_views_wizard.class.php -; Information added by drupal.org packaging script on 2012-08-24 -version = "7.x-3.5" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1345829394" +datestamp = "1365499236" diff --git a/sites/all/modules/views/views_ui.module b/sites/all/modules/views/views_ui.module index 8182714a3f56434705d37640d3f5cda7fdf1807b..5366f778890de616fcb28d64b45fc737a693d2a2 100644 --- a/sites/all/modules/views/views_ui.module +++ b/sites/all/modules/views/views_ui.module @@ -845,7 +845,8 @@ function _views_ui_get_displays_list($view) { function views_ui_library_alter(&$libraries, $module) { if ($module == 'system' && isset($libraries['ui.dialog'])) { - if (version_compare($libraries['ui.dialog']['version'], '1.7.2', '>=')) { + // Only apply the fix, if we don't have an up to date jQueryUI version. + if (version_compare($libraries['ui.dialog']['version'], '1.7.2', '>=') && version_compare($libraries['ui.dialog']['version'], '1.10.0', '<')) { $libraries['ui.dialog']['js'][drupal_get_path('module', 'views') . '/js/jquery.ui.dialog.patch.js'] = array(); } } diff --git a/sites/all/settings.php.sample b/sites/all/settings.php.sample new file mode 100644 index 0000000000000000000000000000000000000000..67cc86f2cc682250025a03376d1725dd5caf7ea3 --- /dev/null +++ b/sites/all/settings.php.sample @@ -0,0 +1,54 @@ +<?php + +// Enable or disable maintenance mode. +// $conf['maintenance_mode'] = 0; + +// Tell drupal its ok to let varnish cache pages. +$conf['page_cache_invoke_hooks'] = FALSE; + +$conf['unl_clean_file_url'] = TRUE; + +// Enable page caching with a 1 day lifetime. +$conf['cache'] = true; +$conf['page_cache_maximum_age'] = 86400; +$conf['preprocess_css'] = 1; +$conf['preprocess_js'] = 1; + +// If the UNL CAS cookie is set, we don't want pages cached. +if (isset($_COOKIE['unl_sso']) && request_path() != 'admin/config/development/performance') { + $conf['cache'] = FALSE; +} + +// Configure the memcache cache backend. +$conf['cache_backends'][] = 'sites/all/modules/memcache/memcache.inc'; +$conf['memcache_servers'] = array('127.0.0.1:11211' => 'default'); +$conf['memcache_key_prefix'] = 'host.example.com/path' . conf_path(); + +// Configure the varnish cache backend. +$conf['cache_backends'][] = 'sites/all/modules/varnish/varnish.cache.inc'; +$conf['varnish_control_terminal'] = '127.0.0.1:6082'; +$conf['varnish_control_key'] = 'password'; +$conf['varnish_cache_clear'] = 1; + +// Set the default cache backend. +$conf['cache_default_class'] = 'MemCacheDrupal'; +#$conf['cache_class_cache_page'] = 'VarnishCache'; + +$databases = array ( + 'default' => + array ( + 'default' => + array ( + 'driver' => 'mysql', + 'database' => 'drupal', + 'username' => 'drupal', + 'password' => 'password', + 'host' => 'localhost', + 'port' => '', + 'prefix' => 'drupal_', + ), + ), +); + +$conf['reverse_proxy'] = TRUE; +$conf['reverse_proxy_addresses'] = array('127.0.0.1'); diff --git a/sites/all/themes/unl_wdn/lib/.pear2registry b/sites/all/themes/unl_wdn/lib/.pear2registry index b4b979dfe7fbcdd47f323cc982d05403dca9ca01..4bfb2b8bddf4d41abd319514a159887f7adc7970 100644 Binary files a/sites/all/themes/unl_wdn/lib/.pear2registry and b/sites/all/themes/unl_wdn/lib/.pear2registry differ diff --git a/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml b/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml similarity index 92% rename from sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml rename to sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml index 817ae5a5557fd79d71cf031af5e55af50573b908..894b761f0972bda69fd35554ebe3513999cb2108 100644 --- a/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml +++ b/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml @@ -13,10 +13,10 @@ This package generates php class files (objects) from Dreamweaver template files <email>brett.bieber@gmail.com</email> <active>yes</active> </lead> - <date>2012-10-02</date> - <time>11:50:32</time> + <date>2013-05-24</date> + <time>10:02:57</time> <version> - <release>0.8.0</release> + <release>0.9.0</release> <api>0.7.1</api> </version> <stability> @@ -24,22 +24,21 @@ This package generates php class files (objects) from Dreamweaver template files <api>beta</api> </stability> <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD</license> - <notes>Feature Release: - -* Dreamweaver templates using params are now properly supported -* Minor API changes -** Add UNL_DWT::getTemplateFile() which is used during the rendering process + <notes>Feature Release +* Add support for immedaitely rendering a scanned DWT [saltybeagle] +Bug Fixes + * Prevent greedy matching of template regions [spam38] </notes> <contents> <dir name="/"> - <file role="php" name="php/UNL/DWT/Scanner.php" md5sum="042fb529bbc104b3eb742e8946e0961e"/> + <file role="php" name="php/UNL/DWT/Scanner.php" md5sum="276e82e5db587c9c18a100e1e877082e"/> <file role="php" name="php/UNL/DWT/Region.php" md5sum="858136d43bf29868dca876783e51d196"/> <file role="php" name="php/UNL/DWT/Generator.php" md5sum="a3b933a0d7f8d81f72836bb2c5fb6914"/> <file role="php" name="php/UNL/DWT/Exception.php" md5sum="5b99b44fbfde7349c6b9e6d9be78e9dc"/> <file role="php" name="php/UNL/DWT/createTemplates.php" md5sum="9089565d275b52e0cd65c52edd50ef18"/> - <file role="php" name="php/UNL/DWT.php" md5sum="0889c13bcecd0ebf5e517367345772f9"/> - <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/scanner_example.php" md5sum="f0807792c3c0c4a0524f9186e69e0be7"/> + <file role="php" name="php/UNL/DWT.php" md5sum="ca9d707c266ad9150e39d1a9a60c5643"/> + <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/scanner_example.php" md5sum="2d16f0e62c4227aa28108bf78d74156a"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/Template_style1.tpl" md5sum="b524ef4684be7dba47ed8c245577347a"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/Template_style1.php" md5sum="096998b112a1e27bddc6c171380d590e"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/template_style1.dwt" md5sum="0d5a4f5ca86e9c2a3c0050f39acbb034"/> diff --git a/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php b/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php index 05d117dd0d28ebdaf0a65867d8bdf24e6213fa92..00184321118c0efda8e8b714c02ca742b04efb3c 100644 --- a/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php +++ b/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php @@ -1,6 +1,8 @@ <?php set_include_path(dirname(__DIR__).'/../src'); +error_reporting(E_ALL); +ini_set('display_errors', true); require_once 'UNL/DWT/Scanner.php'; @@ -8,5 +10,10 @@ $file = file_get_contents(dirname(__FILE__).'/basic/'.'template_style1.dwt'); $scanned = new UNL_DWT_Scanner($file); -echo $scanned->leftnav; -echo $scanned->content; +// Modify the scanned content +$scanned->content .= '<h3>Scanned content from the left nav:</h3>'; + +// Also, access the content that was scanned in +$scanned->content .= '<pre>'.$scanned->leftnav.'</pre>'; + +echo $scanned; diff --git a/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php b/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php index 3e7bc3479847741f476ad94dddde4ab6ac573f38..696e79ad1244dde3c1bfcf2845b88575a22195c4 100644 --- a/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php +++ b/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php @@ -149,17 +149,23 @@ class UNL_DWT foreach ($regions as $region => $value) { /* Replace the region with the replacement text */ + $startMarker = $this->getRegionBeginMarker(self::TEMPLATE_TOKEN, $region); + $endMarker = $this->getRegionEndMarker(self::TEMPLATE_TOKEN); $p = str_replace( - self::strBetween($this->getRegionBeginMarker(self::TEMPLATE_TOKEN, $region), - $this->getRegionEndMarker(self::TEMPLATE_TOKEN), $p), - $value, $p, $count + self::strBetween($startMarker, $endMarker, $p, true), + $startMarker . $value . $endMarker, + $p, + $count ); if (!$count) { + $startMarker = $this->getRegionBeginMarker(self::INSTANCE_TOKEN, $region); + $endMarker = $this->getRegionEndMarker(self::INSTANCE_TOKEN); $p = str_replace( - self::strBetween($this->getRegionBeginMarker(self::INSTANCE_TOKEN, $region), - $this->getRegionEndMarker(self::INSTANCE_TOKEN), $p), - $value, $p, $count + self::strBetween($startMarker, $endMarker, $p, true), + $startMarker . $value . $endMarker, + $p, + $count ); } @@ -299,16 +305,16 @@ class UNL_DWT * * @return string */ - static function strBetween($start, $end, $p) + static function strBetween($start, $end, $p, $inclusive = false) { if (!empty($start) && strpos($p, $start) !== false) { - $p = substr($p, strpos($p, $start)+strlen($start)); + $p = substr($p, strpos($p, $start)+($inclusive ? 0 : strlen($start))); } else { return ''; } if (strpos($p, $end) !==false) { - $p = substr($p, 0, strpos($p, $end)); + $p = substr($p, 0, strpos($p, $end)+($inclusive ? strlen($end) : 0)); } else { return ''; } diff --git a/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php b/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php index c6e79eadd2fb111ad0aee938573aefced20b61be..e4a2a29bfa2816146e046a5e1a6861d7e0e8a722 100644 --- a/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php +++ b/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php @@ -1,6 +1,6 @@ <?php /** - * Handles scanning a dwt file for regions. + * Handles scanning a dwt file for regions and rendering. * * PHP version 5 * @@ -12,6 +12,7 @@ * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License * @link http://pear.unl.edu/package/UNL_DWT */ +require_once 'UNL/DWT.php'; require_once 'UNL/DWT/Region.php'; /** @@ -23,7 +24,7 @@ require_once 'UNL/DWT/Region.php'; * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License * @link http://pear.unl.edu/package/UNL_DWT */ -class UNL_DWT_Scanner +class UNL_DWT_Scanner extends UNL_DWT { protected $_regions; @@ -34,9 +35,20 @@ class UNL_DWT_Scanner */ function __construct($dwt) { + $this->__template = $dwt; $this->scanRegions($dwt); } + /** + * Return the template markup + * + * @return string + */ + function getTemplateFile() + { + return $this->__template; + } + function scanRegions($dwt) { $this->_regions[] = array(); @@ -133,4 +145,14 @@ class UNL_DWT_Scanner return null; } + /** + * Allow directly rendering + * + * @return string + */ + function __toString() + { + return $this->toHtml(); + } + } diff --git a/sites/all/themes/unl_wdn/template.php b/sites/all/themes/unl_wdn/template.php index 39d6f1c095837ba5d6f72fde2e71128025a6889e..2e623a2ce1091f4f583f0b852b06cd59c9873dbd 100644 --- a/sites/all/themes/unl_wdn/template.php +++ b/sites/all/themes/unl_wdn/template.php @@ -285,20 +285,11 @@ function unl_wdn_preprocess_username(&$vars) { */ function unl_wdn_username_alter(&$name, $account) { if ($account->uid) { - // Drupal does not support "display names" so convert the user name (jdoe2 to Jane Doe) using UNL Directory service. - $context = stream_context_create(array( - 'http' => array('timeout' => 1) - )); - if (function_exists('unl_url_get_contents')) { - $result = json_decode(unl_url_get_contents('http://directory.unl.edu/service.php?format=json&uid='.$name, $context)); - } - else { - $result = json_decode(file_get_contents('http://directory.unl.edu/service.php?format=json&uid='.$name, 0, $context)); - } - if (!empty($result) && $result->sn) { - $zero = '0'; - $firstname = ($result->eduPersonNickname ? $result->eduPersonNickname->$zero : $result->givenName->$zero); - $name = $firstname . ' ' . $result->sn->$zero; + // If the CAS module is enabled, we should have their full name. + $user = user_load_by_name($name); + if ($user && isset($user->data['unl']['fullName'])) { + $name = $user->data['unl']['fullName']; + return; } } } diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 2b207f224138f6a0a3c6bc9ece062eedb2366796..40f552e1df6f488239906541e7149fc84815feb2 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -147,7 +147,7 @@ * 'authmap' => 'shared_', * ), * @endcode - * You can also use a reference to a schema/database as a prefix. This maybe + * You can also use a reference to a schema/database as a prefix. This may be * useful if your Drupal installation exists in a schema that is not the default * or you want to access several databases from the same code base at the same * time. @@ -435,7 +435,7 @@ ini_set('session.cookie_lifetime', 2000000); /** * String overrides: * - * To override specific strings on your site with or without enabling locale + * To override specific strings on your site with or without enabling the Locale * module, add an entry to this list. This functionality allows you to change * a small number of your site's default English language interface strings. * diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info index 0e0c6d8106cbdc8f30f370006c33acd1df8c147c..f4e452785167f76aef3a38b242ad06da7d426773 100644 --- a/themes/bartik/bartik.info +++ b/themes/bartik/bartik.info @@ -34,8 +34,8 @@ regions[footer] = Footer settings[shortcut_module_link] = 0 -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/themes/garland/garland.info b/themes/garland/garland.info index adc546fb6f990a6fe73e068067fed27e0dbd7d79..62efe64037e092ba00830ac70e3150d8b6348271 100644 --- a/themes/garland/garland.info +++ b/themes/garland/garland.info @@ -7,8 +7,8 @@ stylesheets[all][] = style.css stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/themes/seven/seven.info b/themes/seven/seven.info index 76e4a190959bcc52b445601515c4c2f3b8ad5684..9f68e4b8c264bbab86a289cdf23271f02e426686 100644 --- a/themes/seven/seven.info +++ b/themes/seven/seven.info @@ -13,8 +13,8 @@ regions[page_bottom] = Page bottom regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/themes/seven/template.php b/themes/seven/template.php index 437e9a79f006752a9197ae16ecc44b3573396b9b..5c086fa4971dabf60a6d837dc793858a5f009763 100644 --- a/themes/seven/template.php +++ b/themes/seven/template.php @@ -104,12 +104,15 @@ function seven_css_alter(&$css) { // Use Seven's vertical tabs style instead of the default one. if (isset($css['misc/vertical-tabs.css'])) { $css['misc/vertical-tabs.css']['data'] = drupal_get_path('theme', 'seven') . '/vertical-tabs.css'; + $css['misc/vertical-tabs.css']['type'] = 'file'; } if (isset($css['misc/vertical-tabs-rtl.css'])) { $css['misc/vertical-tabs-rtl.css']['data'] = drupal_get_path('theme', 'seven') . '/vertical-tabs-rtl.css'; + $css['misc/vertical-tabs-rtl.css']['type'] = 'file'; } // Use Seven's jQuery UI theme style instead of the default one. if (isset($css['misc/ui/jquery.ui.theme.css'])) { $css['misc/ui/jquery.ui.theme.css']['data'] = drupal_get_path('theme', 'seven') . '/jquery.ui.theme.css'; + $css['misc/ui/jquery.ui.theme.css']['type'] = 'file'; } } diff --git a/themes/stark/stark.info b/themes/stark/stark.info index 3ad46b80c1893e8951530020a5f38157b6f17169..b9bc5cfe3c28b73bf72e8280b23b6fe67b13f59f 100644 --- a/themes/stark/stark.info +++ b/themes/stark/stark.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x stylesheets[all][] = layout.css -; Information added by drupal.org packaging script on 2013-03-07 -version = "7.21" +; Information added by drupal.org packaging script on 2013-04-03 +version = "7.22" project = "drupal" -datestamp = "1362616996" +datestamp = "1365027012" diff --git a/update.php b/update.php index d46a931bd1c911506bb35bf826a3523e47371074..d482de5f24ae42806bd00842af34c69c6348c75f 100644 --- a/update.php +++ b/update.php @@ -1,7 +1,7 @@ <?php /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); @@ -27,6 +27,9 @@ define('DRUPAL_ROOT', getcwd()); */ define('MAINTENANCE_MODE', 'update'); +/** + * Renders a form with a list of available database updates. + */ function update_selection_page() { drupal_set_title('Drupal database update'); $elements = drupal_get_form('update_script_selection_form'); @@ -37,6 +40,9 @@ function update_selection_page() { return $output; } +/** + * Form constructor for the list of available database module updates. + */ function update_script_selection_form($form, &$form_state) { $count = 0; $incompatible_count = 0; @@ -141,9 +147,10 @@ function update_script_selection_form($form, &$form_state) { return $form; } +/** + * Provides links to the homepage and administration pages. + */ function update_helpful_links() { - // NOTE: we can't use l() here because the URL would point to - // 'update.php?q=admin'. $links[] = '<a href="' . base_path() . '">Front page</a>'; if (user_access('access administration pages')) { $links[] = '<a href="' . base_path() . '?q=admin">Administration pages</a>'; @@ -151,6 +158,9 @@ function update_helpful_links() { return $links; } +/** + * Displays results of the update script with any accompanying errors. + */ function update_results_page() { drupal_set_title('Drupal database update'); $links = update_helpful_links(); @@ -231,6 +241,15 @@ function update_results_page() { return $output; } +/** + * Provides an overview of the Drupal database update. + * + * This page provides cautionary suggestions that should happen before + * proceeding with the update to ensure data integrity. + * + * @return + * Rendered HTML form. + */ function update_info_page() { // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); @@ -256,6 +275,12 @@ function update_info_page() { return $output; } +/** + * Renders a 403 access denied page for update.php. + * + * @return + * Rendered HTML warning with 403 status. + */ function update_access_denied_page() { drupal_add_http_header('Status', '403 Forbidden'); watchdog('access denied', 'update.php', NULL, WATCHDOG_WARNING); @@ -294,7 +319,7 @@ function update_access_allowed() { } /** - * Add the update task list to the current page. + * Adds the update task list to the current page. */ function update_task_list($active = NULL) { // Default list of tasks. @@ -310,8 +335,7 @@ function update_task_list($active = NULL) { } /** - * Returns (and optionally stores) extra requirements that only apply during - * particular parts of the update.php process. + * Returns and stores extra requirements that apply during the update process. */ function update_extra_requirements($requirements = NULL) { static $extra_requirements = array(); @@ -322,7 +346,7 @@ function update_extra_requirements($requirements = NULL) { } /** - * Check update requirements and report any errors or (optionally) warnings. + * Checks update requirements and reports errors and (optionally) warnings. * * @param $skip_warnings * (optional) If set to TRUE, requirement warnings will be ignored, and a diff --git a/vendor/WDN-TinyMCE b/vendor/WDN-TinyMCE index d2e8c2533e64ba71f2c5feb4a39e4c2490fd01aa..7fa4ac9c50f554b8e2b542160ca16ee3b4537f00 160000 --- a/vendor/WDN-TinyMCE +++ b/vendor/WDN-TinyMCE @@ -1 +1 @@ -Subproject commit d2e8c2533e64ba71f2c5feb4a39e4c2490fd01aa +Subproject commit 7fa4ac9c50f554b8e2b542160ca16ee3b4537f00