From 752e32c25bd77fa787d7a0e4cf1c20f68a861ddf Mon Sep 17 00:00:00 2001 From: Tim Steiner <tsteiner2@unl.edu> Date: Thu, 27 May 2010 19:28:53 +0000 Subject: [PATCH] Update drupal-7.x branch to alpha5. git-svn-id: file:///tmp/wdn_thm_drupal/branches/drupal-7.x@86 20a16fea-79d4-4915-8869-1ea9d5ebf173 --- .htaccess | 4 +- CHANGELOG.txt | 4 +- INSTALL.txt | 47 +- UPGRADE.txt | 12 +- includes/actions.inc | 27 +- includes/ajax.inc | 75 +- includes/authorize.inc | 4 +- includes/bootstrap.inc | 444 +++++++--- includes/cache-install.inc | 4 +- includes/cache.inc | 22 +- includes/common.inc | 503 ++++++++---- includes/database/database.inc | 140 ++-- includes/database/mysql/database.inc | 17 +- includes/database/mysql/query.inc | 16 +- includes/database/pgsql/database.inc | 3 +- includes/database/pgsql/install.inc | 10 +- includes/database/pgsql/query.inc | 12 +- includes/database/prefetch.inc | 8 +- includes/database/query.inc | 94 ++- includes/database/schema.inc | 6 +- includes/database/select.inc | 86 +- includes/database/sqlite/query.inc | 14 +- includes/database/sqlite/schema.inc | 4 +- includes/entity.inc | 10 +- includes/file.inc | 27 +- includes/form.inc | 86 +- includes/install.core.inc | 27 +- includes/install.inc | 16 +- includes/iso.inc | 4 +- includes/language.inc | 4 +- includes/locale.inc | 8 +- includes/lock.inc | 4 +- includes/mail.inc | 6 +- includes/menu.inc | 41 +- includes/module.inc | 90 +- includes/pager.inc | 6 +- includes/password.inc | 68 +- includes/path.inc | 92 +-- includes/session.inc | 9 +- includes/stream_wrappers.inc | 14 +- includes/theme.inc | 252 +++--- includes/theme.maintenance.inc | 45 +- includes/update.inc | 123 ++- includes/xmlrpc.inc | 4 +- includes/xmlrpcs.inc | 5 +- misc/ajax.js | 6 +- misc/autocomplete.js | 32 +- misc/collapse.js | 8 +- misc/displace.js | 115 +++ misc/drupal.js | 20 +- misc/farbtastic/farbtastic.css | 8 +- misc/states.js | 33 +- misc/tabledrag.js | 6 +- misc/tableheader.js | 4 +- misc/vertical-tabs.js | 4 +- modules/aggregator/aggregator.admin.inc | 12 +- modules/aggregator/aggregator.css | 5 +- modules/aggregator/aggregator.info | 6 +- modules/aggregator/aggregator.install | 9 +- modules/aggregator/aggregator.module | 10 +- modules/aggregator/aggregator.parser.inc | 6 +- modules/aggregator/tests/aggregator_test.info | 6 +- .../aggregator/tests/aggregator_test.module | 4 +- modules/block/block.admin.inc | 16 +- modules/block/block.api.php | 8 +- modules/block/block.info | 6 +- modules/block/block.install | 9 +- modules/block/block.js | 6 +- modules/block/block.module | 16 +- modules/block/tests/block_test.info | 6 +- modules/blog/blog.info | 6 +- modules/blog/blog.install | 17 + modules/book/book.admin.inc | 8 +- modules/book/book.info | 6 +- modules/book/book.install | 3 +- modules/book/book.module | 27 +- modules/color/color-rtl.css | 9 +- modules/color/color.css | 9 +- modules/color/color.info | 6 +- modules/color/color.module | 4 +- modules/color/preview.js | 10 +- modules/comment/comment-wrapper.tpl.php | 12 +- modules/comment/comment.info | 6 +- modules/comment/comment.module | 41 +- modules/comment/comment.test | 15 +- modules/contact/contact.info | 6 +- modules/contextual/contextual.css | 3 +- modules/contextual/contextual.info | 6 +- modules/dashboard/dashboard.css | 8 +- modules/dashboard/dashboard.info | 6 +- modules/dashboard/dashboard.js | 14 +- modules/dashboard/dashboard.module | 17 +- modules/dblog/dblog-rtl.css | 5 +- modules/dblog/dblog.css | 11 +- modules/dblog/dblog.info | 6 +- modules/dblog/dblog.install | 20 +- modules/field/field.api.php | 481 +++++++---- modules/field/field.attach.inc | 5 +- modules/field/field.crud.inc | 10 +- modules/field/field.default.inc | 24 +- modules/field/field.form.inc | 34 +- modules/field/field.info | 6 +- .../field_sql_storage/field_sql_storage.info | 6 +- .../field_sql_storage.module | 18 +- modules/field/modules/list/list.info | 6 +- modules/field/modules/list/list.module | 6 +- .../field/modules/list/tests/list_test.info | 6 +- modules/field/modules/number/number.info | 6 +- modules/field/modules/number/number.module | 4 +- modules/field/modules/options/options.info | 6 +- modules/field/modules/text/text.info | 6 +- modules/field/modules/text/text.module | 4 +- modules/field/tests/field.test | 55 +- modules/field/tests/field_test.entity.inc | 22 +- modules/field/tests/field_test.field.inc | 15 +- modules/field/tests/field_test.info | 6 +- modules/field/tests/field_test.module | 6 +- modules/field/theme/field-rtl.css | 6 +- modules/field/theme/field.css | 4 +- modules/field_ui/field_ui.admin.inc | 14 +- modules/field_ui/field_ui.api.php | 47 +- modules/field_ui/field_ui.info | 6 +- modules/field_ui/field_ui.test | 39 +- modules/file/file.info | 6 +- modules/file/file.js | 4 +- modules/file/file.module | 9 +- modules/file/tests/file_module_test.info | 6 +- modules/filter/filter.info | 6 +- modules/filter/filter.install | 4 +- modules/filter/filter.js | 4 +- modules/filter/filter.module | 68 +- modules/forum/forum-topic-list.tpl.php | 4 +- modules/forum/forum.css | 9 +- modules/forum/forum.info | 6 +- modules/forum/forum.install | 6 +- modules/forum/forum.module | 74 +- modules/forum/forum.pages.inc | 21 +- modules/forum/forum.test | 4 +- modules/help/help.info | 6 +- modules/image/image.effects.inc | 4 +- modules/image/image.field.inc | 7 +- modules/image/image.info | 6 +- modules/image/image.module | 12 +- modules/image/image.test | 4 +- modules/locale/locale.admin.inc | 14 +- modules/locale/locale.api.php | 4 +- modules/locale/locale.css | 6 +- modules/locale/locale.datepicker.js | 70 ++ modules/locale/locale.info | 6 +- modules/locale/locale.install | 11 +- modules/locale/locale.module | 26 +- modules/locale/locale.test | 40 +- modules/locale/tests/locale_test.info | 6 +- modules/menu/menu.admin.inc | 7 +- modules/menu/menu.api.php | 495 +---------- modules/menu/menu.info | 6 +- modules/menu/menu.test | 10 +- modules/node/content_types.inc | 14 +- modules/node/content_types.js | 3 +- modules/node/node.api.php | 17 +- modules/node/node.css | 5 +- modules/node/node.info | 6 +- modules/node/node.install | 77 +- modules/node/node.js | 5 +- modules/node/node.module | 131 ++- modules/node/node.pages.inc | 21 +- modules/node/node.test | 32 +- modules/node/tests/node_access_test.info | 6 +- modules/node/tests/node_presave_test.info | 6 +- modules/node/tests/node_test.info | 6 +- modules/node/tests/node_test_exception.info | 6 +- modules/openid/openid.api.php | 6 +- modules/openid/openid.css | 35 +- modules/openid/openid.inc | 54 +- modules/openid/openid.info | 6 +- modules/openid/openid.module | 66 +- modules/openid/openid.test | 14 +- modules/openid/tests/openid_test.info | 6 +- modules/overlay/overlay-parent.css | 5 +- modules/overlay/overlay-parent.js | 77 +- modules/overlay/overlay.info | 6 +- modules/overlay/overlay.module | 12 +- modules/path/path.admin.inc | 47 +- modules/path/path.info | 6 +- modules/php/php.info | 6 +- modules/poll/poll.info | 6 +- modules/poll/poll.module | 7 +- modules/poll/poll.test | 4 +- modules/profile/profile.info | 6 +- modules/rdf/rdf.info | 6 +- modules/rdf/rdf.module | 6 +- modules/rdf/tests/rdf_test.info | 6 +- modules/search/search.api.php | 10 +- modules/search/search.extender.inc | 34 +- modules/search/search.info | 6 +- modules/search/search.module | 99 ++- modules/search/search.pages.inc | 14 +- modules/search/search.test | 294 ++++++- modules/shortcut/shortcut.info | 6 +- modules/simpletest/drupal_web_test_case.php | 46 +- modules/simpletest/files/README.txt | 4 +- modules/simpletest/files/javascript-1.txt | 2 +- modules/simpletest/files/php-1.txt | 2 +- modules/simpletest/simpletest.info | 6 +- modules/simpletest/simpletest.pages.inc | 9 +- modules/simpletest/simpletest.test | 22 +- modules/simpletest/tests/actions.test | 8 +- .../simpletest/tests/actions_loop_test.info | 6 +- modules/simpletest/tests/ajax.test | 15 +- modules/simpletest/tests/ajax_forms_test.info | 6 +- .../simpletest/tests/ajax_forms_test.module | 26 +- modules/simpletest/tests/ajax_test.info | 6 +- modules/simpletest/tests/batch.test | 4 +- modules/simpletest/tests/batch_test.info | 6 +- modules/simpletest/tests/bootstrap.test | 12 +- modules/simpletest/tests/common.test | 77 +- modules/simpletest/tests/common_test.info | 6 +- modules/simpletest/tests/common_test.module | 32 +- modules/simpletest/tests/database_test.info | 6 +- modules/simpletest/tests/database_test.test | 84 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- modules/simpletest/tests/error_test.info | 6 +- modules/simpletest/tests/file.test | 45 +- modules/simpletest/tests/file_test.info | 6 +- modules/simpletest/tests/file_test.module | 113 ++- modules/simpletest/tests/filter_test.info | 6 +- modules/simpletest/tests/form.test | 50 +- modules/simpletest/tests/form_test.info | 6 +- modules/simpletest/tests/form_test.module | 55 +- modules/simpletest/tests/graph.test | 4 +- modules/simpletest/tests/image_test.info | 6 +- modules/simpletest/tests/menu_test.info | 6 +- modules/simpletest/tests/module_test.info | 6 +- modules/simpletest/tests/registry.test | 26 +- modules/simpletest/tests/session.test | 4 +- modules/simpletest/tests/session_test.info | 6 +- .../tests/system_dependencies_test.info | 6 +- modules/simpletest/tests/system_test.info | 6 +- modules/simpletest/tests/system_test.module | 19 +- modules/simpletest/tests/taxonomy_test.info | 6 +- modules/simpletest/tests/theme.test | 31 +- modules/simpletest/tests/theme_test.info | 6 +- modules/simpletest/tests/theme_test.module | 30 +- modules/simpletest/tests/update.test | 30 +- modules/simpletest/tests/update_test_1.info | 6 +- .../simpletest/tests/update_test_1.install | 31 +- modules/simpletest/tests/update_test_2.info | 6 +- .../simpletest/tests/update_test_2.install | 21 +- modules/simpletest/tests/update_test_3.info | 6 +- .../simpletest/tests/update_test_3.install | 4 +- modules/simpletest/tests/url_alter_test.info | 6 +- modules/simpletest/tests/xmlrpc.test | 4 +- modules/simpletest/tests/xmlrpc_test.info | 6 +- modules/statistics/statistics.info | 6 +- modules/statistics/statistics.install | 20 +- modules/statistics/statistics.module | 14 +- modules/statistics/statistics.test | 65 +- modules/syslog/syslog.info | 6 +- modules/system/admin-rtl.css | 8 +- modules/system/admin.css | 11 +- modules/system/system-behavior-rtl.css | 8 +- modules/system/system-behavior.css | 12 +- modules/system/system-menus-rtl.css | 6 +- modules/system/system-menus.css | 8 +- modules/system/system.admin.inc | 125 +-- modules/system/system.api.php | 774 ++++++++++++++++-- modules/system/system.css | 64 +- modules/system/system.info | 6 +- modules/system/system.install | 209 +++-- modules/system/system.module | 91 +- modules/system/system.updater.inc | 6 +- modules/system/theme.api.php | 104 ++- modules/taxonomy/taxonomy.admin.inc | 26 +- modules/taxonomy/taxonomy.api.php | 15 +- modules/taxonomy/taxonomy.info | 6 +- modules/taxonomy/taxonomy.install | 7 +- modules/taxonomy/taxonomy.module | 34 +- modules/taxonomy/taxonomy.test | 21 +- modules/toolbar/toolbar.css | 37 +- modules/toolbar/toolbar.info | 6 +- modules/toolbar/toolbar.js | 19 +- modules/toolbar/toolbar.module | 29 +- modules/toolbar/toolbar.tpl.php | 4 +- modules/tracker/tracker.info | 6 +- modules/translation/translation.info | 6 +- modules/trigger/tests/trigger_test.info | 6 +- modules/trigger/trigger.admin.inc | 7 +- modules/trigger/trigger.info | 6 +- modules/trigger/trigger.test | 24 +- modules/update/tests/aaa_update_test.info | 6 +- modules/update/tests/bbb_update_test.info | 6 +- modules/update/tests/ccc_update_test.info | 6 +- modules/update/tests/update_test.info | 6 +- modules/update/update.api.php | 25 +- modules/update/update.css | 5 +- modules/update/update.fetch.inc | 18 +- modules/update/update.info | 6 +- modules/update/update.install | 9 +- modules/update/update.manager.inc | 8 +- modules/update/update.module | 8 +- modules/user/user.admin.inc | 4 +- modules/user/user.css | 8 +- modules/user/user.info | 6 +- modules/user/user.install | 4 +- modules/user/user.module | 79 +- modules/user/user.pages.inc | 10 +- modules/user/user.test | 51 +- profiles/minimal/minimal.info | 6 +- profiles/standard/standard.info | 6 +- profiles/standard/standard.install | 8 +- scripts/password-hash.sh | 8 +- themes/garland/color/preview.css | 8 +- themes/garland/fix-ie-rtl.css | 10 +- themes/garland/fix-ie.css | 8 +- themes/garland/garland.info | 6 +- themes/garland/print.css | 30 +- themes/garland/style-rtl.css | 50 +- themes/garland/style.css | 217 +++-- themes/seven/ie6.css | 8 +- themes/seven/reset.css | 5 +- themes/seven/seven.info | 6 +- themes/seven/style.css | 124 ++- themes/seven/template.php | 14 +- themes/stark/stark.info | 6 +- themes/tests/test_theme/test_theme.info | 6 +- .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- update.php | 8 +- 329 files changed, 6127 insertions(+), 3595 deletions(-) create mode 100644 misc/displace.js create mode 100644 modules/blog/blog.install create mode 100644 modules/locale/locale.datepicker.js diff --git a/.htaccess b/.htaccess index 82d1d2ca5..2f101bd04 100644 --- a/.htaccess +++ b/.htaccess @@ -38,8 +38,6 @@ DirectoryIndex index.php index.html index.htm php_value mbstring.http_input pass php_value mbstring.http_output pass php_flag mbstring.encoding_translation off - # Report all PHP errors, including compile-time errors. - php_value error_reporting -1 </IfModule> # Requires mod_expires to be enabled. @@ -113,4 +111,4 @@ DirectoryIndex index.php index.html index.htm RewriteRule ^ index.php [L] </IfModule> -# $Id: .htaccess,v 1.108 2010/04/11 18:33:43 dries Exp $ +# $Id: .htaccess,v 1.109 2010/05/05 06:15:59 webchick Exp $ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 505fb3248..0e59762d8 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,6 @@ -// $Id: CHANGELOG.txt,v 1.363 2010/04/26 21:24:54 webchick Exp $ +// $Id: CHANGELOG.txt,v 1.365 2010/05/23 15:26:38 webchick Exp $ -Drupal 7.0 alpha 4, 2010-04-26 +Drupal 7.0, alpha 5, 2010-05-23 ---------------------- - Database: * Fully rewritten database layer utilizing PHP 5's PDO abstraction layer. diff --git a/INSTALL.txt b/INSTALL.txt index cefdc36d2..c2b97d6d2 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -1,4 +1,4 @@ -// $Id: INSTALL.txt,v 1.79 2010/02/13 21:35:08 dries Exp $ +// $Id: INSTALL.txt,v 1.80 2010/04/30 08:09:21 dries Exp $ CONTENTS OF THIS FILE --------------------- @@ -266,35 +266,40 @@ INSTALLATION customized similar to the above example, to add your site-specific cron key and domain name.) -DRUPAL ADMINISTRATION ---------------------- + +BUILDING AND CUSTOMIZING YOUR SITE +---------------------------------- A new installation of Drupal defaults to a very basic configuration with only a -few active modules and minimal user access rights. +few active modules and minimal user access rights. When extending your site, +you use "modules" and "themes". A module is a plugin that adds functionallity to +Drupal, while a theme changes the front-end look and behavior of your site. + +It is important to install these correctly and not mix them in with the core +Drupal module and theme set (directories modules and themes at the top level). +So normally you place them under the following paths: -Use your administration panel to enable and configure services. For example: +Modules: + sites/all/modules/example_module -General Settings Administer > Site configuration > Site information -Enable Modules Administer > Structure > Modules -Configure Themes Administer > Structure > Themes -Set User Permissions Administer > User management > Permissions +Themes: + sites/all/themes/example_theme -For more information on configuration options, read the instructions which -accompany the different configuration settings and consult the various help -pages available in the administration panel. +If you run a multisite installation you will want to do this a bit differently. +You can read more about that on the multisite part of this file. -Community-contributed modules and themes are available at http://drupal.org/. +Contributed modules can be found at: +http://drupal.org/project/Modules -CUSTOMIZING YOUR THEME(S) -------------------------- +Contributed themes can be found at: +http://drupal.org/project/Themes -Now that your installation is running, you will want to customize the look of -your site. Several sample themes are included and more can be downloaded from -drupal.org. +Later on you might want to write your own code, but remember to NEVER modify the +core modules and themes in Drupal directories modules and themes. Instead use +the hooks available in the Drupal API. You can read more about the Drupal API +and how to develop modules at +http://drupal.org/developing/modules -Simple customization of your theme can be done using only CSS. Further changes -require understanding the phptemplate engine that is part of Drupal. See -http://drupal.org/handbook/customization to find out more. MULTISITE CONFIGURATION ----------------------- diff --git a/UPGRADE.txt b/UPGRADE.txt index eae2bd154..7521fb32a 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -1,4 +1,4 @@ -// $Id: UPGRADE.txt,v 1.20 2010/04/22 08:15:39 webchick Exp $ +// $Id: UPGRADE.txt,v 1.21 2010/04/29 04:49:02 webchick Exp $ UPGRADING --------- @@ -14,12 +14,10 @@ Prior to upgrading, you should ensure that: Let's begin! -1. Backup your database and Drupal directory - especially your "sites" - directory which contains your configuration file and added modules and - themes, any contributed or custom modules in your "modules" directory, - and your "files" directory which contains uploaded files. If other files - have modifications, such as .htaccess or robots.txt, those should be - backed up as well. +1. Back up your Drupal database and site root directory. Be especially sure + to back up your "sites" directory which contains your configuration file, + added modules and themes, and your site's uploaded files. If other files + have modifications, such as .htaccess or robots.txt, back those up as well. Note: for a single site setup, the configuration file is the "settings.php" file located at sites/default/settings.php. The default.settings.php file diff --git a/includes/actions.inc b/includes/actions.inc index 77734fe63..d7c625c8a 100644 --- a/includes/actions.inc +++ b/includes/actions.inc @@ -1,5 +1,5 @@ <?php -// $Id: actions.inc,v 1.36 2010/03/07 07:22:41 webchick Exp $ +// $Id: actions.inc,v 1.38 2010/05/06 05:59:30 webchick Exp $ /** * @file @@ -166,7 +166,7 @@ function actions_list($reset = FALSE) { } // See module_implements() for an explanation of this cast. - return (array)$actions; + return (array) $actions; } /** @@ -197,7 +197,7 @@ function actions_get_all_actions() { } /** - * Creates an associative array keyed by md5 hashes of function names or IDs. + * Creates an associative array keyed by hashes of function names or IDs. * * Hashes are used to prevent actual function names from going out into HTML * forms and coming back. @@ -207,14 +207,14 @@ function actions_get_all_actions() { * and associative arrays with keys 'label', 'type', etc. as values. * This is usually the output of actions_list() or actions_get_all_actions(). * @return - * An associative array whose keys are md5 hashes of the input array keys, and + * An associative array whose keys are hashes of the input array keys, and * whose corresponding values are associative arrays with components * 'callback', 'label', 'type', and 'configurable' from the input array. */ function actions_actions_map($actions) { $actions_map = array(); foreach ($actions as $callback => $array) { - $key = md5($callback); + $key = drupal_hash_base64($callback); $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; $actions_map[$key]['label'] = $array['label']; $actions_map[$key]['type'] = $array['type']; @@ -224,12 +224,12 @@ function actions_actions_map($actions) { } /** - * Given an md5 hash of an action array key, returns the key (function or ID). + * Given a hash of an action array key, returns the key (function or ID). * * Faster than actions_actions_map() when you only need the function name or ID. * * @param $hash - * MD5 hash of a function name or action ID array key. The array key + * Hash of a function name or action ID array key. The array key * is a key into the return value of actions_list() (array key is the action * function name) or actions_get_all_actions() (array key is the action ID). * @return @@ -239,13 +239,20 @@ function actions_function_lookup($hash) { // Check for a function name match. $actions_list = actions_list(); foreach ($actions_list as $function => $array) { - if (md5($function) == $hash) { + if (drupal_hash_base64($function) == $hash) { return $function; } } - + $aid = FALSE; // Must be a configurable action; check database. - return db_query("SELECT aid FROM {actions} WHERE MD5(aid) = :hash AND parameters <> ''", array(':hash' => $hash))->fetchField(); + $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC); + foreach ($result as $row) { + if (drupal_hash_base64($row['aid']) == $hash) { + $aid = $row['aid']; + break; + } + } + return $aid; } /** diff --git a/includes/ajax.inc b/includes/ajax.inc index c5f302321..74b4e73dd 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -1,5 +1,5 @@ <?php -// $Id: ajax.inc,v 1.29 2010/03/31 19:34:56 dries Exp $ +// $Id: ajax.inc,v 1.31 2010/04/30 08:07:54 dries Exp $ /** * @file @@ -20,11 +20,10 @@ * forms, it can be used with the #ajax property. * The #ajax property can be used to bind events to the AJAX framework. By * default, #ajax uses 'system/ajax' as its path for submission and thus calls - * @link ajax_form_callback ajax_form_callback @endlink and a defined - * #ajax['callback'] function. However, you may optionally specify a different - * path to request or a different callback function to invoke, which can return - * updated HTML or can also return a richer set of - * @link ajax_commands AJAX framework commands @endlink. + * ajax_form_callback() and a defined #ajax['callback'] function. + * However, you may optionally specify a different path to request or a + * different callback function to invoke, which can return updated HTML or can + * also return a richer set of @link ajax_commands AJAX framework commands @endlink. * * Standard form handling is as follows: * - A form element has a #ajax member. @@ -124,9 +123,10 @@ * selected automatically for the type of form widget being used, and * is only needed if you need to override the default behavior. * - #ajax['method']: The jQuery method to use to place the new HTML. - * Defaults to 'replace'. May be: 'replace', 'append', 'prepend', - * 'before', 'after', or 'html'. See the jQuery documentation for more - * information on these methods. + * Defaults to 'replaceWith'. May be: 'replaceWith', 'append', 'prepend', + * 'before', 'after', or 'html'. See the + * @link http://api.jquery.com/category/manipulation/ jQuery manipulators documentation @endlink + * for more information on these methods. * - #ajax['progress']: Choose either a throbber or progress bar that is * displayed while awaiting a response from the callback, and add an optional * message. Possible keys: 'type', 'message', 'url', 'interval'. @@ -168,7 +168,7 @@ * $commands[] = ajax_command_changed('#object-1'); * // Menu 'page callback' and #ajax['callback'] functions are supposed to * // return render arrays. If returning an AJAX commands array, it must be - * // encapsulated in a render array structure. + * // encapsulated in a render array structure. * return array('#type' => 'ajax', '#commands' => $commands); * @endcode * @@ -336,13 +336,16 @@ function ajax_deliver($page_callback_result) { } } else { - // Like normal page callbacks, simple AJAX callbacks can return html - // content, as a string or renderable array, to replace what was previously - // there in the wrapper. In this case, in addition to the content, we want - // to add the status messages, but inside the new wrapper, so that they get - // replaced on subsequent AJAX calls for the same wrapper. + // Like normal page callbacks, simple AJAX callbacks can return HTML + // content, as a string or render array. This HTML is inserted in some + // relationship to #ajax['wrapper'], as determined by which jQuery DOM + // manipulation method is used. The method used is specified by + // #ajax['method']. The default method is 'replaceWith', which completely + // replaces the old wrapper element and its content with the new HTML. $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result); - $commands[] = ajax_command_replace(NULL, $html); + $commands[] = ajax_command_insert(NULL, $html); + // Add the status messages inside the new content's wrapper element, so that + // on subsequent AJAX requests, it is treated as old content. $commands[] = ajax_command_prepend(NULL, theme('status_messages')); } @@ -460,10 +463,15 @@ function ajax_process_form($element, &$form_state) { 'selector' => '#' . $element['#id'], 'effect' => 'none', 'speed' => 'none', - 'method' => 'replace', + 'method' => 'replaceWith', 'progress' => array('type' => 'throbber'), ); + // @todo Legacy support. Remove in Drupal 8. + if ($settings['method'] == 'replace') { + $settings['method'] = 'replaceWith'; + } + // Change path to url. $settings['url'] = isset($settings['path']) ? url($settings['path']) : url('system/ajax'); unset($settings['path']); @@ -552,6 +560,37 @@ function ajax_command_alert($text) { ); } +/** + * Creates a Drupal AJAX 'insert' command using the method in #ajax['method']. + * + * This command instructs the client to insert the given HTML using whichever + * jQuery DOM manipulation method has been specified in the #ajax['method'] + * variable of the element that triggered the request. + * + * This command is implemented by Drupal.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @param $selector + * A jQuery selector string. If the command is a response to a request from + * an #ajax form element then this value can be NULL. + * @param $html + * The data to use with the jQuery method. + * @param $settings + * An optional array of settings that will be used for this command only. + * + * @return + * An array suitable for use with the ajax_render() function. + */ +function ajax_command_insert($selector, $html, $settings = NULL) { + return array( + 'command' => 'insert', + 'method' => NULL, + 'selector' => $selector, + 'data' => $html, + 'settings' => $settings, + ); +} + /** * Creates a Drupal AJAX 'insert/replaceWith' command. * @@ -573,7 +612,7 @@ function ajax_command_alert($text) { * @return * An array suitable for use with the ajax_render() function. * - * @see http://docs.jquery.com/Manipulation/replaceWith#content + * 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/authorize.inc b/includes/authorize.inc index 341fc3110..c5ba6916f 100644 --- a/includes/authorize.inc +++ b/includes/authorize.inc @@ -1,5 +1,5 @@ <?php -// $Id: authorize.inc,v 1.10 2010/04/23 05:21:19 webchick Exp $ +// $Id: authorize.inc,v 1.11 2010/05/14 04:50:18 webchick Exp $ /** * @file @@ -83,7 +83,7 @@ function authorize_filetransfer_form($form_state) { $form['connection_settings']['authorize_filetransfer_default']['#options'][$name] = $backend['title']; $form['connection_settings'][$name] = array( '#type' => 'fieldset', - '#attributes' => array('class' => "filetransfer-$name filetransfer"), + '#attributes' => array('class' => array("filetransfer-$name", 'filetransfer')), '#title' => t('@backend connection settings', array('@backend' => $backend['title'])), ); diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 843c475a7..3a76f2d44 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -1,5 +1,5 @@ <?php -// $Id: bootstrap.inc,v 1.377 2010/04/26 21:24:54 webchick Exp $ +// $Id: bootstrap.inc,v 1.391 2010/05/23 15:26:38 webchick Exp $ /** * @file @@ -9,7 +9,7 @@ /** * The current system version. */ -define('VERSION', '7.0-alpha4'); +define('VERSION', '7.0-alpha5'); /** * Core API compatibility. @@ -19,7 +19,7 @@ define('DRUPAL_CORE_COMPATIBILITY', '7.x'); /** * Minimum supported version of PHP. */ -define('DRUPAL_MINIMUM_PHP', '5.2.0'); +define('DRUPAL_MINIMUM_PHP', '5.2.1'); /** * Minimum recommended value of PHP memory_limit. @@ -48,19 +48,12 @@ define('CACHE_PERMANENT', 0); */ define('CACHE_TEMPORARY', -1); -/** - * Indicates that page caching is disabled. - */ -define('CACHE_DISABLED', 0); - -/** - * Indicates that page caching is enabled, using "normal" mode. - */ -define('CACHE_NORMAL', 1); - /** * Log message severity -- Emergency: system is unusable. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -69,6 +62,9 @@ define('WATCHDOG_EMERGENCY', 0); /** * Log message severity -- Alert: action must be taken immediately. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -77,6 +73,9 @@ define('WATCHDOG_ALERT', 1); /** * Log message severity -- Critical: critical conditions. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -85,6 +84,9 @@ define('WATCHDOG_CRITICAL', 2); /** * Log message severity -- Error: error conditions. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -93,6 +95,9 @@ define('WATCHDOG_ERROR', 3); /** * Log message severity -- Warning: warning conditions. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -101,6 +106,9 @@ define('WATCHDOG_WARNING', 4); /** * Log message severity -- Notice: normal but significant condition. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -109,6 +117,9 @@ define('WATCHDOG_NOTICE', 5); /** * Log message severity -- Informational: informational messages. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -117,6 +128,9 @@ define('WATCHDOG_INFO', 6); /** * Log message severity -- Debug: debug-level messages. * + * The WATCHDOG_* constant definitions correspond to the logging severity levels + * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html + * * @see watchdog() * @see watchdog_severity_levels() */ @@ -622,7 +636,7 @@ function drupal_settings_initialize() { ini_set('session.cookie_secure', TRUE); } $prefix = ini_get('session.cookie_secure') ? 'SSESS' : 'SESS'; - session_name($prefix . md5($session_name)); + session_name($prefix . substr(hash('sha256', $session_name), 0, 32)); } /** @@ -1079,14 +1093,14 @@ function drupal_serve_page_from_cache(stdClass $cache) { // drupal_add_http_headers(). Keys are mixed-case. $default_headers = array(); - foreach ($cache->headers as $name => $value) { + foreach ($cache->data['headers'] as $name => $value) { // In the case of a 304 response, certain headers must be sent, and the // remaining may not (see RFC 2616, section 10.3.5). Do not override // headers set in hook_boot(). $name_lower = strtolower($name); if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) { drupal_add_http_header($name, $value); - unset($cache->headers[$name]); + unset($cache->data['headers'][$name]); } } @@ -1096,7 +1110,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { // do not bother caching the page in a public proxy, because the cached copy // will only be served to that particular user due to Vary: Cookie, unless // the Vary header has been replaced or unset in hook_boot() (see below). - $max_age = !variable_get('page_cache_invoke_hooks', TRUE) && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('cache_lifetime', 0) : 0; + $max_age = !variable_get('page_cache_invoke_hooks', TRUE) && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('page_cache_maximum_age', 0) : 0; $default_headers['Cache-Control'] = 'public, max-age=' . $max_age; // Entity tag should change if the output changes. @@ -1116,7 +1130,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { } // Send the remaining headers. - foreach ($cache->headers as $name => $value) { + foreach ($cache->data['headers'] as $name => $value) { drupal_add_http_header($name, $value); } @@ -1144,19 +1158,20 @@ function drupal_serve_page_from_cache(stdClass $cache) { header('Vary: Accept-Encoding', FALSE); // If page_compression is enabled, the cache contains gzipped data. if ($return_compressed) { - // $cache->data is already gzip'ed, so make sure zlib.output_compression - // does not compress it once more. + // $cache->data['body'] is already gzip'ed, so make sure + // zlib.output_compression does not compress it once more. ini_set('zlib.output_compression', '0'); header('Content-Encoding: gzip'); } else { // The client does not support compression, so unzip the data in the // cache. Strip the gzip header and run uncompress. - $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8)); + $cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8)); } } - print $cache->data; + // Print the page. + print $cache->data['body']; } /** @@ -1177,7 +1192,7 @@ function bootstrap_hooks() { function drupal_unpack($obj, $field = 'data') { if ($obj->$field && $data = unserialize($obj->$field)) { foreach ($data as $key => $value) { - if (!isset($obj->$key)) { + if (!empty($key) && !isset($obj->$key)) { $obj->$key = $value; } } @@ -1186,12 +1201,13 @@ function drupal_unpack($obj, $field = 'data') { } /** - * Translate strings to the page language or a given language. + * Translates a string to the current language or to a given language. * - * Human-readable text that will be displayed somewhere within a page should - * be run through the t() function. + * All human-readable text that will be displayed on the site or sent to a user + * should be passed through the t() function. This ensures that sites can be + * fully translated into other languages. * - * Examples: + * Here are some examples of translating static text using t(): * @code * if (!$info || !$info['extension']) { * form_set_error('picture_upload', t('The uploaded file was not an image.')); @@ -1203,95 +1219,85 @@ function drupal_unpack($obj, $field = 'data') { * ); * @endcode * - * Any text within t() can be extracted by translators and changed into - * the equivalent text in their native language. - * - * Special variables called "placeholders" are used to signal dynamic - * information in a string which should not be translated. Placeholders - * can also be used for text that may change from time to time (such as - * link paths) to be changed without requiring updates to translations. - * - * For example: - * @code - * $output = t('There are currently %members and %visitors online.', array( - * '%members' => format_plural($total_users, '1 user', '@count users'), - * '%visitors' => format_plural($guests->count, '1 guest', '@count guests'))); - * @endcode - * - * There are three styles of placeholders: - * - !variable, which indicates that the text should be inserted as-is. This is - * useful for inserting variables into things like e-mail. + * In addition to translating static text, t() can handle text that should not + * be translated or that might change from time to time (such as link paths) + * and dynamic text from variables, using special "placeholders". There are + * three styles of placeholders: + * - !variable: Indicates that the text should be inserted as-is. This is + * useful for inserting variables into things like e-mail. Example: * @code * $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE)))); * @endcode - * - * - @variable, which indicates that the text should be run through - * check_plain, to escape HTML characters. Use this for any output that's - * displayed within a Drupal page. + * - @variable: Indicates that the text should be run through check_plain(), to + * escape HTML characters. Use this for any output that is displayed within a + * Drupal page. Example: * @code * drupal_set_title($title = t("@name's blog", array('@name' => format_username($account))), PASS_THROUGH); * @endcode - * - * - %variable, which indicates that the string should be HTML escaped and - * highlighted with theme_placeholder() which shows up by default as - * <em>emphasized</em>. + * - %variable: Indicates that the string should be HTML-escaped and highlighted + * with theme_placeholder(), which shows up by default as <em>emphasized</em>. * @code * $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => format_username($user), '%name-to' => format_username($account))); * @endcode * - * When using t(), try to put entire sentences and strings in one t() call. - * This makes it easier for translators, as it provides context as to what - * each word refers to. HTML markup within translation strings is allowed, but - * should be avoided if possible. The exception are embedded links; link - * titles add a context for translators, so should be kept in the main string. - * - * Here is an example of incorrect usage of t(): - * @code - * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact'))); - * @endcode - * - * Here is an example of t() used correctly: - * @code - * $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>'; - * @endcode - * - * Avoid escaping quotation marks wherever possible. - * - * Incorrect: - * @code - * $output .= t('Don\'t click me.'); - * @endcode - * - * Correct: - * @code - * $output .= t("Don't click me."); - * @endcode - * - * Because t() is designed for handling code-based strings, in almost all - * cases, the actual string and not a variable must be passed through t(). - * - * Extraction of translations is done based on the strings contained in t() - * calls. If a variable is passed through t(), the content of the variable - * cannot be extracted from the file for translation. + * When using t(), try to put entire paragraphs in one t() call. This makes it + * easier for translators, as it provides context as to what each word refers + * to (and also allows translators to adjust word order, which may not be the + * same in all languages). HTML markup within translation strings is allowed, + * but should be avoided if possible. The exception is embedded links: link + * titles add context for translators and need to be translated, so they should + * be kept in the main string, while link URLs should be generated using + * placeholders. + * - Incorrect HTML in t(): + * @code + * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact'))); + * @endcode + * - Correct HTML in t(): + * @code + * $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>'; + * @endcode * - * Incorrect: - * @code - * $message = 'An error occurred.'; - * drupal_set_message(t($message), 'error'); - * $output .= t($message); - * @endcode + * Another thing that is helpful is to avoid escaping quotation marks wherever + * possible, because it can be confusing to translation teams. + * - Less desirable quotation mark escaping: + * @code + * $output .= t('Don\'t click me.'); + * @endcode + * - Better way to use quotation marks: + * @code + * $output .= t("Don't click me."); + * @endcode * - * Correct: - * @code - * $message = t('An error occurred.'); - * drupal_set_message($message, 'error'); - * $output .= $message; - * @endcode + * It is important that all translation uses the t() mechanism, because in + * addition to actually translating the text at run-time, the t() function is + * also used by text-extraction routines to find text that needs to be + * translated, and build databases of text to be translated for translation + * teams. For that reason, you must put the actual string into the t() function, + * in most cases, and not a variable. + * - Incorrect use of a variable in t(): + * @code + * $message = 'An error occurred.'; + * drupal_set_message(t($message), 'error'); + * $output .= t($message); + * @endcode + * - Correct translation of a variable with t(): + * @code + * $message = t('An error occurred.'); + * drupal_set_message($message, 'error'); + * $output .= $message; + * @endcode * * The only case in which variables can be passed safely through t() is when * code-based versions of the same strings will be passed through t() (or * otherwise extracted) elsewhere. * + * Also, you cannot use t() early in the bootstrap process, prior to the + * DRUPAL_BOOTSTRAP_LANGUAGE phase. The language variables will not be + * initialized yet, so the string will not be translated into the correct + * language. Examples of places where t() cannot be used include: + * - In a PHP define() statement. + * - In a hook_boot() implementation. + * * In some cases, modules may include strings in code that can't use t() * calls. For example, a module may use an external PHP application that * produces strings that are loaded into variables in Drupal for output. @@ -1308,7 +1314,7 @@ function drupal_unpack($obj, $field = 'data') { * } * @endcode * - * Sample dummy file. + * Sample dummy file: * @code * // Dummy function included in example.potx.inc. * function example_potx() { @@ -1322,9 +1328,7 @@ function drupal_unpack($obj, $field = 'data') { * @endcode * * Having passed strings through t() in a dummy function, it is then - * okay to pass variables through t(). - * - * Correct (if a dummy file was used): + * possible to pass variables through t(): * @code * $time = new Time(); * $output .= t($time->today); @@ -1334,7 +1338,7 @@ function drupal_unpack($obj, $field = 'data') { * sources should not be passed through t(). Doing so leads to the following * problems and errors: * - The t() system doesn't support updates to existing strings. When user - * data is updated, the next time it's passed through t() a new record is + * data is updated, the next time it's passed through t(), a new record is * created instead of an update. The database bloats over time and any * existing translations are orphaned with each update. * - The t() system assumes any data it receives is in English. User data may @@ -1344,6 +1348,9 @@ function drupal_unpack($obj, $field = 'data') { * passed through t(), they are added to this text group, which is rendered * inaccurate since it is a mix of actual interface strings and various user * input strings of uncertain origin. + * Instead, translation of these data can be done through the locale system, + * either directly through hook_local() or through helper functions provided by + * contributed modules. * * Incorrect: * @code @@ -1351,16 +1358,9 @@ function drupal_unpack($obj, $field = 'data') { * $output .= check_plain(t($item['title'])); * @endcode * - * Instead, translation of these data can be done through the locale system, - * either directly or through helper functions provided by contributed - * modules. - * @see hook_locale() - * * During installation, st() is used in place of t(). Code that may be called * during installation or during normal operation should use the get_t() * helper function. - * @see st() - * @see get_t() * * @param $string * A string containing the English string to translate. @@ -1369,15 +1369,16 @@ function drupal_unpack($obj, $field = 'data') { * of any key in this array are replaced with the corresponding value. Based * on the first character of the key, the value is escaped and/or themed: * - !variable: inserted as is - * - @variable: escape plain text to HTML (check_plain) + * - @variable: escape plain text to HTML (using check_plain()) * - %variable: escape text and theme as a placeholder for user-submitted - * content (check_plain + theme_placeholder) + * content (using check_plain() + theme_placeholder()) * @param $options * An associative array of additional options, with the following keys: - * - 'langcode' (default to the current language) The language code to + * - 'langcode' (defaults to the current language) The language code to * translate to a language other than what is used to display the page. - * - 'context' (default to the empty context) The context the source string + * - 'context' (defaults to the empty context) The context the source string * belongs to. + * * @return * The translated string. * @@ -1464,7 +1465,7 @@ function check_plain($text) { // drupal_validate_utf8() here. This avoids the overhead of an additional // function call, since check_plain() may be called hundreds of times during // a request. For PHP 5.2.5+, this check for valid UTF-8 should be handled - // internally by PHP in htmlspecialchars(). + // internally by PHP in htmlspecialchars(). // See http://www.php.net/releases/5_2_5.php. // @todo remove this when support for either IE6 or PHP < 5.2.5 is dropped. @@ -1662,6 +1663,48 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { return array(); } +/** + * Get the title of the current page, for display on the page and in the title bar. + * + * @return + * The current page's title. + */ +function drupal_get_title() { + $title = drupal_set_title(); + + // During a bootstrap, menu.inc is not included and thus we cannot provide a title. + if (!isset($title) && function_exists('menu_get_active_title')) { + $title = check_plain(menu_get_active_title()); + } + + return $title; +} + +/** + * Set the title of the current page, for display on the page and in the title bar. + * + * @param $title + * Optional string value to assign to the page title; or if set to NULL + * (default), leaves the current title unchanged. + * @param $output + * Optional flag - normally should be left as CHECK_PLAIN. Only set to + * PASS_THROUGH if you have already removed any possibly dangerous code + * from $title using a function like check_plain() or filter_xss(). With this + * flag the string will be passed through unchanged. + * + * @return + * The updated title of the current page. + */ +function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { + $stored_title = &drupal_static(__FUNCTION__); + + if (isset($title)) { + $stored_title = ($output == PASS_THROUGH) ? $title : check_plain($title); + } + + return $stored_title; +} + /** * Check to see if an IP address has been blocked. * @@ -1711,6 +1754,91 @@ function drupal_block_denied($ip) { } } +/** + * Returns a string of highly randomized bytes (over the full 8-bit range). + * + * This function is better than simply calling mt_rand() or any other built-in + * PHP function because it can return a long string of bytes (compared to < 4 + * bytes normally from mt_rand()) and uses the best available pseudo-random source. + * + * @param $count + * The number of characters (bytes) to return in the string. + */ +function drupal_random_bytes($count) { + // $random_state does not use drupal_static as it stores random bytes. + static $random_state, $bytes; + // Initialize on the first call. The contents of $_SERVER includes a mix of + // user-specific and system information that varies a little with each page. + if (!isset($random_state)) { + $random_state = print_r($_SERVER, TRUE); + if (function_exists('getmypid')) { + // Further initialize with the somewhat random PHP process ID. + $random_state .= getmypid(); + } + $bytes = ''; + } + if (strlen($bytes) < $count) { + // /dev/urandom is available on many *nix systems and is considered the + // best commonly available pseudo-random source. + if ($fh = @fopen('/dev/urandom', 'rb')) { + // PHP only performs buffered reads, so in reality it will always read + // at least 4096 bytes. Thus, it costs nothing extra to read and store + // that much so as to speed any additional invocations. + $bytes .= fread($fh, max(4096, $count)); + fclose($fh); + } + // If /dev/urandom is not available or returns no bytes, this loop will + // generate a good set of pseudo-random bytes on any system. + // Note that it may be important that our $random_state is passed + // through hash() prior to being rolled into $output, that the two hash() + // invocations are different, and that the extra input into the first one - + // the microtime() - is prepended rather than appended. This is to avoid + // directly leaking $random_state via the $output stream, which could + // allow for trivial prediction of further "random" numbers. + while (strlen($bytes) < $count) { + $random_state = hash('sha256', microtime() . mt_rand() . $random_state); + $bytes .= hash('sha256', mt_rand() . $random_state, TRUE); + } + } + $output = substr($bytes, 0, $count); + $bytes = substr($bytes, $count); + return $output; +} + +/** + * Calculate a base-64 encoded, URL-safe sha-256 hmac. + * + * @param $data + * String to be validated with the hmac. + * @param $key + * A secret string key. + * + * @return + * A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and + * any = padding characters removed. + */ +function drupal_hmac_base64($data, $key) { + $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE)); + // Modify the hmac so it's safe to use in URLs. + return strtr($hmac, array('+' => '-', '/' => '_', '=' => '')); +} + +/** + * Calculate a base-64 encoded, URL-safe sha-256 hash. + * + * @param $data + * String to be hashed. + * + * @return + * A base-64 encoded sha-256 hash, with + replaced with -, / with _ and + * any = padding characters removed. + */ +function drupal_hash_base64($data) { + $hash = base64_encode(hash('sha256', $data, TRUE)); + // Modify the hash so it's safe to use in URLs. + return strtr($hash, array('+' => '-', '/' => '_', '=' => '')); +} + /** * Generates a default anonymous $user object. * @@ -1911,16 +2039,16 @@ function _drupal_bootstrap_page_cache() { } // Check for a cache mode force from settings.php. if (variable_get('page_cache_without_database')) { - $cache_mode = CACHE_NORMAL; + $cache_enabled = TRUE; } else { drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE); - $cache_mode = variable_get('cache'); + $cache_enabled = variable_get('cache'); } drupal_block_denied(ip_address()); // If there is no session cookie and cache is enabled (or forced), try // to serve a cached page. - if (!isset($_COOKIE[session_name()]) && $cache_mode == CACHE_NORMAL) { + if (!isset($_COOKIE[session_name()]) && $cache_enabled) { // Make sure there is a user object because it's timestamp will be // checked, hook_boot might check for anonymous user etc. $user = drupal_anonymous_user(); @@ -1928,13 +2056,16 @@ function _drupal_bootstrap_page_cache() { $cache = drupal_page_get_cache(); // If there is a cached page, display it. if (is_object($cache)) { + header('X-Drupal-Cache: HIT'); + // Restore the metadata cached with the page. + $_GET['q'] = $cache->data['path']; + drupal_set_title($cache->data['title'], PASS_THROUGH); date_default_timezone_set(drupal_get_user_timezone()); // If the skipping of the bootstrap hooks is not enforced, call // hook_boot. if (variable_get('page_cache_invoke_hooks', TRUE)) { bootstrap_invoke_all('boot'); } - header('X-Drupal-Cache: HIT'); drupal_serve_page_from_cache($cache); // If the skipping of the bootstrap hooks is not enforced, call // hook_exit. @@ -1944,6 +2075,9 @@ function _drupal_bootstrap_page_cache() { // We are done. exit; } + else { + header('X-Drupal-Cache: MISS'); + } } } @@ -1958,6 +2092,15 @@ function _drupal_bootstrap_database() { header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); exit; } + + // Redirect the user to the installation script if Drupal has not been + // installed yet (i.e., if no $databases array has been defined in the + // settings.php file) and we are not already installing. + if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) { + include_once DRUPAL_ROOT . '/includes/install.inc'; + install_goto('install.php'); + } + // Initialize the database system. Note that the connection // won't be initialized until it is actually requested. require_once DRUPAL_ROOT . '/includes/database/database.inc'; @@ -1992,9 +2135,6 @@ function _drupal_bootstrap_variables() { */ function _drupal_bootstrap_page_header() { bootstrap_invoke_all('boot'); - if (!drupal_page_get_cache(TRUE) && drupal_page_is_cacheable()) { - header('X-Drupal-Cache: MISS'); - } // Prepare for non-cached page workflow. require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); @@ -2029,9 +2169,9 @@ function drupal_valid_test_ua($user_agent) { // the database is not yet initialized and we can't access any Drupal variables. // The file properties add more entropy not easily accessible to others. $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc'; - $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE); + $key = serialize($databases) . filectime($filepath) . fileinode($filepath); // The HMAC must match. - return $hmac == base64_encode(hash_hmac('sha1', $check_string, $key, TRUE)); + return $hmac == drupal_hmac_base64($check_string, $key); } /** @@ -2046,12 +2186,12 @@ function drupal_generate_test_ua($prefix) { // check the HMAC before the database is initialized. filectime() // and fileinode() are not easily determined from remote. $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc'; - $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE); + $key = serialize($databases) . filectime($filepath) . fileinode($filepath); } // Generate a moderately secure HMAC based on the database credentials. $salt = uniqid('', TRUE); $check_string = $prefix . ';' . time() . ';' . $salt; - return $check_string . ';' . base64_encode(hash_hmac('sha1', $check_string, $key, TRUE)); + return $check_string . ';' . drupal_hmac_base64($check_string, $key); } /** @@ -2237,6 +2377,54 @@ function request_path() { return $path; } +/** + * Return a component of the current Drupal path. + * + * When viewing a page at the path "admin/structure/types", for example, arg(0) + * returns "admin", arg(1) returns "structure", and arg(2) returns "types". + * + * Avoid use of this function where possible, as resulting code is hard to read. + * In menu callback functions, attempt to use named arguments. See the explanation + * in menu.inc for how to construct callbacks that take arguments. When attempting + * to use this function to load an element from the current path, e.g. loading the + * node on a node page, please use menu_get_object() instead. + * + * @param $index + * The index of the component, where each component is separated by a '/' + * (forward-slash), and where the first component has an index of 0 (zero). + * @param $path + * A path to break into components. Defaults to the path of the current page. + * + * @return + * The component specified by $index, or NULL if the specified component was + * not found. + */ +function arg($index = NULL, $path = NULL) { + // Even though $arguments doesn't need to be resettable for any functional + // reasons (the result of explode() does not depend on any run-time + // information), it should be resettable anyway in case a module needs to + // free up the memory used by it. + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['arguments'] = &drupal_static(__FUNCTION__); + } + $arguments = &$drupal_static_fast['arguments']; + + if (!isset($path)) { + $path = $_GET['q']; + } + if (!isset($arguments[$path])) { + $arguments[$path] = explode('/', $path); + } + if (!isset($index)) { + return $arguments[$path]; + } + if (isset($arguments[$path][$index])) { + return $arguments[$path][$index]; + } +} + /** * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of @@ -2440,7 +2628,7 @@ function _registry_check_code($type, $name = NULL) { if ($lookup_cache[$cache_key]) { require DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; } - return (bool)$lookup_cache[$cache_key]; + return (bool) $lookup_cache[$cache_key]; } // This function may get called when the default database is not active, but diff --git a/includes/cache-install.inc b/includes/cache-install.inc index 3beebf3ab..c4cf081ac 100644 --- a/includes/cache-install.inc +++ b/includes/cache-install.inc @@ -1,5 +1,5 @@ <?php -// $Id: cache-install.inc,v 1.8 2010/04/11 17:16:45 dries Exp $ +// $Id: cache-install.inc,v 1.9 2010/05/18 18:26:30 dries Exp $ /** * @file @@ -24,7 +24,7 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac return array(); } - function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) { + function set($cid, $data, $expire = CACHE_PERMANENT) { } function clear($cid = NULL, $wildcard = FALSE) { diff --git a/includes/cache.inc b/includes/cache.inc index b5dc86e8a..a625266bf 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -1,5 +1,5 @@ <?php -// $Id: cache.inc,v 1.47 2010/03/07 07:26:13 webchick Exp $ +// $Id: cache.inc,v 1.48 2010/05/18 18:26:30 dries Exp $ /** * Get the cache object for a cache bin. @@ -133,11 +133,9 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. - * @param $headers - * A string containing HTTP header information for cached pages. */ -function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT, array $headers = NULL) { - return _cache_get_object($bin)->set($cid, $data, $expire, $headers); +function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { + return _cache_get_object($bin)->set($cid, $data, $expire); } /** @@ -263,10 +261,8 @@ interface DrupalCacheInterface { * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. - * @param $headers - * A string containing HTTP header information for cached pages. */ - function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL); + function set($cid, $data, $expire = CACHE_PERMANENT); /** @@ -312,7 +308,7 @@ class DrupalDatabaseCache implements DrupalCacheInterface { try { // Garbage collection necessary when enforcing a minimum cache lifetime. $this->garbageCollection($this->bin); - $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject(); + $cache = db_query("SELECT data, created, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject(); return $this->prepareItem($cache); } catch (Exception $e) { @@ -327,7 +323,7 @@ class DrupalDatabaseCache implements DrupalCacheInterface { // Garbage collection necessary when enforcing a minimum cache lifetime. $this->garbageCollection($this->bin); $query = db_select($this->bin); - $query->fields($this->bin, array('cid', 'data', 'created', 'headers', 'expire', 'serialized')); + $query->fields($this->bin, array('cid', 'data', 'created', 'expire', 'serialized')); $query->condition($this->bin . '.cid', $cids, 'IN'); $result = $query->execute(); $cache = array(); @@ -401,19 +397,15 @@ class DrupalDatabaseCache implements DrupalCacheInterface { if ($cache->serialized) { $cache->data = unserialize($cache->data); } - if (isset($cache->headers)) { - $cache->headers = unserialize($cache->headers); - } return $cache; } - function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) { + function set($cid, $data, $expire = CACHE_PERMANENT) { $fields = array( 'serialized' => 0, 'created' => REQUEST_TIME, 'expire' => $expire, - 'headers' => isset($headers) ? serialize($headers) : NULL, ); if (!is_string($data)) { $fields['data'] = serialize($data); diff --git a/includes/common.inc b/includes/common.inc index b340f0280..b976c90a6 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1,5 +1,5 @@ <?php -// $Id: common.inc,v 1.1151 2010/04/24 14:53:59 dries Exp $ +// $Id: common.inc,v 1.1170 2010/05/21 15:52:19 dries Exp $ /** * @file @@ -660,7 +660,7 @@ function drupal_encode_path($path) { function drupal_goto($path = '', array $options = array(), $http_response_code = 302) { // A destination in $_GET always overrides the function arguments. if (isset($_GET['destination'])) { - $destination = drupal_parse_url(urldecode($_GET['destination'])); + $destination = drupal_parse_url($_GET['destination']); $path = $destination['path']; $options['query'] = $destination['query']; $options['fragment'] = $destination['fragment']; @@ -758,7 +758,9 @@ function drupal_access_denied() { * - error * If an error occurred, the error message. Otherwise not set. * - headers - * An array containing the response headers as name/value pairs. + * An array containing the response headers as name/value pairs. HTTP + * header names are case-insensitive (RFC 2616, section 4.2), so for easy + * access the array keys are returned in lower case. * - data * A string containing the response body that was received. */ @@ -877,21 +879,33 @@ function drupal_http_request($url, array $options = array()) { fwrite($fp, $request); - // Fetch response. + // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782 + // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but + // instead must invoke stream_get_meta_data() each iteration. + $info = stream_get_meta_data($fp); + $alive = !$info['eof'] && !$info['timed_out']; $response = ''; - while (!feof($fp)) { + + while ($alive) { // Calculate how much time is left of the original timeout value. $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000; if ($timeout <= 0) { - $result->code = HTTP_REQUEST_TIMEOUT; - $result->error = 'request timed out'; - return $result; + $info['timed_out'] = TRUE; + break; } stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1))); - $response .= fread($fp, 1024); + $chunk = fread($fp, 1024); + $response .= $chunk; + $info = stream_get_meta_data($fp); + $alive = !$info['eof'] && !$info['timed_out'] && $chunk; } fclose($fp); + if ($info['timed_out']) { + $result->code = HTTP_REQUEST_TIMEOUT; + $result->error = 'request timed out'; + return $result; + } // Parse response headers from the response body. list($response, $result->data) = explode("\r\n\r\n", $response, 2); $response = preg_split("/\r\n|\n|\r/", $response); @@ -905,14 +919,15 @@ function drupal_http_request($url, array $options = array()) { // Parse the response headers. while ($line = trim(array_shift($response))) { - list($header, $value) = explode(':', $line, 2); - if (isset($result->headers[$header]) && $header == 'Set-Cookie') { + list($name, $value) = explode(':', $line, 2); + $name = strtolower($name); + if (isset($result->headers[$name]) && $name == 'set-cookie') { // RFC 2109: the Set-Cookie response header comprises the token Set- // Cookie:, followed by a comma-separated list of one or more cookies. - $result->headers[$header] .= ',' . trim($value); + $result->headers[$name] .= ',' . trim($value); } else { - $result->headers[$header] = trim($value); + $result->headers[$name] = trim($value); } } @@ -972,7 +987,7 @@ function drupal_http_request($url, array $options = array()) { case 301: // Moved permanently case 302: // Moved temporarily case 307: // Moved temporarily - $location = $result->headers['Location']; + $location = $result->headers['location']; $options['timeout'] -= timer_read(__FUNCTION__) / 1000; if ($options['timeout'] <= 0) { $result->code = HTTP_REQUEST_TIMEOUT; @@ -2419,7 +2434,7 @@ function drupal_page_footer() { // Commit the user session, if needed. drupal_session_commit(); - if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && ($cache = drupal_page_set_cache())) { + if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) { drupal_serve_page_from_cache($cache); } else { @@ -2881,10 +2896,7 @@ function drupal_aggregate_css(&$css_groups) { // the group's data property to the file path of the aggregate file. case 'file': if ($group['preprocess'] && $preprocess_css) { - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'css_' . md5(serialize($group['items'])) . '.css'; - $css_groups[$key]['data'] = drupal_build_css_cache($group['items'], $filename); + $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); } break; // Aggregate all inline CSS content into the group's data property. @@ -3010,7 +3022,7 @@ function drupal_pre_render_styles($elements) { // for the aggregate file. if (isset($group['data'])) { $element = $link_element_defaults; - $element['#attributes']['href'] = file_create_url($group['data']) . '?' . $query_string; + $element['#attributes']['href'] = file_create_url($group['data']); $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; @@ -3093,23 +3105,39 @@ function drupal_pre_render_styles($elements) { } /** - * Aggregate and optimize CSS files, putting them in the files directory. + * Aggregates and optimizes CSS files into a cache file in the files directory. + * + * The file name for the CSS cache file is generated from the hash of the + * aggregated contents of the files in $css. This forces proxies and browsers + * to download new CSS when the CSS changes. + * + * The cache file name is retrieved on a page load via a lookup variable that + * contains an associative array. The array key is the hash of the file names + * in $css while the value is the cache file name. The cache file is generated + * in two cases. First, if there is no file name value for the key, which will + * happen if a new file name has been added to $css or after the lookup + * variable is emptied to force a rebuild of the cache. Second, the cache + * file is generated if it is missing on disk. Old cache files are not deleted + * immediately when the lookup variable is emptied, but are deleted after a set + * period by drupal_delete_file_if_stale(). This ensures that files referenced + * by a cached page will still be available. * * @param $css * An array of CSS files to aggregate and compress into one file. - * @param $filename - * The name of the aggregate CSS file. + * * @return - * The name of the CSS file, or FALSE if the file could not be saved. + * The URI of the CSS cache file, or FALSE if the file could not be saved. */ -function drupal_build_css_cache($css, $filename) { +function drupal_build_css_cache($css) { $data = ''; + $uri = ''; + $map = variable_get('drupal_css_cache_files', array()); + $key = hash('sha256', serialize($css)); + if (isset($map[$key])) { + $uri = $map[$key]; + } - // Create the css/ within the files folder. - $csspath = 'public://css'; - $uri = $csspath . '/' . $filename; - - if (!file_exists($uri)) { + if (empty($uri) || !file_exists($uri)) { // Build aggregate CSS file. foreach ($css as $stylesheet) { // Only 'file' stylesheets can be aggregated. @@ -3130,11 +3158,20 @@ function drupal_build_css_cache($css, $filename) { $data = preg_replace($regexp, '', $data); $data = implode('', $matches[0]) . $data; + // Prefix filename to prevent blocking by firewalls which reject files + // starting with "ad*". + $filename = 'css_' . drupal_hash_base64($data) . '.css'; + // Create the css/ within the files folder. + $csspath = 'public://css'; + $uri = $csspath . '/' . $filename; // Create the CSS file. file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); - if (!file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { + if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { return FALSE; } + // Save the updated map. + $map[$key] = $uri; + variable_set('drupal_css_cache_files', $map); } return $uri; } @@ -3263,10 +3300,21 @@ function _drupal_load_stylesheet($matches) { } /** - * Delete all cached CSS files. + * Deletes old cached CSS files. */ function drupal_clear_css_cache() { - file_scan_directory('public://css', '/.*/', array('callback' => 'file_unmanaged_delete')); + variable_del('drupal_css_cache_files'); + file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale')); +} + +/** + * Callback to delete files modified more than a set time ago. + */ +function drupal_delete_file_if_stale($uri) { + // Default stale file threshold is 30 days. + if (REQUEST_TIME - filemtime($uri) > variable_get('drupal_stale_file_threshold', 2592000)) { + file_unmanaged_delete($uri); + } } /** @@ -3752,15 +3800,12 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { // Aggregate any remaining JS files that haven't already been output. if ($preprocess_js && count($files) > 0) { - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". foreach ($files as $key => $file_set) { - $filename = 'js_' . md5(serialize($file_set)) . '.js'; - $uri = drupal_build_js_cache($file_set, $filename); + $uri = drupal_build_js_cache($file_set); // Only include the file if was written successfully. Errors are logged // using watchdog. if ($uri) { - $preprocess_file = file_create_url($uri) . '?' . $default_query_string; + $preprocess_file = file_create_url($uri); $js_element = $element; $js_element['#attributes']['src'] = $preprocess_file; $processed[$key] = theme('html_tag', array('element' => $js_element)); @@ -3879,41 +3924,129 @@ function drupal_process_attached($elements, $weight = JS_DEFAULT, $dependency_ch } /** - * Adds JavaScript to the element to allow it to have different active states. + * Adds JavaScript to change the state of an element based on another element. * - * @param $elements - * The structured array that may contain an array item named states. This - * array describes the different JavaScript states that can be applied to the - * element when certain conditions are met. The #states array is first keyed - * by one of the following states: - * - enabled - * - invisible - * - invalid - * - untouched - * - optional - * - filled - * - unchecked - * - irrelevant - * - expanded - * - readwrite - * - * Each of these states is an array containing conditions that must be met in - * order for this state to be active. The key to this conditioning array is - * a jQuery selector for the element that is checked. The value of the - * conditioning array are the states that are checked on the element (empty, - * checked, value, collapsed, etc) and the expected value of that condition. - * - * @code - * $form['email_canceled']['settings'] = array( - * '#type' => 'container', - * '#states' => array( - * // Hide the settings when the cancel notify checkbox is disabled. - * 'invisible' => array( - * 'input[name="email_canceled_toggle"]' => array('checked' => FALSE), - * ), + * A "state" means a certain property on a DOM element, such as "visible" or + * "checked". A state can be applied to an element, depending on the state of + * another element on the page. In general, states depend on HTML attributes and + * DOM element properties, which change due to user interaction. + * + * Since states are driven by JavaScript only, it is important to understand + * that all states are applied on presentation only, none of the states force + * any server-side logic, and that they will not be applied for site visitors + * without JavaScript support. All modules implementing states have to make + * sure that the intended logic also works without JavaScript being enabled. + * + * #states is an associative array in the form of: + * @code + * array( + * STATE1 => CONDITIONS_ARRAY1, + * STATE2 => CONDITIONS_ARRAY2, + * ... + * ) + * @endcode + * Each key is the name of a state to apply to the element, such as 'visible'. + * Each value is a list of conditions that denote when the state should be + * applied. + * + * Multiple different states may be specified to act on complex conditions: + * @code + * array( + * 'visible' => CONDITIONS, + * 'checked' => OTHER_CONDITIONS, + * ) + * @endcode + * + * Every condition is a key/value pair, whose key is a jQuery selector that + * denotes another element on the page, and whose value is an array of + * conditions, which must bet met on that element: + * @code + * array( + * 'visible' => array( + * JQUERY_SELECTOR => REMOTE_CONDITIONS, + * JQUERY_SELECTOR => REMOTE_CONDITIONS, + * ... + * ), + * ) + * @endcode + * All conditions must be met for the state to be applied. + * + * Each remote condition is a key/value pair specifying conditions on the other + * element that need to be met to apply the state to the element: + * @code + * array( + * 'visible' => array( + * ':input[name="remote_checkbox"]' => array('checked' => TRUE), + * ), + * ) + * @endcode + * + * For example, to show a textfield only when a checkbox is checked: + * @code + * $form['toggle_me'] = array( + * '#type' => 'checkbox', + * '#title' => t('Tick this box to type'), + * ); + * $form['settings'] = array( + * '#type' => 'textfield', + * '#states' => array( + * // Only show this field when the 'toggle_me' checkbox is enabled. + * 'visible' => array( + * ':input[name="toggle_me"]' => array('checked' => TRUE), * ), - * ); - * @endcode + * ), + * ); + * @endcode + * + * The following states may be applied to an element: + * - enabled + * - disabled + * - visible + * - invisible + * - checked + * - unchecked + * - expanded + * - collapsed + * + * The following states may be used in remote conditions: + * - enabled + * - disabled + * - visible + * - invisible + * - checked + * - unchecked + * - value + * + * The following states exist for both states and remote conditions, but are not + * fully implemented and may not change anything on the element: + * - required + * - optional + * - relevant + * - irrelevant + * - valid + * - invalid + * - touched + * - untouched + * - filled + * - empty + * - readwrite + * - readonly + * + * When referencing select lists and radio buttons in remote conditions, a + * 'value' condition must be used: + * @code + * '#states' => array( + * // Show the settings if 'bar' has been selected for 'foo'. + * 'visible' => array( + * ':input[name="foo"]' => array('value' => 'bar'), + * ), + * ), + * @endcode + * + * @param $elements + * A renderable array element having a #states property as described above. + * + * @see form_example_states_form() */ function drupal_process_states(&$elements) { $elements['#attached']['js']['misc/states.js'] = array('weight' => JS_LIBRARY + 1); @@ -4157,46 +4290,70 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro } /** - * Aggregate JS files, putting them in the files directory. + * Aggregates JavaScript files into a cache file in the files directory. + * + * The file name for the JavaScript cache file is generated from the hash of + * the aggregated contents of the files in $files. This forces proxies and + * browsers to download new JavaScript when the JavaScript changes. + * + * The cache file name is retrieved on a page load via a lookup variable that + * contains an associative array. The array key is the hash of the names in + * $files while the value is the cache file name. The cache file is generated + * in two cases. First, if there is no file name value for the key, which will + * happen if a new file name has been added to $files or after the lookup + * variable is emptied to force a rebuild of the cache. Second, the cache + * file is generated if it is missing on disk. Old cache files are not deleted + * immediately when the lookup variable is emptied, but are deleted after a set + * period by drupal_delete_file_if_stale(). This ensures that files referenced + * by a cached page will still be available. * * @param $files - * An array of JS files to aggregate and compress into one file. - * @param $filename - * The name of the aggregate JS file. + * An array of JavaScript files to aggregate and compress into one file. + * * @return - * The name of the JS file, or FALSE if the file could not be saved. + * The URI of the cache file, or FALSE if the file could not be saved. */ -function drupal_build_js_cache($files, $filename) { +function drupal_build_js_cache($files) { $contents = ''; + $uri = ''; + $map = variable_get('drupal_js_cache_files', array()); + $key = hash('sha256', serialize($files)); + if (isset($map[$key])) { + $uri = $map[$key]; + } - // Create the js/ within the files folder. - $jspath = 'public://js'; - $uri = $jspath . '/' . $filename; - - if (!file_exists($uri)) { + if (empty($uri) || !file_exists($uri)) { // Build aggregate JS file. foreach ($files as $path => $info) { if ($info['preprocess']) { - // Append a ';' after each JS file to prevent them from running together. - $contents .= file_get_contents($path) . ';'; + // Append a ';' and a newline after each JS file to prevent them from running together. + $contents .= file_get_contents($path) . ";\n"; } } - + // Prefix filename to prevent blocking by firewalls which reject files + // starting with "ad*". + $filename = 'js_' . drupal_hash_base64($contents) . '.js'; + // Create the js/ within the files folder. + $jspath = 'public://js'; + $uri = $jspath . '/' . $filename; // Create the JS file. file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); if (!file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { return FALSE; } + $map[$key] = $uri; + variable_set('drupal_js_cache_files', $map); } return $uri; } /** - * Delete all cached JS files. + * Deletes old cached JavaScript files and variables. */ function drupal_clear_js_cache() { - file_scan_directory('public://js', '/.*/', array('callback' => 'file_unmanaged_delete')); - variable_set('javascript_parsed', array()); + variable_del('javascript_parsed'); + variable_del('drupal_js_cache_files'); + file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale')); } /** @@ -4232,53 +4389,14 @@ function drupal_json_decode($var) { * (optional) If set, the variable will be converted to JSON and output. */ function drupal_json_output($var = NULL) { - // We are returning JavaScript, so tell the browser. - drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8'); + // We are returning JSON, so tell the browser. + drupal_add_http_header('Content-Type', 'application/json'); if (isset($var)) { echo drupal_json_encode($var); } } -/** - * Returns a string of highly randomized bytes (over the full 8-bit range). - * - * This function is better than simply calling mt_rand() or any other built-in - * PHP function because it can return a long string of bytes (compared to < 4 - * bytes normally from mt_rand()) and uses the best available pseudo-random source. - * - * @param $count - * The number of characters (bytes) to return in the string. - */ -function drupal_random_bytes($count) { - // $random_state does not use drupal_static as it stores random bytes. - static $random_state; - // We initialize with the somewhat random PHP process ID on the first call. - if (empty($random_state)) { - $random_state = getmypid(); - } - $output = ''; - // /dev/urandom is available on many *nix systems and is considered the best - // commonly available pseudo-random source. - if ($fh = @fopen('/dev/urandom', 'rb')) { - $output = fread($fh, $count); - fclose($fh); - } - // If /dev/urandom is not available or returns no bytes, this loop will - // generate a good set of pseudo-random bytes on any system. - // Note that it may be important that our $random_state is passed - // through md5() prior to being rolled into $output, that the two md5() - // invocations are different, and that the extra input into the first one - - // the microtime() - is prepended rather than appended. This is to avoid - // directly leaking $random_state via the $output stream, which could - // allow for trivial prediction of further "random" numbers. - while (strlen($output) < $count) { - $random_state = md5(microtime() . mt_rand() . $random_state); - $output .= md5(mt_rand() . $random_state, TRUE); - } - return substr($output, 0, $count); -} - /** * Get a salt useful for hardening against SQL injection. * @@ -4289,7 +4407,7 @@ function drupal_get_hash_salt() { global $drupal_hash_salt, $databases; // If the $drupal_hash_salt variable is empty, a hash of the serialized // database credentials is used as a fallback salt. - return empty($drupal_hash_salt) ? sha1(serialize($databases)) : $drupal_hash_salt; + return empty($drupal_hash_salt) ? hash('sha256', serialize($databases)) : $drupal_hash_salt; } /** @@ -4300,7 +4418,7 @@ function drupal_get_hash_salt() { */ function drupal_get_private_key() { if (!($key = variable_get('drupal_private_key', 0))) { - $key = md5(drupal_random_bytes(64)); + $key = drupal_hash_base64(drupal_random_bytes(55)); variable_set('drupal_private_key', $key); } return $key; @@ -4313,10 +4431,7 @@ function drupal_get_private_key() { * An additional value to base the token on. */ function drupal_get_token($value = '') { - $private_key = drupal_get_private_key(); - // A single md5() is vulnerable to length-extension attacks, so use it twice. - // @todo: add md5 and sha1 hmac functions to core. - return md5(drupal_get_hash_salt() . md5(session_id() . $value . $private_key)); + return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt()); } /** @@ -4347,7 +4462,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc'); require_once DRUPAL_ROOT . '/includes/theme.inc'; require_once DRUPAL_ROOT . '/includes/pager.inc'; - require_once DRUPAL_ROOT . '/' . variable_get('menu_inc', '/includes/menu.inc'); + require_once DRUPAL_ROOT . '/' . variable_get('menu_inc', 'includes/menu.inc'); require_once DRUPAL_ROOT . '/includes/tablesort.inc'; require_once DRUPAL_ROOT . '/includes/file.inc'; require_once DRUPAL_ROOT . '/includes/unicode.inc'; @@ -4406,28 +4521,32 @@ function drupal_page_set_cache() { if (drupal_page_is_cacheable()) { $cache = (object) array( 'cid' => $base_root . request_uri(), - 'data' => ob_get_clean(), + 'data' => array( + 'path' => $_GET['q'], + 'body' => ob_get_clean(), + 'title' => drupal_get_title(), + 'headers' => array(), + ), 'expire' => CACHE_TEMPORARY, 'created' => REQUEST_TIME, - 'headers' => array(), ); // Restore preferred header names based on the lower-case names returned // by drupal_get_http_header(). $header_names = _drupal_set_preferred_header_name(); foreach (drupal_get_http_header() as $name_lower => $value) { - $cache->headers[$header_names[$name_lower]] = $value; + $cache->data['headers'][$header_names[$name_lower]] = $value; if ($name_lower == 'expires') { // Use the actual timestamp from an Expires header if available. $cache->expire = strtotime($value); } } - if ($cache->data) { + if ($cache->data['body']) { if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) { - $cache->data = gzencode($cache->data, 9, FORCE_GZIP); + $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP); } - cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire, $cache->headers); + cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire); } return $cache; } @@ -4534,31 +4653,51 @@ function drupal_cron_cleanup() { } /** - * Return an array of system file objects. - * - * Returns an array of file objects of the given type from the site-wide - * directory (i.e. modules/), the all-sites directory (i.e. sites/all/modules/), - * the profiles directory (i.e. profiles/your_site_profile/modules/), and the - * site-specific directory (i.e. sites/your_site_dir/modules/). The returned - * array will be keyed using the key specified (uri, filename, or name). Using - * name or filename will cause site-specific files to be prioritized over - * similar files in the default directories. That is, if a file with the same - * name appears in both the site-wide directory and site-specific directory, - * only the site-specific version will be included. - * - * @param $mask - * The preg_match() regular expression of the files to find. - * @param $directory + * Returns information about system object files (modules, themes, etc.). + * + * This function is used to find all or some system object files (module files, + * theme files, etc.) that exist on the site. It searches in several locations, + * depending on what type of object you are looking for. For instance, if you + * are looking for modules and call: + * @code + * drupal_system_listing("/\.module$/", "modules", 'name', 0); + * @endcode + * this function will search the site-wide modules directory (i.e., /modules/), + * your install profile's directory (i.e., + * /profiles/your_site_profile/modules/), the all-sites directory (i.e., + * /sites/all/modules/), and your site-specific directory (i.e., + * /sites/your_site_dir/modules/), in that order, and return information about + * all of the files ending in .module in those directories. + * + * The information is returned in an associative array, which can be keyed on + * the file name ($key = 'filename'), the file name without the extension ($key + * = 'name'), or the full file stream URI ($key = 'uri'). If you use a key of + * 'filename' or 'name', files found later in the search will take precedence + * over files found earlier; if you choose a key of 'uri', you will get all + * files found. + * + * @param string $mask + * The preg_match() regular expression for the files to find. + * @param string $directory * The subdirectory name in which the files are found. For example, - * 'modules' will search in both modules/ and - * sites/somesite/modules/. - * @param $key - * The key to be passed to file_scan_directory(). - * @param $min_depth - * Minimum depth of directories to return files from. - * - * @return - * An array of file objects of the specified type. + * 'modules' will search in sub-directories of the top-level /modules + * directory, sub-directories of /sites/all/modules/, etc. + * @param string $key + * The key to be used for the associative array returned. Possible values are + * 'uri', for the file's URI; 'filename', for the basename of the file; and + * 'name' for the name of the file without the extension. If you choose 'name' + * or 'filename', only the highest-precedence file will be returned. + * @param int $min_depth + * Minimum depth of directories to return files from, relative to each + * directory searched. For instance, a minimum depth of 2 would find modules + * inside /modules/node/tests, but not modules directly in /modules/node. + * + * @return array + * An associative array of file objects, keyed on the chosen key. Each element + * in the array is an object containing file information, with properties: + * - 'uri': Full URI of the file. + * - 'filename': File name. + * - 'name': Name of file without the extension. */ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { $config = conf_path(); @@ -4836,7 +4975,7 @@ function drupal_render_page($page) { * using uasort(). Since this is expensive, when passing already sorted * elements to drupal_render(), for example from a database query, set * $elements['#sorted'] = TRUE to avoid sorting them a second time. - * + * * drupal_render() flags each element with a '#printed' status to indicate that * the element has been rendered, which allows individual elements of a given * array to be rendered independently and prevents them from being rendered @@ -5127,7 +5266,7 @@ function drupal_render_cache_set(&$markup, $elements) { function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORARY, $granularity = NULL) { $cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity)); $query->preExecute(); - $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments()))); + $cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments()))); return array( '#query' => $query, '#pre_render' => array($function . '_pre_render'), @@ -5378,7 +5517,19 @@ function drupal_common_theme() { 'variables' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()), ), 'image' => array( - 'variables' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => array(), 'getsize' => TRUE), + // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft + // allows the alt attribute to be omitted in some cases. Therefore, + // default the alt attribute to an empty string, but allow code calling + // theme('image') to pass explicit NULL for it 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 the alt 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 + // The title attribute is optional in all cases, so it is omitted by + // default. + 'variables' => array('path' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => array(), 'getsize' => TRUE), ), 'breadcrumb' => array( 'variables' => array('breadcrumb' => NULL), @@ -5389,9 +5540,6 @@ function drupal_common_theme() { 'table' => array( 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''), ), - 'table_select_header_cell' => array( - 'variables' => array(), - ), 'tablesort_indicator' => array( 'variables' => array('style' => NULL), ), @@ -6061,10 +6209,15 @@ function drupal_flush_all_caches() { registry_rebuild(); drupal_clear_css_cache(); drupal_clear_js_cache(); + + // Rebuild the theme data. Note that the module data is rebuilt above, as + // part of registry_rebuild(). system_rebuild_theme_data(); drupal_theme_rebuild(); + menu_rebuild(); node_types_rebuild(); + // Don't clear cache_form - in-progress form submissions may break. // Ordered so clearing the page cache will always be the last action. $core = array('cache', 'cache_filter', 'cache_bootstrap', 'cache_page'); @@ -6299,7 +6452,7 @@ function entity_extract_ids($entity_type, $entity) { * containing these elements: * 0: primary id of the entity * 1: revision id of the entity, or NULL if $entity_type is not versioned - * 2: bundle name of the entity + * 2: bundle name of the entity, or NULL if $entity_type has no bundles * @return * An entity structure, initialized with the ids provided. */ @@ -6307,10 +6460,10 @@ function entity_create_stub_entity($entity_type, $ids) { $entity = new stdClass(); $info = entity_get_info($entity_type); $entity->{$info['entity keys']['id']} = $ids[0]; - if (isset($info['entity keys']['revision']) && !is_null($ids[1])) { + if (!empty($info['entity keys']['revision']) && !is_null($ids[1])) { $entity->{$info['entity keys']['revision']} = $ids[1]; } - if ($info['entity keys']['bundle']) { + if (!empty($info['entity keys']['bundle']) && !is_null($ids[2])) { $entity->{$info['entity keys']['bundle']} = $ids[2]; } return $entity; diff --git a/includes/database/database.inc b/includes/database/database.inc index 5c771b18d..8c2891f6c 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.119 2010/04/26 14:12:59 dries Exp $ +// $Id: database.inc,v 1.122 2010/05/05 16:51:30 dries Exp $ /** * @file @@ -559,7 +559,6 @@ abstract class DatabaseConnection extends PDO { } } catch (PDOException $e) { - _db_check_install_needed(); if ($options['throw_exception']) { // Add additional debug information. if ($query instanceof DatabaseStatementInterface) { @@ -779,6 +778,20 @@ abstract class DatabaseConnection extends PDO { return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); } + /** + * Escapes a field name string. + * + * Force all field names to be strictly alphanumeric-plus-underscore. + * For some database drivers, it may also wrap the field name in + * database-specific escape characters. + * + * @return + * The sanitized field name string. + */ + public function escapeField($field) { + return preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + } + /** * Escapes characters that work as wildcard characters in a LIKE pattern. * @@ -1408,8 +1421,6 @@ abstract class Database { final public static function parseConnectionInfo() { global $databases; - _db_check_install_needed(); - $database_info = is_array($databases) ? $databases : array(); foreach ($database_info as $index => $info) { foreach ($database_info[$index] as $target => $value) { @@ -1496,46 +1507,38 @@ abstract class Database { if (empty(self::$databaseInfo)) { self::parseConnectionInfo(); } - try { - // If the requested database does not exist then it is an unrecoverable - // error. - if (!isset(self::$databaseInfo[$key])) { - throw new DatabaseConnectionNotDefinedException('The specified database connection is not defined: ' . $key); - } - if (!$driver = self::$databaseInfo[$key][$target]['driver']) { - throw new DatabaseDriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); - } + // If the requested database does not exist then it is an unrecoverable + // error. + if (!isset(self::$databaseInfo[$key])) { + throw new DatabaseConnectionNotDefinedException('The specified database connection is not defined: ' . $key); + } - // We cannot rely on the registry yet, because the registry requires an - // open database connection. - $driver_class = 'DatabaseConnection_' . $driver; - require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/database.inc'; - $new_connection = new $driver_class(self::$databaseInfo[$key][$target]); - $new_connection->setTarget($target); - - // If we have any active logging objects for this connection key, we need - // to associate them with the connection we just opened. - if (!empty(self::$logs[$key])) { - $new_connection->setLogger(self::$logs[$key]); - } + if (!$driver = self::$databaseInfo[$key][$target]['driver']) { + throw new DatabaseDriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); + } - // We need to pass around the simpletest database prefix in the request - // and we put that in the user_agent header. The header HMAC was already - // validated in bootstrap.inc. - if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) { - $db_prefix_string = is_array($db_prefix) ? $db_prefix['default'] : $db_prefix; - $db_prefix = $db_prefix_string . $matches[1]; - } - return $new_connection; + // We cannot rely on the registry yet, because the registry requires an + // open database connection. + $driver_class = 'DatabaseConnection_' . $driver; + require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/database.inc'; + $new_connection = new $driver_class(self::$databaseInfo[$key][$target]); + $new_connection->setTarget($target); + + // If we have any active logging objects for this connection key, we need + // to associate them with the connection we just opened. + if (!empty(self::$logs[$key])) { + $new_connection->setLogger(self::$logs[$key]); } - catch (Exception $e) { - // It is extremely rare that an exception will be generated here other - // than when installing. We therefore intercept it and try the installer, - // passing on the exception otherwise. - _db_check_install_needed(); - throw $e; + + // We need to pass around the simpletest database prefix in the request + // and we put that in the user_agent header. The header HMAC was already + // validated in bootstrap.inc. + if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) { + $db_prefix_string = is_array($db_prefix) ? $db_prefix['default'] : $db_prefix; + $db_prefix = $db_prefix_string . $matches[1]; } + return $new_connection; } /** @@ -1927,12 +1930,13 @@ interface DatabaseStatementInterface extends Traversable { * @param $fetch * The fetchmode to use. If set to PDO::FETCH_ASSOC, PDO::FETCH_NUM, or * PDO::FETCH_BOTH the returned value with be an array of arrays. For any - * other value it will be an array of objects. + * other value it will be an array of objects. By default, the fetch mode + * set for the query will be used. * * @return * An associative array. */ - public function fetchAllAssoc($key, $fetch = PDO::FETCH_OBJ); + public function fetchAllAssoc($key, $fetch = NULL); } /** @@ -1998,19 +2002,22 @@ class DatabaseStatementBase extends PDOStatement implements DatabaseStatementInt return $this->fetchAll(PDO::FETCH_COLUMN, $index); } - public function fetchAllAssoc($key, $fetch = PDO::FETCH_OBJ) { + public function fetchAllAssoc($key, $fetch = NULL) { $return = array(); - $this->setFetchMode($fetch); - if (in_array($fetch, array(PDO::FETCH_ASSOC, PDO::FETCH_NUM, PDO::FETCH_BOTH))) { - foreach ($this as $record) { - $return[$record[$key]] = $record; + if (isset($fetch)) { + if (is_string($fetch)) { + $this->setFetchMode(PDO::FETCH_CLASS, $fetch); } - } - else { - foreach ($this as $record) { - $return[$record->$key] = $record; + else { + $this->setFetchMode($fetch); } } + + foreach ($this as $record) { + $record_key = is_object($record) ? $record->$key : $record[$key]; + $return[$record_key] = $record; + } + return $return; } @@ -2091,7 +2098,7 @@ class DatabaseStatementEmpty implements Iterator, DatabaseStatementInterface { return array(); } - public function fetchAllAssoc($key, $fetch = PDO::FETCH_OBJ) { + public function fetchAllAssoc($key, $fetch = NULL) { return array(); } @@ -2430,7 +2437,7 @@ function db_set_active($key = 'default') { } /** - * Restricts a dynamic table, column, or constraint name to safe characters. + * Restricts a dynamic table name to safe characters. * * Only keeps alphanumeric and underscores. * @@ -2444,6 +2451,21 @@ function db_escape_table($table) { return Database::getConnection()->escapeTable($table); } +/** + * Restricts a dynamic column or constraint name to safe characters. + * + * Only keeps alphanumeric and underscores. + * + * @param $field + * The field name to escape. + * + * @return + * The escaped field name as a string. + */ +function db_escape_field($field) { + return Database::getConnection()->escapeField($field); +} + /** * Escapes characters that work as wildcard characters in a LIKE pattern. * @@ -2900,17 +2922,3 @@ function db_ignore_slave() { } } -/** - * Redirects the user to the installation script. - * - * This will check if Drupal has not been installed yet (i.e., if no $databases - * array has been defined in the settings.php file) and we are not already - * installing. If both are true, the user is redirected to install.php. - */ -function _db_check_install_needed() { - global $databases; - if (empty($databases) && !drupal_installation_attempted()) { - include_once DRUPAL_ROOT . '/includes/install.inc'; - install_goto('install.php'); - } -} diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index d868cec41..856e31200 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.26 2010/03/07 08:03:44 webchick Exp $ +// $Id: database.inc,v 1.27 2010/05/05 06:28:39 webchick Exp $ /** * @file @@ -20,14 +20,17 @@ class DatabaseConnection_mysql extends DatabaseConnection { // MySQL never supports transactional DDL. $this->transactionalDDLSupport = FALSE; - // Default to TCP connection on port 3306. - if (empty($connection_options['port'])) { - $connection_options['port'] = 3306; - } - $this->connectionOptions = $connection_options; - $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . $connection_options['port'] . ';dbname=' . $connection_options['database']; + // The DSN should use either a socket or a host/port. + if (isset($connection_options['unix_socket'])) { + $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket']; + } + else { + // Default to TCP connection on port 3306. + $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']); + } + $dsn .= ';dbname=' . $connection_options['database']; parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( // So we don't have to mess around with cursors and unbuffered queries by default. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index cbf6dd074..70770f4a0 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.15 2010/04/19 04:43:05 webchick Exp $ +// $Id: query.inc,v 1.17 2010/05/15 07:04:21 dries Exp $ /** * @ingroup database @@ -34,7 +34,7 @@ class InsertQuery_mysql extends InsertQuery { $values = $this->fromQuery->getArguments(); } - $last_insert_id = $this->connection->query((string)$this, $values, $this->queryOptions); + $last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions); // Re-initialize the values array so that we can re-use this query. $this->insertValues = array(); @@ -43,6 +43,8 @@ class InsertQuery_mysql extends InsertQuery { } public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -50,10 +52,10 @@ class InsertQuery_mysql extends InsertQuery { // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. if (!empty($this->fromQuery)) { - return "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } - $query = "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; + $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; $max_placeholder = 0; $values = array(); @@ -138,11 +140,13 @@ class MergeQuery_mysql extends MergeQuery { // // @link http ://dev.mysql.com/doc/refman/5.0/en/mysql-affected-rows.html $this->queryOptions['return'] = Database::RETURN_AFFECTED; - return $this->connection->query((string)$this, $values, $this->queryOptions); + return $this->connection->query((string) $this, $values, $this->queryOptions); } public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; // Set defaults. if ($this->updateFields) { @@ -164,7 +168,7 @@ class MergeQuery_mysql extends MergeQuery { $insert_fields = $this->insertFields + $this->keyFields; - $query = "INSERT INTO {" . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES '; + $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES '; $max_placeholder = 0; $values = array(); diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index 6c5cd9717..a4a2cc7b9 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.36 2010/03/07 08:03:45 webchick Exp $ +// $Id: database.inc,v 1.37 2010/04/29 03:48:16 webchick Exp $ /** * @file @@ -90,7 +90,6 @@ class DatabaseConnection_pgsql extends DatabaseConnection { } } catch (PDOException $e) { - _db_check_install_needed(); if ($options['throw_exception']) { // Add additional debug information. if ($query instanceof DatabaseStatementInterface) { diff --git a/includes/database/pgsql/install.inc b/includes/database/pgsql/install.inc index f7b323bb8..f3b47b7ea 100644 --- a/includes/database/pgsql/install.inc +++ b/includes/database/pgsql/install.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.inc,v 1.6 2010/01/30 07:59:24 dries Exp $ +// $Id: install.inc,v 1.7 2010/05/13 08:23:56 dries Exp $ /** * @file @@ -84,14 +84,6 @@ class DatabaseTasks_pgsql extends DatabaseTasks { LANGUAGE \'sql\'' ); } - db_query('CREATE OR REPLACE FUNCTION "if"(boolean, text, text) RETURNS text AS - \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\' - LANGUAGE \'sql\'' - ); - db_query('CREATE OR REPLACE FUNCTION "if"(boolean, integer, integer) RETURNS integer AS - \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\' - LANGUAGE \'sql\'' - ); db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\' diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index 96f212266..dd2d6345e 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.16 2009/09/11 02:47:11 webchick Exp $ +// $Id: query.inc,v 1.18 2010/05/15 07:04:21 dries Exp $ /** * @ingroup database @@ -19,7 +19,7 @@ class InsertQuery_pgsql extends InsertQuery { return NULL; } - $stmt = $this->connection->prepareQuery((string)$this); + $stmt = $this->connection->prepareQuery((string) $this); // Fetch the list of blobs and sequences used on that table. $table_information = $this->connection->schema()->queryTableInformation($this->table); @@ -71,6 +71,8 @@ class InsertQuery_pgsql extends InsertQuery { } public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -78,10 +80,10 @@ class InsertQuery_pgsql extends InsertQuery { // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. if (!empty($this->fromQuery)) { - return "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } - $query = "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; + $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; $max_placeholder = 0; $values = array(); @@ -121,7 +123,7 @@ class UpdateQuery_pgsql extends UpdateQuery { // Because we filter $fields the same way here and in __toString(), the // placeholders will all match up properly. - $stmt = $this->connection->prepareQuery((string)$this); + $stmt = $this->connection->prepareQuery((string) $this); // Fetch the list of blobs and sequences used on that table. $table_information = $this->connection->schema()->queryTableInformation($this->table); diff --git a/includes/database/prefetch.inc b/includes/database/prefetch.inc index 4a07e784d..a314eacf0 100644 --- a/includes/database/prefetch.inc +++ b/includes/database/prefetch.inc @@ -1,5 +1,5 @@ <?php -// $Id: prefetch.inc,v 1.8 2010/03/26 17:14:45 dries Exp $ +// $Id: prefetch.inc,v 1.10 2010/04/30 13:47:46 dries Exp $ /** * @file @@ -175,7 +175,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface // as soon as possible. $this->rowCount = $statement->rowCount(); $this->data = $statement->fetchAll(PDO::FETCH_ASSOC); - // Destroy the statement as soon as possible. See + // Destroy the statement as soon as possible. See // DatabaseConnection_sqlite::PDOPrepare() for explanation. unset($statement); @@ -475,8 +475,8 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface return $result; } - public function fetchAllAssoc($key, $fetch_style = PDO::FETCH_OBJ) { - $this->fetchStyle = $fetch_style; + public function fetchAllAssoc($key, $fetch_style = NULL) { + $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; $this->fetchOptions = $this->defaultFetchOptions; $result = array(); diff --git a/includes/database/query.inc b/includes/database/query.inc index ead5318b6..f8e5fdd0a 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.46 2010/04/19 04:43:05 webchick Exp $ +// $Id: query.inc,v 1.49 2010/05/15 07:04:21 dries Exp $ /** * @ingroup database @@ -21,10 +21,11 @@ interface QueryConditionInterface { * * This method can take a variable number of parameters. If called with two * parameters, they are taken as $field and $value with $operator having a value - * of =. + * of IN if $value is an array and = otherwise. * * @param $field - * The name of the field to check. + * The name of the field to check. If you would like to add a more complex + * condition involving operators or functions, use where(). * @param $value * The value to test the field against. In most cases, this is a scalar. For more * complex options, it is an array. The meaning of each element in the array is @@ -239,6 +240,13 @@ abstract class Query implements QueryPlaceholderInterface { */ protected $nextPlaceholder = 0; + /** + * An array of comments that can be prepended to a query. + * + * @var array + */ + protected $comments = array(); + public function __construct(DatabaseConnection $connection, $options) { $this->connection = $connection; $this->queryOptions = $options; @@ -262,6 +270,44 @@ abstract class Query implements QueryPlaceholderInterface { public function nextPlaceholder() { return $this->nextPlaceholder++; } + + /** + * Adds a comment to the query. + * + * By adding a comment to a query, you can more easily find it in your + * query log or the list of active queries on an sql server. This allows + * for easier debugging and allows you to more easily find where a query + * with a performance problem is being generated. + * + * @param $comment + * The comment string to be inserted into the query. + * @return Query + * The called object. + */ + public function comment($comment) { + $this->comments[] = $comment; + return $this; + } + + /** + * Returns a reference to the comments array for the query. + * + * Because this method returns by reference, alter hooks may edit the comments + * array directly to make their changes. If just adding comments, however, the + * use of comment() is preferred. + * + * Note that this method must be called by reference as well: + * + * @code + * $comments =& $query->getComments(); + * @endcode + * + * @return + * A reference to the comments array structure. + */ + public function &getComments() { + return $this->comments; + } } /** @@ -441,7 +487,7 @@ class InsertQuery extends Query { // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. if (!empty($this->fromQuery)) { - $sql = (string)$this; + $sql = (string) $this; // The SelectQuery may contain arguments, load and pass them through. return $this->connection->query($sql, $this->fromQuery->getArguments(), $this->queryOptions); } @@ -452,7 +498,7 @@ class InsertQuery extends Query { // we wrap it in a transaction so that it is atomic where possible. On many // databases, such as SQLite, this is also a notable performance boost. $transaction = $this->connection->startTransaction(); - $sql = (string)$this; + $sql = (string) $this; foreach ($this->insertValues as $insert_values) { $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions); } @@ -467,11 +513,14 @@ class InsertQuery extends Query { public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); if (!empty($this->fromQuery)) { - return "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; } // For simplicity, we will use the $placeholders array to inject @@ -481,7 +530,7 @@ class InsertQuery extends Query { $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); $placeholders = array_pad($placeholders, count($this->insertFields), '?'); - return 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')'; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')'; } /** @@ -779,7 +828,7 @@ class MergeQuery extends Query { } $select = $select->countQuery(); - $sql = (string)$select; + $sql = (string) $select; $arguments = $select->getArguments(); $num_existing = $this->connection->query($sql, $arguments)->fetchField(); @@ -895,11 +944,15 @@ class DeleteQuery extends Query implements QueryConditionInterface { $values = $this->condition->arguments(); } - return $this->connection->query((string)$this, $values, $this->queryOptions); + return $this->connection->query((string) $this, $values, $this->queryOptions); } public function __toString() { - $query = 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; + + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + + $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; if (count($this->condition)) { @@ -935,11 +988,14 @@ class TruncateQuery extends Query { } public function execute() { - return $this->connection->query((string)$this, array(), $this->queryOptions); + return $this->connection->query((string) $this, array(), $this->queryOptions); } public function __toString() { - return 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + + return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; } } @@ -1095,10 +1151,14 @@ class UpdateQuery extends Query implements QueryConditionInterface { $update_values = array_merge($update_values, $this->condition->arguments()); } - return $this->connection->query((string)$this, $update_values, $this->queryOptions); + return $this->connection->query((string) $this, $update_values, $this->queryOptions); } public function __toString() { + + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Expressions take priority over literal fields, so we process those first // and remove any literal fields that conflict. $fields = $this->fields; @@ -1113,7 +1173,7 @@ class UpdateQuery extends Query implements QueryConditionInterface { $update_fields[] = $field . '=:db_update_placeholder_' . ($max_placeholder++); } - $query = 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); + $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); if (count($this->condition)) { $this->condition->compile($this->connection, $this); @@ -1217,7 +1277,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { if ($condition['field'] instanceof QueryConditionInterface) { // Compile the sub-condition recursively and add it to the list. $condition['field']->compile($connection, $queryPlaceholder); - $condition_fragments[] = '(' . (string)$condition['field'] . ')'; + $condition_fragments[] = '(' . (string) $condition['field'] . ')'; $arguments += $condition['field']->arguments(); } else { @@ -1239,7 +1299,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { $placeholders = array(); if ($condition['value'] instanceof SelectQueryInterface) { $condition['value']->compile($connection, $queryPlaceholder); - $placeholders[] = (string)$condition['value']; + $placeholders[] = (string) $condition['value']; $arguments += $condition['value']->arguments(); } // We assume that if there is a delimiter, then the value is an @@ -1255,7 +1315,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { $placeholders[] = $placeholder; } } - $condition_fragments[] = ' (' . $condition['field'] . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; + $condition_fragments[] = ' (' . $connection->escapeField($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; } } } diff --git a/includes/database/schema.inc b/includes/database/schema.inc index 012b74afa..8f3b80679 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -1,5 +1,5 @@ <?php -// $Id: schema.inc,v 1.34 2010/04/07 15:07:59 dries Exp $ +// $Id: schema.inc,v 1.35 2010/04/28 20:25:21 dries Exp $ /** * @file @@ -604,9 +604,9 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { if ($this->tableExists($name)) { throw new DatabaseSchemaObjectExistsException(t('Table %name already exists.', array('%name' => $name))); } - $statements = $this->createTableSql($name, $table); + $statements = $this->createTableSql($name, $table); foreach ($statements as $statement) { - $this->connection->query($statement); + $this->connection->query($statement); } } diff --git a/includes/database/select.inc b/includes/database/select.inc index 61c85c68d..6e2954175 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1,5 +1,5 @@ <?php -// $Id: select.inc,v 1.35 2010/04/11 18:33:43 dries Exp $ +// $Id: select.inc,v 1.41 2010/05/15 07:04:21 dries Exp $ /** * @ingroup database @@ -232,7 +232,9 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * this clause should use a named placeholder and the value or values to * insert should be passed in the 4th parameter. For the first table joined * on a query, this value is ignored as the first table is taken as the base - * table. + * table. The token %alias can be used in this string to be replaced with + * the actual alias. This is useful when $alias is modified by the database + * system, for example, when joining the same table more than once. * @param $arguments * An array of arguments to replace into the $condition of this join. * @return @@ -253,7 +255,9 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * this clause should use a named placeholder and the value or values to * insert should be passed in the 4th parameter. For the first table joined * on a query, this value is ignored as the first table is taken as the base - * table. + * table. The token %alias can be used in this string to be replaced with + * the actual alias. This is useful when $alias is modified by the database + * system, for example, when joining the same table more than once. * @param $arguments * An array of arguments to replace into the $condition of this join. * @return @@ -274,7 +278,9 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * this clause should use a named placeholder and the value or values to * insert should be passed in the 4th parameter. For the first table joined * on a query, this value is ignored as the first table is taken as the base - * table. + * table. The token %alias can be used in this string to be replaced with + * the actual alias. This is useful when $alias is modified by the database + * system, for example, when joining the same table more than once. * @param $arguments * An array of arguments to replace into the $condition of this join. * @return @@ -295,7 +301,9 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * this clause should use a named placeholder and the value or values to * insert should be passed in the 4th parameter. For the first table joined * on a query, this value is ignored as the first table is taken as the base - * table. + * table. The token %alias can be used in this string to be replaced with + * the actual alias. This is useful when $alias is modified by the database + * system, for example, when joining the same table more than once. * @param $arguments * An array of arguments to replace into the $condition of this join. * @return @@ -324,7 +332,9 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * this clause should use a named placeholder and the value or values to * insert should be passed in the 4th parameter. For the first table joined * on a query, this value is ignored as the first table is taken as the base - * table. + * table. The token %alias can be used in this string to be replaced with + * the actual alias. This is useful when $alias is modified by the database + * system, for example, when joining the same table more than once. * @param $arguments * An array of arguments to replace into the $condition of this join. * @return @@ -444,6 +454,29 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn */ public function preExecute(SelectQueryInterface $query = NULL); + /** + * Helper function to build most common HAVING conditional clauses. + * + * This method can take a variable number of parameters. If called with two + * parameters, they are taken as $field and $value with $operator having a value + * of IN if $value is an array and = otherwise. + * + * @param $field + * The name of the field to check. If you would like to add a more complex + * condition involving operators or functions, use having(). + * @param $value + * The value to test the field against. In most cases, this is a scalar. For more + * complex options, it is an array. The meaning of each element in the array is + * dependent on the $operator. + * @param $operator + * The comparison operator, such as =, <, or >=. It also accepts more complex + * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array + * = otherwise. + * @return QueryConditionInterface + * The called object. + */ + public function havingCondition($field, $value = NULL, $operator = NULL); + /** * Clone magic method. * @@ -727,7 +760,7 @@ class SelectQueryExtender implements SelectQueryInterface { } public function __toString() { - return (string)$this->query; + return (string) $this->query; } public function __clone() { @@ -906,10 +939,7 @@ class SelectQuery extends Query implements SelectQueryInterface { /* Implementations of QueryConditionInterface for the WHERE clause. */ public function condition($field, $value = NULL, $operator = NULL) { - if (!isset($num_args)) { - $num_args = func_num_args(); - } - $this->where->condition($field, $value, $operator, $num_args); + $this->where->condition($field, $value, $operator); return $this; } @@ -942,11 +972,8 @@ class SelectQuery extends Query implements SelectQueryInterface { /* Implementations of QueryConditionInterface for the HAVING clause. */ - public function havingCondition($field, $value = NULL, $operator = '=') { - if (!isset($num_args)) { - $num_args = func_num_args(); - } - $this->having->condition($field, $value, $operator, $num_args); + public function havingCondition($field, $value = NULL, $operator = NULL) { + $this->having->condition($field, $value, $operator); return $this; } @@ -1069,10 +1096,11 @@ class SelectQuery extends Query implements SelectQueryInterface { // Modules may alter all queries or only those having a particular tag. if (isset($this->alterTags)) { - drupal_alter('query', $query); + $hooks = array('query'); foreach ($this->alterTags as $tag => $value) { - drupal_alter("query_$tag", $query); + $hooks[] = 'query_' . $tag; } + drupal_alter($hooks, $query); } return $this->prepared = TRUE; } @@ -1085,7 +1113,7 @@ class SelectQuery extends Query implements SelectQueryInterface { } $args = $this->getArguments(); - return $this->connection->query((string)$this, $args, $this->queryOptions); + return $this->connection->query((string) $this, $args, $this->queryOptions); } public function distinct($distinct = TRUE) { @@ -1192,6 +1220,10 @@ class SelectQuery extends Query implements SelectQueryInterface { } $alias = $alias_candidate; + if (is_string($condition)) { + $condition = str_replace('%alias', $alias, $condition); + } + $this->tables[$alias] = array( 'join type' => $type, 'table' => $table, @@ -1285,8 +1317,11 @@ class SelectQuery extends Query implements SelectQueryInterface { public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // SELECT - $query = 'SELECT '; + $query = $comments . 'SELECT '; if ($this->distinct) { $query .= 'DISTINCT '; } @@ -1295,13 +1330,13 @@ class SelectQuery extends Query implements SelectQueryInterface { $fields = array(); foreach ($this->tables as $alias => $table) { if (!empty($table['all_fields'])) { - $fields[] = $alias . '.*'; + $fields[] = $this->connection->escapeTable($alias) . '.*'; } } foreach ($this->fields as $alias => $field) { // Always use the AS keyword for field aliases, as some // databases require it (e.g., PostgreSQL). - $fields[] = (isset($field['table']) ? $field['table'] . '.' : '') . $field['field'] . ' AS ' . $field['alias']; + $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeField($field['alias']); } foreach ($this->expressions as $alias => $expression) { $fields[] = $expression['expression'] . ' AS ' . $expression['alias']; @@ -1319,7 +1354,10 @@ class SelectQuery extends Query implements SelectQueryInterface { // If the table is a subquery, compile it and integrate it into this query. if ($table['table'] instanceof SelectQueryInterface) { - $table_string = '(' . (string)$table['table'] . ')'; + // Run preparation steps on this sub-query before converting to string. + $subquery = $table['table']; + $subquery->preExecute(); + $table_string = '(' . (string) $subquery . ')'; } else { $table_string = '{' . $this->connection->escapeTable($table['table']) . '}'; @@ -1327,7 +1365,7 @@ class SelectQuery extends Query implements SelectQueryInterface { // Don't use the AS keyword for table aliases, as some // databases don't support it (e.g., Oracle). - $query .= $table_string . ' ' . $table['alias']; + $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']); if (!empty($table['condition'])) { $query .= ' ON ' . $table['condition']; diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index d2ced9f62..8be26c148 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.9 2009/11/02 00:19:27 webchick Exp $ +// $Id: query.inc,v 1.10 2010/05/15 07:04:21 dries Exp $ /** * @file @@ -33,16 +33,19 @@ class InsertQuery_sqlite extends InsertQuery { } public function __toString() { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Produce as many generic placeholders as necessary. $placeholders = array_fill(0, count($this->insertFields), '?'); // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. if (!empty($this->fromQuery)) { - return "INSERT INTO {" . $this->table . '} (' . implode(', ', $this->insertFields) . ') ' . $this->fromQuery; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') ' . $this->fromQuery; } - return 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')'; + return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')'; } } @@ -143,7 +146,10 @@ class DeleteQuery_sqlite extends DeleteQuery { */ class TruncateQuery_sqlite extends TruncateQuery { public function __toString() { - return 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + + return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; } } diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index 515fc6aff..432ebd1e7 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -1,5 +1,5 @@ <?php -// $Id: schema.inc,v 1.16 2010/04/07 15:07:59 dries Exp $ +// $Id: schema.inc,v 1.17 2010/04/28 20:25:21 dries Exp $ /** * @file @@ -14,7 +14,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema { - /** + /** * Override DatabaseSchema::$defaultSchema */ protected $defaultSchema = 'main'; diff --git a/includes/entity.inc b/includes/entity.inc index 7c1ddf673..159c04e01 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -1,5 +1,5 @@ <?php -// $Id: entity.inc,v 1.7 2010/03/27 05:52:49 webchick Exp $ +// $Id: entity.inc,v 1.8 2010/05/09 13:27:31 dries Exp $ /** * Interface for entity controller classes. @@ -188,14 +188,6 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { // The id field is provided by entity, so remove it. unset($entity_revision_fields[$this->idKey]); - // Change timestamp to revision_timestamp, and revision uid to - // revision_uid before adding them to the query. - // TODO: This is node specific and has to be moved into NodeController. - unset($entity_revision_fields['timestamp']); - $query->addField('revision', 'timestamp', 'revision_timestamp'); - unset($entity_revision_fields['uid']); - $query->addField('revision', 'uid', 'revision_uid'); - // Remove all fields from the base table that are also fields by the same // name in the revision table. $entity_field_keys = array_flip($entity_fields); diff --git a/includes/file.inc b/includes/file.inc index 3e4ec953a..cef2b1500 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1,5 +1,5 @@ <?php -// $Id: file.inc,v 1.207 2010/04/10 17:30:15 dries Exp $ +// $Id: file.inc,v 1.209 2010/05/11 10:56:04 dries Exp $ /** * @file @@ -343,9 +343,21 @@ function file_create_url($uri) { $scheme = file_uri_scheme($uri); if (!$scheme) { - // If this is not a properly formatted stream, then it is a shipped file. - // Therefor, return the URI with the base URL prepended. - return $GLOBALS['base_url'] . '/' . $uri; + // Allow for: + // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) + // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to + // http://example.com/bar.jpg by the browser when viewing a page over + // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) + // Both types of relative URIs are characterized by a leading slash, hence + // we can use a single check. + if (drupal_substr($uri, 0, 1) == '/') { + return $uri; + } + else { + // If this is not a properly formatted stream, then it is a shipped file. + // Therefor, return the URI with the base URL prepended. + return $GLOBALS['base_url'] . '/' . $uri; + } } elseif ($scheme == 'http' || $scheme == 'https') { // Check for http so that we don't have to implement getExternalUrl() for @@ -409,7 +421,9 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) */ function file_ensure_htaccess() { file_create_htaccess('public://', FALSE); - file_create_htaccess('private://', TRUE); + if (variable_get('file_private_path', FALSE)) { + file_create_htaccess('private://', TRUE); + } file_create_htaccess('temporary://', TRUE); } @@ -1586,8 +1600,7 @@ function file_download() { $scheme = array_shift($args); $target = implode('/', $args); $uri = $scheme . '://' . $target; - - if (file_exists($uri)) { + if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) { // Let other modules provide headers and controls access to the file. $headers = module_invoke_all('file_download', $uri); if (in_array(-1, $headers)) { diff --git a/includes/form.inc b/includes/form.inc index a57d7ba87..21008c0c9 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1,5 +1,5 @@ <?php -// $Id: form.inc,v 1.454 2010/04/24 14:49:13 dries Exp $ +// $Id: form.inc,v 1.464 2010/05/19 19:22:24 dries Exp $ /** * @defgroup forms Form builder functions @@ -203,7 +203,7 @@ function drupal_build_form($form_id, &$form_state) { } $form = drupal_retrieve_form($form_id, $form_state); - $form_build_id = 'form-' . md5(uniqid(mt_rand(), TRUE)); + $form_build_id = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); $form['#build_id'] = $form_build_id; // Fix the form method, if it is 'get' in $form_state, but not in $form. @@ -331,7 +331,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { // Otherwise, a new #build_id is generated, to not clobber the previous // build's data in the form cache; also allowing the user to go back to an // earlier build, make changes, and re-submit. - $form['#build_id'] = isset($old_form['#build_id']) ? $old_form['#build_id'] : 'form-' . md5(mt_rand()); + $form['#build_id'] = isset($old_form['#build_id']) ? $old_form['#build_id'] : 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); // #action defaults to request_uri(), but in case of AJAX and other partial // rebuilds, the form is submitted to an alternate URL, and the original @@ -491,7 +491,7 @@ function form_state_keys_no_cache() { * $form_state['values']['body'] = 'This is the body text!'; * $form_state['values']['name'] = 'robo-user'; * $form_state['values']['op'] = t('Save'); - * drupal_form_submit('story_node_form', $form_state, (object)$node); + * drupal_form_submit('story_node_form', $form_state, (object) $node); * @endcode */ function drupal_form_submit($form_id, &$form_state) { @@ -651,7 +651,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { // We'll clear out the cached copies of the form and its stored data // here, as we've finished with them. The in-memory copies are still // here, though. - if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) { + if (!variable_get('cache', 0) && !empty($form_state['values']['form_build_id'])) { cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form'); cache_clear_all('storage_' . $form_state['values']['form_build_id'], 'cache_form'); } @@ -774,11 +774,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { } } - // Invoke hook_form_FORM_ID_alter() implementations. - drupal_alter('form_' . $form_id, $form, $form_state); - - // Invoke hook_form_alter() implementations. - drupal_alter('form', $form, $form_state, $form_id); + // Invoke hook_form_alter() and hook_form_FORM_ID_alter() implementations. + drupal_alter(array('form', 'form_' . $form_id), $form, $form_state, $form_id); } @@ -1294,6 +1291,13 @@ function form_builder($form_id, $element, &$form_state) { // Recurse through all child elements. $count = 0; foreach (element_children($element) as $key) { + // Prior to checking properties of child elements, their default properties + // need to be loaded. + if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = element_info($element[$key]['#type']))) { + $element[$key] += $info; + $element[$key]['#defaults_loaded'] = TRUE; + } + // Don't squash an existing tree value. if (!isset($element[$key]['#tree'])) { $element[$key]['#tree'] = $element['#tree']; @@ -1315,12 +1319,6 @@ function form_builder($form_id, $element, &$form_state) { $array_parents[] = $key; $element[$key]['#array_parents'] = $array_parents; - // Prior to handling #weight, default element properties need to be applied. - if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = element_info($element[$key]['#type']))) { - $element[$key] += $info; - $element[$key]['#defaults_loaded'] = TRUE; - } - // Assign a decimal placeholder weight to preserve original array order. if (!isset($element[$key]['#weight'])) { $element[$key]['#weight'] = $count/1000; @@ -1869,7 +1867,7 @@ function form_type_textfield_value($element, $input = FALSE) { */ function form_type_token_value($element, $input = FALSE) { if ($input !== FALSE) { - return (string)$input; + return (string) $input; } } @@ -2001,8 +1999,8 @@ function form_select_options($element, $choices = NULL) { $options .= form_select_options($element, $choice->option); } else { - $key = (string)$key; - if ($value_valid && (!$value_is_array && (string)$element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) { + $key = (string) $key; + if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) { $selected = ' selected="selected"'; } else { @@ -2177,8 +2175,8 @@ function form_process_password_confirm($element) { */ function password_confirm_validate($element, &$element_state) { $pass1 = trim($element['pass1']['#value']); - if (!empty($pass1)) { - $pass2 = trim($element['pass2']['#value']); + $pass2 = trim($element['pass2']['#value']); + if (!empty($pass1) || !empty($pass2)) { if (strcmp($pass1, $pass2)) { form_error($element, t('The specified passwords do not match.')); } @@ -2544,8 +2542,10 @@ function theme_tableselect($variables) { } // Add an empty header or a "Select all" checkbox to provide room for the // checkboxes/radios in the first table column. - $first_col = $element['#js_select'] ? array(theme('table_select_header_cell')) : array(''); - $header = array_merge($first_col, $header); + if ($element['#js_select']) { + drupal_add_js('misc/tableselect.js'); + array_unshift($header, array('class' => array('select-all'))); + } } return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $element['#empty'], 'attributes' => $element['#attributes'])); } @@ -2652,13 +2652,16 @@ function form_process_fieldset(&$element, &$form_state) { // Contains form element summary functionalities. $element['#attached']['js']['misc/form.js'] = array('weight' => JS_LIBRARY + 1); + // The .form-wrapper class is required for #states to treat fieldsets like + // containers. + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = array(); + } + $element['#attributes']['class'][] = 'form-wrapper'; + // Collapsible fieldsets if (!empty($element['#collapsible'])) { $element['#attached']['js'][] = 'misc/collapse.js'; - if (!isset($element['#attributes']['class'])) { - $element['#attributes']['class'] = array(); - } - $element['#attributes']['class'][] = 'form-wrapper'; $element['#attributes']['class'][] = 'collapsible'; if (!empty($element['#collapsed'])) { $element['#attributes']['class'][] = 'collapsed'; @@ -3029,49 +3032,46 @@ function theme_form_element($variables) { // This is also used in the installer, pre-database setup. $t = get_t(); + // Add element #id for #type 'item'. + if (isset($element['#markup']) && !empty($element['#id'])) { + $attributes['id'] = $element['#id']; + } // Add element's #type and #name as class to aid with JS/CSS selectors. - $class = array('form-item'); + $attributes['class'] = array('form-item'); if (!empty($element['#type'])) { - $class[] = 'form-type-' . strtr($element['#type'], '_', '-'); + $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); } if (!empty($element['#name'])) { - $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); + $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); } + $output = '<div' . drupal_attributes($attributes) . '>' . "\n"; // If #title is not set, we don't display any label or required marker. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; } - - $output = '<div class="' . implode(' ', $class) . '">' . "\n"; - - if (isset($element['#field_prefix'])) { - $output .= '<span class="field-prefix">' . $element['#field_prefix'] . '</span> '; - } + $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : ''; + $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : ''; switch ($element['#title_display']) { case 'before': $output .= ' ' . theme('form_element_label', $variables); - $output .= ' ' . $element['#children'] . "\n"; + $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; break; case 'invisible': case 'after': - $output .= ' ' . $element['#children']; + $output .= ' ' . $prefix . $element['#children'] . $suffix; $output .= ' ' . theme('form_element_label', $variables) . "\n"; break; case 'none': case 'attribute': // Output no label and no required marker, only the children. - $output .= ' ' . $element['#children'] . "\n"; + $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; break; } - if (isset($element['#field_suffix'])) { - $output .= ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>'; - } - if (!empty($element['#description'])) { $output .= ' <div class="description">' . $element['#description'] . "</div>\n"; } diff --git a/includes/install.core.inc b/includes/install.core.inc index 6fed3728d..5fb86f667 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.core.inc,v 1.13 2010/04/24 14:49:13 dries Exp $ +// $Id: install.core.inc,v 1.19 2010/05/18 18:11:13 dries Exp $ /** * @file @@ -1011,7 +1011,7 @@ function install_settings_form_submit($form, &$form_state) { 'required' => TRUE, ); $settings['drupal_hash_salt'] = array( - 'value' => sha1(drupal_random_bytes(64)), + 'value' => drupal_hash_base64(drupal_random_bytes(55)), 'required' => TRUE, ); drupal_rewrite_settings($settings); @@ -1082,7 +1082,7 @@ function _install_select_profile($profiles) { if (sizeof($profiles) == 1) { $profile = array_pop($profiles); // TODO: is this right? - require_once $profile->uri; + require_once DRUPAL_ROOT . '/' . $profile->uri; return $profile->name; } else { @@ -1332,11 +1332,7 @@ function install_load_profile(&$install_state) { * An array of information about the current installation state. */ function install_bootstrap_full(&$install_state) { - // Bootstrap newly installed Drupal, while preserving existing messages. - $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : ''; - drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); - $_SESSION['messages'] = $messages; } /** @@ -1479,11 +1475,6 @@ function install_finished(&$install_state) { $output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>'; $output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>'; - // Rebuild the module and theme data, in case any newly-installed modules - // need to modify it via hook_system_info_alter(). - system_rebuild_module_data(); - system_rebuild_theme_data(); - // Flush all caches to ensure that any full bootstraps during the installer // do not leave stale cached data, and that any content types or other items // registered by the install profile are registered correctly. @@ -1736,21 +1727,13 @@ function install_configure_form_submit($form, &$form_state) { } } - // Turn this off temporarily so that we can pass a password through. - variable_set('user_email_verification', FALSE); - $form_state['old_values'] = $form_state['values']; - $form_state['values'] = $form_state['values']['account']; - // We precreated user 1 with placeholder values. Let's save the real values. $account = user_load(1); - $merge_data = array('init' => $form_state['values']['mail'], 'roles' => array(), 'status' => 1); - user_save($account, array_merge($form_state['values'], $merge_data)); + $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1); + user_save($account, array_merge($form_state['values']['account'], $merge_data)); // Load global $user and perform final login tasks. $user = user_load(1); user_login_finalize(); - $form_state['values'] = $form_state['old_values']; - unset($form_state['old_values']); - variable_set('user_email_verification', TRUE); if (isset($form_state['values']['clean_url'])) { variable_set('clean_url', $form_state['values']['clean_url']); diff --git a/includes/install.inc b/includes/install.inc index a88d05c17..9095e8e76 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.inc,v 1.132 2010/04/24 14:53:59 dries Exp $ +// $Id: install.inc,v 1.135 2010/05/18 18:11:13 dries Exp $ /** * Indicates that a module has not been installed yet. @@ -367,7 +367,7 @@ abstract class DatabaseTasks { } } if (!empty($message)) { - $message = '<p>In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.</p>' . $message; + $message = '<p>In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.</p>' . $message; throw new DatabaseTaskException($message); } } @@ -384,7 +384,7 @@ abstract class DatabaseTasks { $this->pass('Drupal can CONNECT to the database ok.'); } catch (Exception $e) { - $this->fail(st('Failed to connect to your %name database server. %name reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), '%name' => $this->name()))); + $this->fail(st('Failed to connect to your %name database server. %name reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), '%name' => $this->name()))); return FALSE; } return TRUE; @@ -547,7 +547,7 @@ function drupal_verify_profile($install_state) { * functions can be made available while other modules are installed. */ function drupal_install_system() { - $system_path = dirname(drupal_get_filename('module', 'system', NULL)); + $system_path = drupal_get_path('module', 'system'); require_once DRUPAL_ROOT . '/' . $system_path . '/system.install'; module_invoke('system', 'install'); @@ -849,10 +849,14 @@ function install_goto($path) { * @see t() * @ingroup sanitization */ -function st($string, $args = array()) { +function st($string, array $args = array(), array $options = array()) { static $locale_strings = NULL; global $install_state; + if (empty($options['context'])) { + $options['context'] = ''; + } + if (!isset($locale_strings)) { $locale_strings = array(); if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) { @@ -883,7 +887,7 @@ function st($string, $args = array()) { case '!': } } - return strtr((!empty($locale_strings[$string]) ? $locale_strings[$string] : $string), $args); + return strtr((!empty($locale_strings[$options['context']][$string]) ? $locale_strings[$options['context']][$string] : $string), $args); } /** diff --git a/includes/iso.inc b/includes/iso.inc index 6b5baf0d7..9af86391d 100644 --- a/includes/iso.inc +++ b/includes/iso.inc @@ -1,5 +1,5 @@ <?php -// $Id: iso.inc,v 1.9 2010/04/17 12:55:08 dries Exp $ +// $Id: iso.inc,v 1.10 2010/04/30 08:15:56 dries Exp $ /** * @file @@ -376,7 +376,7 @@ function _locale_get_predefined_list() { 'ku' => array('Kurdish', 'Kurdî'), 'kv' => array('Komi'), 'kw' => array('Cornish'), - 'ky' => array('Kyrgyz', 'Кыргыз тили'), + 'ky' => array('Kyrgyz', 'Кыргызча'), 'la' => array('Latin', 'Latina'), 'lb' => array('Luxembourgish'), 'lg' => array('Luganda'), diff --git a/includes/language.inc b/includes/language.inc index 1b55a18c6..61bbbd76c 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -1,5 +1,5 @@ <?php -// $Id: language.inc,v 1.29 2010/03/10 19:36:14 webchick Exp $ +// $Id: language.inc,v 1.30 2010/05/12 08:26:14 dries Exp $ /** * @file @@ -283,7 +283,7 @@ function language_provider_invoke($provider_id, $provider = NULL) { // If the language 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', CACHE_DISABLED); + $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; diff --git a/includes/locale.inc b/includes/locale.inc index b5fe671de..a9c8281b5 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -1,5 +1,5 @@ <?php -// $Id: locale.inc,v 1.251 2010/04/09 12:14:25 dries Exp $ +// $Id: locale.inc,v 1.254 2010/05/01 08:12:22 dries Exp $ /** * @file @@ -683,7 +683,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL $languages = language_list(); if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) { // Since we only need to parse the header if we ought to update the - // plural formula, only run this if we don't need to keep existing + // plural formula, only run this if we don't need to keep existing // data untouched or if we don't have an existing plural formula. $header = _locale_import_parse_header($value['msgstr']); @@ -1391,7 +1391,7 @@ function _locale_export_po($language = NULL, $output = NULL) { header("Content-Disposition: attachment; filename=$filename"); header("Content-Type: text/plain; charset=utf-8"); print $output; - exit(); + drupal_exit(); } /** @@ -1680,7 +1680,7 @@ function _locale_rebuild_js($langcode = NULL) { } $data .= "'strings': " . drupal_json_encode($translations) . " };"; - $data_hash = md5($data); + $data_hash = drupal_hash_base64($data); } // Construct the filepath where JS translation files are stored. diff --git a/includes/lock.inc b/includes/lock.inc index c927a97f2..fe856e97f 100644 --- a/includes/lock.inc +++ b/includes/lock.inc @@ -1,5 +1,5 @@ <?php -// $Id: lock.inc,v 1.3 2010/02/24 19:59:32 dries Exp $ +// $Id: lock.inc,v 1.4 2010/05/06 15:20:18 dries Exp $ /** * @file @@ -18,7 +18,7 @@ * * This is a cooperative, advisory lock system. Any long-running operation * that could potentially be attempted in parallel by multiple requests should - * try to acquire a lock before proceeding. By obtainng a lock, one request + * try to acquire a lock before proceeding. By obtaining a lock, one request * notifies any other requests that a specific operation is in progress which * must not be executed in parallel. * diff --git a/includes/mail.inc b/includes/mail.inc index 3fefa09fc..e10bee72f 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -1,5 +1,5 @@ <?php -// $Id: mail.inc,v 1.30 2010/01/09 23:03:21 webchick Exp $ +// $Id: mail.inc,v 1.31 2010/05/04 15:11:21 dries Exp $ /** * @file @@ -575,7 +575,7 @@ function _drupal_html_to_text_pad($text, $pad, $prefix = '') { if (($p = strrpos($text, "\n")) === FALSE) { $p = -1; } - $n = max(0, 79 - (strlen($text) - $p)); + $n = max(0, 79 - (strlen($text) - $p) - strlen($prefix)); // Add prefix and padding, and restore linebreak. - return $text . $prefix . str_repeat($pad, $n - strlen($prefix)) . "\n"; + return $text . $prefix . str_repeat($pad, $n) . "\n"; } diff --git a/includes/menu.inc b/includes/menu.inc index 975a162e9..ce945481c 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1,5 +1,5 @@ <?php -// $Id: menu.inc,v 1.387 2010/04/26 14:06:23 dries Exp $ +// $Id: menu.inc,v 1.392 2010/05/17 18:47:25 dries Exp $ /** * @file @@ -407,9 +407,9 @@ function menu_get_item($path = NULL, $router_item = NULL) { $parts = array_slice($original_map, 0, MENU_MAX_PARTS); $ancestors = menu_get_ancestors($parts); - // Since there is no limit to the length of $path, but the cids are - // restricted to 255 characters, use md5() to keep it short yet unique. - $cid = 'menu_item:' . md5($path); + // Since there is no limit to the length of $path, use a hash to keep it + // short yet unique. + $cid = 'menu_item:' . hash('sha256', $path); if ($cached = cache_get($cid, 'cache_menu')) { $router_item = $cached->data; } @@ -574,7 +574,7 @@ function _menu_check_access(&$item, $map) { $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); // Check for a TRUE or FALSE value. if (is_numeric($callback)) { - $item['access'] = (bool)$callback; + $item['access'] = (bool) $callback; } else { $arguments = menu_unserialize($item['access_arguments'], $map); @@ -978,7 +978,7 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { // Use $mlid as a flag for whether the data being loaded is for the whole tree. $mlid = isset($link['mlid']) ? $link['mlid'] : 0; // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth. - $cid = 'links:' . $menu_name . ':all-cid:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int)$max_depth; + $cid = 'links:' . $menu_name . ':all-cid:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth; if (!isset($tree[$cid])) { // If the static variable doesn't have the data, check {cache_menu}. @@ -1090,7 +1090,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { $max_depth = min($max_depth, MENU_MAX_DEPTH); } // Generate a cache ID (cid) specific for this page. - $cid = 'links:' . $menu_name . ':page-cid:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int)$item['access'] . ':' . (int)$max_depth; + $cid = 'links:' . $menu_name . ':page-cid:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth; if (!isset($tree[$cid])) { // If the static variable doesn't have the data, check {cache_menu}. @@ -1238,7 +1238,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { * Helper function - compute the real cache ID for menu tree data. */ function _menu_tree_cid($menu_name, $data) { - return 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . md5(serialize($data)); + return 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($data)); } /** @@ -1438,7 +1438,7 @@ function theme_menu_local_task($variables) { $link['title'] = check_plain($link['title']); } $link['localized_options']['html'] = TRUE; - $link_text = t('!local-task-title !active', array('!local-task-title' => $link['title'], '!active' => $active)); + $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); } return '<li' . (!empty($variables['element']['#active']) ? ' class="active"' : '') . '>' . l($link_text, $link['href'], $link['localized_options']) . "</li>\n"; @@ -1685,9 +1685,19 @@ function menu_local_tasks($level = 0) { $tabs = array(); $router_item = menu_get_item(); + + // If this router item points to its parent, start from the parents to + // compute tabs and actions. + if ($router_item && ($router_item['type'] & MENU_LINKS_TO_PARENT)) { + $router_item = menu_get_item($router_item['tab_parent']); + } + + // If we failed to fetch a router item or the current user doesn't have + // access to it, don't bother computing the tabs. if (!$router_item || !$router_item['access']) { return $empty; } + // Get all tabs and the root page. $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_router') @@ -1710,8 +1720,9 @@ function menu_local_tasks($level = 0) { // Store the translated item for later use. $tasks[$item['path']] = $item; } + // Find all tabs below the current path. - $path = ($router_item['type'] == MENU_DEFAULT_LOCAL_TASK ? $router_item['tab_parent'] : $router_item['path']); + $path = $router_item['path']; // Tab parenting may skip levels, so the number of parts in the path may not // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). $depth = 1001; @@ -2130,7 +2141,7 @@ function menu_set_active_trail($new_trail = NULL) { // Make sure the current page is in the trail (needed for the page title), // but exclude tabs and the front page. $last = count($trail) - 1; - if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) { + if ($trail[$last]['href'] != $item['href'] && !(bool) ($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) { $trail[] = $item; } } @@ -2181,7 +2192,7 @@ function menu_get_active_title() { $active_trail = menu_get_active_trail(); foreach (array_reverse($active_trail) as $item) { - if (!(bool)($item['type'] & MENU_IS_LOCAL_TASK)) { + if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) { return $item['title']; } } @@ -3096,8 +3107,8 @@ function _menu_router_build($callbacks) { '_fit' => $fit, ); $item += array( - '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), - '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK), + '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), + '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK), ); if ($move) { $new_path = implode('/', $item['_parts']); @@ -3134,7 +3145,7 @@ function _menu_router_build($callbacks) { // previous iteration assigned one already), try to find the menu name // of the parent item in the currently stored menu links. if (!isset($parent['menu_name'])) { - $menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path", array(':router_path' => $parent_path))->fetchField(); + $menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path AND module = 'system'", array(':router_path' => $parent_path))->fetchField(); if ($menu_name) { $parent['menu_name'] = $menu_name; } diff --git a/includes/module.inc b/includes/module.inc index 1733cff90..380d4c8f5 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -1,5 +1,5 @@ <?php -// $Id: module.inc,v 1.190 2010/04/22 22:36:01 webchick Exp $ +// $Id: module.inc,v 1.192 2010/05/09 13:49:33 dries Exp $ /** * @file @@ -375,10 +375,10 @@ function module_enable($module_list, $enable_dependencies = TRUE) { // Now install the module if necessary. if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { drupal_install_schema($module); - // Allow the module to perform install tasks. - module_invoke($module, 'install'); $versions = drupal_get_schema_versions($module); drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED); + // Allow the module to perform install tasks. + module_invoke($module, 'install'); // Record the fact that it was installed. $modules_installed[] = $module; watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); @@ -786,7 +786,12 @@ function drupal_required_modules() { * * @param $type * A string describing the data type of the alterable $data. 'form', 'links', - * 'node_content', and so on are several examples. + * 'node_content', and so on are several examples. Alternatively can be an + * array, in which case hook_TYPE_alter() is invoked for each value in the + * array, ordered first by module, and then for each module, in the order of + * values in $type. For example, when Form API is using drupal_alter() to + * execute both hook_form_alter() and hook_form_FORM_ID_alter() + * implementations, it passes array('form', 'form_' . $form_id) for $type. * @param &$data * The primary data to be altered. * @param &$context1 @@ -804,14 +809,69 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } $functions = &$drupal_static_fast['functions']; + // Most of the time, $type is passed as a string, so for performance, + // normalize it to that. When passed as an array, usually the first item in + // the array is a generic type, and additional items in the array are more + // specific variants of it, as in the case of array('form', 'form_FORM_ID'). + if (is_array($type)) { + $cid = implode(',', $type); + $extra_types = $type; + $type = array_shift($extra_types); + // Allow if statements in this function to use the faster isset() rather + // than !empty() both when $type is passed as a string, or as an array with + // one item. + if (empty($extra_types)) { + unset($extra_types); + } + } + else { + $cid = $type; + } + // Some alter hooks are invoked many times per page request, so statically // cache the list of functions to call, and on subsequent calls, iterate // through them quickly. - if (!isset($functions[$type])) { - $functions[$type] = array(); + if (!isset($functions[$cid])) { + $functions[$cid] = array(); $hook = $type . '_alter'; - foreach (module_implements($hook) as $module) { - $functions[$type][] = $module . '_' . $hook; + $modules = module_implements($hook); + if (!isset($extra_types)) { + // For the more common case of a single hook, we do not need to call + // function_exists(), since module_implements() returns only modules with + // implementations. + foreach ($modules as $module) { + $functions[$cid][] = $module . '_' . $hook; + } + } + else { + // For multiple hooks, we need $modules to contain every module that + // implements at least one of them. + $extra_modules = array(); + foreach ($extra_types as $extra_type) { + $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter')); + } + // If any modules implement one of the extra hooks that do not implement + // the primary hook, we need to add them to the $modules array in their + // appropriate order. + if (array_diff($extra_modules, $modules)) { + // Order the modules by the order returned by module_list(). + $modules = array_intersect(module_list(), array_merge($modules, $extra_modules)); + } + foreach ($modules as $module) { + // Since $modules is a merged array, for any given module, we do not + // know whether it has any particular implementation, so we need a + // function_exists(). + $function = $module . '_' . $hook; + if (function_exists($function)) { + $functions[$cid][] = $function; + } + foreach ($extra_types as $extra_type) { + $function = $module . '_' . $extra_type . '_alter'; + if (function_exists($function)) { + $functions[$cid][] = $function; + } + } + } } // Allow the theme to alter variables after the theme system has been // initialized. @@ -825,12 +885,22 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { foreach ($theme_keys as $theme_key) { $function = $theme_key . '_' . $hook; if (function_exists($function)) { - $functions[$type][] = $function; + $functions[$cid][] = $function; + } + if (isset($extra_types)) { + foreach ($extra_types as $extra_type) { + $function = $theme_key . '_' . $extra_type . '_alter'; + if (function_exists($function)) { + $functions[$cid][] = $function; + } + } } } } } - foreach ($functions[$type] as $function) { + + foreach ($functions[$cid] as $function) { $function($data, $context1, $context2); } } + diff --git a/includes/pager.inc b/includes/pager.inc index af0065cfa..d8c82e6c6 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -1,5 +1,5 @@ <?php -// $Id: pager.inc,v 1.79 2010/04/13 15:23:02 dries Exp $ +// $Id: pager.inc,v 1.80 2010/05/06 05:59:30 webchick Exp $ /** * @file @@ -85,7 +85,7 @@ class PagerDefault extends SelectQueryExtender { // We calculate the total of pages as ceil(items / limit). $pager_total_items[$this->element] = $this->getCountQuery()->execute()->fetchField(); $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit); - $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1)); + $pager_page_array[$this->element] = max(0, min((int) $pager_page_array[$this->element], ((int) $pager_total[$this->element]) - 1)); $pager_limits[$this->element] = $this->limit; $this->range($pager_page_array[$this->element] * $this->limit, $this->limit); @@ -528,6 +528,6 @@ function pager_load_array($value, $element, $old_array) { } } // Update the changed element. - $new_array[$element] = (int)$value; + $new_array[$element] = (int) $value; return $new_array; } diff --git a/includes/password.inc b/includes/password.inc index 787f14b80..364729b9a 100644 --- a/includes/password.inc +++ b/includes/password.inc @@ -1,5 +1,5 @@ <?php -// $Id: password.inc,v 1.6 2009/02/26 07:30:26 webchick Exp $ +// $Id: password.inc,v 1.8 2010/05/04 15:47:03 dries Exp $ /** * @file @@ -16,8 +16,8 @@ /** * The standard log2 number of iterations for password stretching. This should - * increase by 1 at least every other Drupal version in order to counteract - * increases in the speed and power of computers available to crack the hashes. + * increase by 1 every Drupal version in order to counteract increases in the + * speed and power of computers available to crack the hashes. */ define('DRUPAL_HASH_COUNT', 14); @@ -31,6 +31,11 @@ define('DRUPAL_MIN_HASH_COUNT', 7); */ define('DRUPAL_MAX_HASH_COUNT', 30); +/** + * The expected (and maximum) number of characters in a hashed password. + */ +define('DRUPAL_HASH_LENGTH', 55); + /** * Returns a string for mapping an int to the corresponding base 64 character. */ @@ -49,7 +54,7 @@ function _password_itoa64() { * @return * Encoded string */ -function _password_base64_encode($input, $count) { +function _password_base64_encode($input, $count) { $output = ''; $i = 0; $itoa64 = _password_itoa64(); @@ -93,7 +98,7 @@ function _password_base64_encode($input, $count) { * A 12 character string containing the iteration count and a random salt. */ function _password_generate_salt($count_log2) { - $output = '$P$'; + $output = '$S$'; // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT. $count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT); // Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT. @@ -113,19 +118,23 @@ function _password_generate_salt($count_log2) { * for an attacker to try to break the hash by brute-force computation of the * hashes of a large number of plain-text words or strings to find a match. * + * @param $algo + * The string name of a hashing algorithm usable by hash(), like 'sha256'. * @param $password * The plain-text password to hash. * @param $setting - * An existing hash or the output of _password_generate_salt(). + * An existing hash or the output of _password_generate_salt(). Must be + * at least 12 characters (the settings and salt). * * @return * A string containing the hashed password (and salt) or FALSE on failure. + * The return string will be truncated at DRUPAL_HASH_LENGTH characters max. */ -function _password_crypt($password, $setting) { +function _password_crypt($algo, $password, $setting) { // The first 12 characters of an existing hash are its setting string. $setting = substr($setting, 0, 12); - if (substr($setting, 0, 3) != '$P$') { + if ($setting[0] != '$' || $setting[2] != '$') { return FALSE; } $count_log2 = _password_get_count_log2($setting); @@ -139,22 +148,21 @@ function _password_crypt($password, $setting) { return FALSE; } - // We must use md5() or sha1() here since they are the only cryptographic - // primitives always available in PHP 5. To implement our own low-level - // cryptographic function in PHP would result in much worse performance and - // consequently in lower iteration counts and hashes that are quicker to crack - // (by non-PHP code). - + // Convert the base 2 logarithm into an integer. $count = 1 << $count_log2; - $hash = md5($salt . $password, TRUE); + // We rely on the hash() function being available in PHP 5.2+. + $hash = hash($algo, $salt . $password, TRUE); do { - $hash = md5($hash . $password, TRUE); + $hash = hash($algo, $hash . $password, TRUE); } while (--$count); - $output = $setting . _password_base64_encode($hash, 16); + $len = strlen($hash); + $output = $setting . _password_base64_encode($hash, $len); // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. - return (strlen($output) == 34) ? $output : FALSE; + // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. + $expected = 12 + ceil((8 * $len) / 6); + return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE; } /** @@ -182,7 +190,7 @@ function user_hash_password($password, $count_log2 = 0) { // Use the standard iteration count. $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); } - return _password_crypt($password, _password_generate_salt($count_log2)); + return _password_crypt('sha512', $password, _password_generate_salt($count_log2)); } /** @@ -201,7 +209,7 @@ function user_hash_password($password, $count_log2 = 0) { * TRUE or FALSE. */ function user_check_password($password, $account) { - if (substr($account->pass, 0, 3) == 'U$P') { + if (substr($account->pass, 0, 2) == 'U$') { // This may be an updated password from user_update_7000(). Such hashes // have 'U' added as the first character and need an extra md5(). $stored_hash = substr($account->pass, 1); @@ -210,7 +218,23 @@ function user_check_password($password, $account) { else { $stored_hash = $account->pass; } - $hash = _password_crypt($password, $stored_hash); + + $type = substr($stored_hash, 0, 3); + switch ($type) { + case '$S$': + // A normal Drupal 7 password using sha512. + $hash = _password_crypt('sha512', $password, $stored_hash); + break; + case '$H$': + // phpBB3 uses "$H$" for the same thing as "$P$". + case '$P$': + // A phpass password generated using md5. This is an + // imported password or from an earlier Drupal version. + $hash = _password_crypt('md5', $password, $stored_hash); + break; + default: + return FALSE; + } return ($hash && $stored_hash == $hash); } @@ -234,7 +258,7 @@ function user_check_password($password, $account) { */ function user_needs_new_hash($account) { // Check whether this was an updated password. - if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) != 34)) { + if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) { return TRUE; } // Check whether the iteration count used differs from the standard number. diff --git a/includes/path.inc b/includes/path.inc index 1f4d3557a..98a596262 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -1,5 +1,5 @@ <?php -// $Id: path.inc,v 1.62 2010/04/24 15:11:27 dries Exp $ +// $Id: path.inc,v 1.63 2010/05/18 18:26:30 dries Exp $ /** * @file @@ -234,96 +234,6 @@ function drupal_get_normal_path($path, $path_language = NULL) { return $path; } -/** - * Return a component of the current Drupal path. - * - * When viewing a page at the path "admin/structure/types", for example, arg(0) - * returns "admin", arg(1) returns "structure", and arg(2) returns "types". - * - * Avoid use of this function where possible, as resulting code is hard to read. - * In menu callback functions, attempt to use named arguments. See the explanation - * in menu.inc for how to construct callbacks that take arguments. When attempting - * to use this function to load an element from the current path, e.g. loading the - * node on a node page, please use menu_get_object() instead. - * - * @param $index - * The index of the component, where each component is separated by a '/' - * (forward-slash), and where the first component has an index of 0 (zero). - * @param $path - * A path to break into components. Defaults to the path of the current page. - * - * @return - * The component specified by $index, or NULL if the specified component was - * not found. - */ -function arg($index = NULL, $path = NULL) { - // Even though $arguments doesn't need to be resettable for any functional - // reasons (the result of explode() does not depend on any run-time - // information), it should be resettable anyway in case a module needs to - // free up the memory used by it. - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['arguments'] = &drupal_static(__FUNCTION__); - } - $arguments = &$drupal_static_fast['arguments']; - - if (!isset($path)) { - $path = $_GET['q']; - } - if (!isset($arguments[$path])) { - $arguments[$path] = explode('/', $path); - } - if (!isset($index)) { - return $arguments[$path]; - } - if (isset($arguments[$path][$index])) { - return $arguments[$path][$index]; - } -} - -/** - * Get the title of the current page, for display on the page and in the title bar. - * - * @return - * The current page's title. - */ -function drupal_get_title() { - $title = drupal_set_title(); - - // During a bootstrap, menu.inc is not included and thus we cannot provide a title. - if (!isset($title) && function_exists('menu_get_active_title')) { - $title = check_plain(menu_get_active_title()); - } - - return $title; -} - -/** - * Set the title of the current page, for display on the page and in the title bar. - * - * @param $title - * Optional string value to assign to the page title; or if set to NULL - * (default), leaves the current title unchanged. - * @param $output - * Optional flag - normally should be left as CHECK_PLAIN. Only set to - * PASS_THROUGH if you have already removed any possibly dangerous code - * from $title using a function like check_plain() or filter_xss(). With this - * flag the string will be passed through unchanged. - * - * @return - * The updated title of the current page. - */ -function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { - $stored_title = &drupal_static(__FUNCTION__); - - if (isset($title)) { - $stored_title = ($output == PASS_THROUGH) ? $title : check_plain($title); - } - - return $stored_title; -} - /** * Check if the current page is the front page. * diff --git a/includes/session.inc b/includes/session.inc index 542253e86..07b208d76 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -1,5 +1,5 @@ <?php -// $Id: session.inc,v 1.82 2010/04/13 15:13:40 dries Exp $ +// $Id: session.inc,v 1.83 2010/05/01 08:12:22 dries Exp $ /** * @file @@ -206,7 +206,10 @@ function drupal_session_initialize() { // processes (like drupal_get_token()) needs to know the future // session ID in advance. $user = drupal_anonymous_user(); - session_id(md5(uniqid('', TRUE))); + // Less random sessions (which are much faster to generate) are used for + // anonymous users than are generated in drupal_session_regenerate() when + // a user becomes authenticated. + session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE))); } date_default_timezone_set(drupal_get_user_timezone()); } @@ -284,7 +287,7 @@ function drupal_session_regenerate() { if ($is_https && variable_get('https', FALSE)) { $insecure_session_name = substr(session_name(), 1); $params = session_get_cookie_params(); - $session_id = md5(uniqid(mt_rand(), TRUE)); + $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); setcookie($insecure_session_name, $session_id, REQUEST_TIME + $params['lifetime'], $params['path'], $params['domain'], FALSE, $params['httponly']); $_COOKIE[$insecure_session_name] = $session_id; } diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index 971a58fc6..61784fe8a 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -1,5 +1,5 @@ <?php -// $Id: stream_wrappers.inc,v 1.13 2010/03/26 17:14:45 dries Exp $ +// $Id: stream_wrappers.inc,v 1.15 2010/05/06 05:59:30 webchick Exp $ /** * @file @@ -336,11 +336,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface $path = $this->getLocalPath(); $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); - if ((bool)$this->handle && $options & STREAM_USE_PATH) { + if ((bool) $this->handle && $options & STREAM_USE_PATH) { $opened_url = $path; } - return (bool)$this->handle; + return (bool) $this->handle; } /** @@ -506,7 +506,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface */ public function mkdir($uri, $mode, $options) { $this->uri = $uri; - $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE); + $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); if ($recursive) { // $this->getLocalPath() fails if $uri has multiple levels of directories // that do not yet exist. @@ -581,7 +581,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface $this->uri = $uri; $this->handle = opendir($this->getLocalPath()); - return (bool)$this->handle; + return (bool) $this->handle; } /** @@ -657,7 +657,7 @@ class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { - return variable_get('file_private_path', conf_path() . '/private/files'); + return variable_get('file_private_path', ''); } /** @@ -684,7 +684,7 @@ class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper { * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { - return variable_get('file_temporary_path', conf_path() . '/private/temp'); + return variable_get('file_temporary_path', sys_get_temp_dir()); } /** diff --git a/includes/theme.inc b/includes/theme.inc index a5d6aa1a2..f27b90187 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1,5 +1,5 @@ <?php -// $Id: theme.inc,v 1.593 2010/04/26 17:54:49 webchick Exp $ +// $Id: theme.inc,v 1.598 2010/05/19 19:22:24 dries Exp $ /** * @file @@ -621,130 +621,88 @@ function list_themes($refresh = FALSE) { } /** - * Generate the themed output. - * - * All requests for theme hooks must go through this function. It examines - * the request and routes it to the appropriate theme function. The theme - * registry is checked to determine which implementation to use, which may - * be a function or a template. - * - * If the implementation is a template, the following functions may be used to - * modify the $variables array. They are processed in two distinct phases; - * "preprocess" and "process" functions. The order listed here is the order in - * which they execute. - * - * - template_preprocess(&$variables) - * This sets a default set of variables for all template implementations. - * - * - template_preprocess_HOOK(&$variables) - * This is the first preprocessor called specific to the hook; it should be - * implemented by the module that registers it. - * - * - MODULE_preprocess(&$variables) - * This will be called for all templates; it should only be used if there - * is a real need. It's purpose is similar to template_preprocess(). - * - * - MODULE_preprocess_HOOK(&$variables) - * This is for modules that want to alter or provide extra variables for - * theming hooks not registered to itself. For example, if a module named - * "foo" wanted to alter the $classes_array variable for the hook "node" a - * preprocess function of foo_preprocess_node() can be created to intercept - * and alter the variable. - * - * - ENGINE_engine_preprocess(&$variables) - * This function should only be implemented by theme engines and exists - * so that it can set necessary variables for all hooks. - * - * - ENGINE_engine_preprocess_HOOK(&$variables) - * This is the same as the previous function, but it is called for a single - * theming hook. - * - * - THEME_preprocess(&$variables) - * This is for themes that want to alter or provide extra variables. For - * example, if a theme named "foo" wanted to alter the $classes_array variable - * for the hook "node" a preprocess function of foo_preprocess_node() can be - * created to intercept and alter the variable. - * - * - THEME_preprocess_HOOK(&$variables) - * The same applies from the previous function, but it is called for a - * specific hook. - * - * - template_process(&$variables) - * This sets a default set of variables for all template implementations. - * - * - template_process_HOOK(&$variables) - * This is the first processor called specific to the hook; it should be - * implemented by the module that registers it. - * - * - MODULE_process(&$variables) - * This will be called for all templates; it should only be used if there - * is a real need. It's purpose is similar to template_process(). - * - * - MODULE_process_HOOK(&$variables) - * This is for modules that want to alter or provide extra variables for - * theming hooks not registered to itself. For example, if a module named - * "foo" wanted to alter the $classes_array variable for the hook "node" a - * process function of foo_process_node() can be created to intercept - * and alter the variable. - * - * - ENGINE_engine_process(&$variables) - * This function should only be implemented by theme engines and exists - * so that it can set necessary variables for all hooks. - * - * - ENGINE_engine_process_HOOK(&$variables) - * This is the same as the previous function, but it is called for a single - * theming hook. - * - * - ENGINE_process(&$variables) - * This is meant to be used by themes that utilize a theme engine. It is - * provided so that the processor is not locked into a specific theme. - * This makes it easy to share and transport code but theme authors must be - * careful to prevent fatal re-declaration errors when using sub-themes that - * have their own processor named exactly the same as its base theme. In - * the default theme engine (PHPTemplate), sub-themes will load their own - * template.php file in addition to the one used for its parent theme. This - * increases the risk for these errors. A good practice is to use the engine - * name for the base theme and the theme name for the sub-themes to minimize - * this possibility. - * - * - ENGINE_process_HOOK(&$variables) - * The same applies from the previous function, but it is called for a - * specific hook. - * - * - THEME_process(&$variables) - * These functions are based upon the raw theme; they should primarily be - * used by themes that do not use an engine or by sub-themes. It serves the - * same purpose as ENGINE_process(). - * - * - THEME_process_HOOK(&$variables) - * The same applies from the previous function, but it is called for a - * specific hook. - * - * If the implementation is a function, only the hook-specific preprocess + * Generates themed output. + * + * All requests for themed output must go through this function. It examines + * the request and routes it to the appropriate theme function or template, by + * checking the theme registry. + * + * The first argument to this function is the name of the theme hook. For + * instance, to theme a table, the theme hook name is 'table'. By default, this + * theme hook could be implemented by a function called 'theme_table' or a + * template file called 'table.tpl.php', but hook_theme() can override the + * default function or template name. + * + * If the implementation is a template file, several functions are called + * before the template file is invoked, to modify the $variables array. These + * fall into the "preprocessing" phase and the "processing" phase, and are + * 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. + * - 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. + * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to + * set necessary variables for all theme hooks. + * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set + * necessary variables for the particular theme hook. + * - THEME_preprocess(&$variables, $hook): Allows the theme to set necessary + * variables for all theme hooks. + * - THEME_preprocess_HOOK(&$variables): Allows the theme to set necessary + * variables specific to the particular theme hook. + * - template_process(&$variables, $hook): Creates a default set of variables + * for all theme hooks. + * - template_process_HOOK(&$variables): This is the first processor specific + * to the theme hook; it should be implemented by the module that registers + * it. + * - MODULE_process(&$variables, $hook): hook_process() is invoked on all + * implementing modules. + * - MODULE_process_HOOK(&$variables): hook_process_HOOK() is invoked on + * on all implementing modules, so that modules that didn't define the theme + * hook can alter the variables. + * - ENGINE_engine_process(&$variables, $hook): Allows the theme engine to set + * necessary variables for all theme hooks. + * - ENGINE_engine_process_HOOK(&$variables): Allows the theme engine to set + * necessary variables for the particular theme hook. + * - ENGINE_process(&$variables, $hook): Allows the theme engine to process the + * variables. + * - ENGINE_process_HOOK(&$variables): Allows the theme engine to process the + * variables specific to the theme hook. + * - THEME_process(&$variables, $hook): Allows the theme to process the + * variables. + * - THEME_process_HOOK(&$variables): Allows the theme to process the + * variables specific to the theme hook. + * + * If the implementation is a function, only the theme-hook-specific preprocess * and process functions (the ones ending in _HOOK) are called from the - * above list. This is because theme hooks with function implementations - * need to be fast, and calling the non-hook-specific preprocess and process - * functions for them would incur a noticeable performance penalty. + * list above. This is because theme hooks with function implementations + * need to be fast, and calling the non-theme-hook-specific preprocess and + * process functions for them would incur a noticeable performance penalty. * * There are two special variables that these preprocess and process functions - * can set: - * 'theme_hook_suggestion' and 'theme_hook_suggestions'. These will be merged - * together to form a list of 'suggested' alternate hooks to use, in - * reverse order of priority. theme_hook_suggestion will always be a higher - * priority than items in theme_hook_suggestions. theme() will use the - * highest priority implementation that exists. If none exists, theme() will - * use the implementation for the theme hook it was called with. These - * suggestions are similar to and are used for similar reasons as calling - * theme() with an array as the $hook parameter (see below). The difference - * is whether the suggestions are determined by the code that calls theme() or - * by a preprocess or process function. + * can set: 'theme_hook_suggestion' and 'theme_hook_suggestions'. These will be + * merged together to form a list of 'suggested' alternate theme hooks to use, + * in reverse order of priority. theme_hook_suggestion will always be a higher + * priority than items in theme_hook_suggestions. theme() will use the + * highest priority implementation that exists. If none exists, theme() will + * use the implementation for the theme hook it was called with. These + * suggestions are similar to and are used for similar reasons as calling + * theme() with an array as the $hook parameter (see below). The difference + * is whether the suggestions are determined by the code that calls theme() or + * by a preprocess or process function. * * @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 hook, then the 'links' + * 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: @@ -753,20 +711,20 @@ function list_themes($refresh = FALSE) { * - links * This allows themes to create specific theme implementations for named * objects and contexts of otherwise generic theme hooks. The $hook parameter - * may also be an array, in which case the first hook that has an + * may also be an array, in which case the first theme hook that has an * implementation is used. This allows for the code that calls theme() to * explicitly specify the fallback order in a situation where using the '__' - * convention is not desired or insufficient. - * + * convention is not desired or is insufficient. * @param $variables * An associative array of variables to merge with defaults from the theme * registry, pass to preprocess and process functions for modification, and * finally, pass to the function or template implementing the theme hook. - * Alternatively, this can be a renderable array, in which case, its properties - * are mapped to variables expected by the theme hook implementations. + * Alternatively, this can be a renderable array, in which case, its + * properties are mapped to variables expected by the theme hook + * implementations. * * @return - * An HTML string that generates the themed output. + * An HTML string representing the themed output. */ function theme($hook, $variables = array()) { static $hooks = NULL; @@ -1512,7 +1470,16 @@ function theme_links($variables) { * An associative array containing: * - path: Either the path of the image file (relative to base_path()) or a * full URL. - * - alt: The alternative text for text-based browsers. + * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 + * always require an alt attribute. The HTML 5 draft allows the alt + * 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. + * - 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 * - title: The title text is displayed when the image is hovered in some * popular browsers. * - attributes: Associative array of attributes to be placed in the img tag. @@ -1526,10 +1493,27 @@ function theme_image($variables) { $attributes = $variables['attributes']; $getsize = $variables['getsize']; - if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) { - $attributes = drupal_attributes($attributes); - $url = file_create_url($path); - return '<img src="' . check_url($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />'; + if (!$getsize || (is_file($path) && (list($width, $height) = @getimagesize($path)))) { + // The src attribute can be omitted, by passing NULL for $path and FALSE for + // $getsize. + if (isset($path)) { + $attributes['src'] = file_create_url($path); + } + // The alt attribute defaults to an empty string. By passing NULL as value, + // it can be omitted. + if (isset($alt)) { + $attributes['alt'] = $alt; + } + if (isset($title)) { + $attributes['title'] = $title; + } + if (!isset($attributes['width']) && !empty($width)) { + $attributes['width'] = $width; + } + if (!isset($attributes['height']) && !empty($height)) { + $attributes['height'] = $height; + } + return '<img' . drupal_attributes($attributes) . ' />'; } } @@ -1759,18 +1743,6 @@ function theme_table($variables) { return $output; } -/** - * Returns attributes for a header cell of tables with select all functionality. - * - * @return - * An array of attributes. - */ -function theme_table_select_header_cell() { - drupal_add_js('misc/tableselect.js'); - - return array('class' => array('select-all')); -} - /** * Returns HTML for a sort icon. * @@ -2517,7 +2489,7 @@ function template_preprocess_username(&$variables) { } } else { - $variables['uid'] = (int)$account->uid; + $variables['uid'] = (int) $account->uid; } // Set the name to a formatted name that is safe for printing and diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index f2d416abc..2544bc4e4 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -1,5 +1,5 @@ <?php -// $Id: theme.maintenance.inc,v 1.58 2010/04/20 08:19:01 webchick Exp $ +// $Id: theme.maintenance.inc,v 1.59 2010/05/12 09:22:24 dries Exp $ /** * @file @@ -144,12 +144,6 @@ function theme_install_page($variables) { // Delay setting the message variable so it can be processed below. $variables['show_messages'] = FALSE; - // Variable processors invoked manually since this function and theme_update_page() - // are exceptions in how it works within the theme system. - template_preprocess($variables, 'install_page'); - template_preprocess_maintenance_page($variables); - template_process($variables, 'install_page'); - template_process_maintenance_page($variables); // Special handling of error messages $messages = drupal_set_message(); @@ -174,20 +168,7 @@ function theme_install_page($variables) { $variables['messages'] .= theme('status_messages', array('display' => 'status')); } - // This was called as a theme hook (not template), so we need to fix - // path_to_theme() for the template, to point at the actual theme rather than - // system module as owner of the hook. Additionally, figure out the - // maintenance page template to use. - global $theme_path, $theme_info, $base_theme_info; - $theme_path = dirname($theme_info->uri); - $base_themes = $base_theme_info; - // Make sure a maintenance-page.tpl.php is always found. - $base_themes[] = 'modules/system'; - while (!file_exists($theme_path . '/maintenance-page.tpl.php') && $base_theme = array_shift($base_themes)) { - $theme_path = dirname($base_theme->uri); - } - - return theme_render_template($theme_path . '/maintenance-page.tpl.php', $variables); + return theme('maintenance_page', $variables); } /** @@ -205,13 +186,6 @@ function theme_update_page($variables) { // Set required headers. drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - // Variable processors invoked manually since this function and theme_install_page() - // are exceptions in how it works within the theme system. - template_preprocess($variables, 'update_page'); - template_preprocess_maintenance_page($variables); - template_process($variables, 'update_page'); - template_process_maintenance_page($variables); - // Special handling of warning messages. $messages = drupal_set_message(); if (isset($messages['warning'])) { @@ -220,20 +194,7 @@ function theme_update_page($variables) { $variables['messages'] .= theme('status_messages', array('display' => 'warning')); } - // This was called as a theme hook (not template), so we need to fix - // path_to_theme() for the template, to point at the actual theme rather than - // system module as owner of the hook. Additionally, figure out the - // maintenance page template to use. - global $theme_path, $theme_info, $base_theme_info; - $theme_path = dirname($theme_info->uri); - $base_themes = $base_theme_info; - // Make sure a maintenance-page.tpl.php is always found. - $base_themes[] = 'modules/system'; - while (!file_exists($theme_path . '/maintenance-page.tpl.php') && $base_theme = array_shift($base_themes)) { - $theme_path = dirname($base_theme->uri); - } - - return theme_render_template($theme_path . '/maintenance-page.tpl.php', $variables); + return theme('maintenance_page', $variables); } /** diff --git a/includes/update.inc b/includes/update.inc index fe84dd5c2..0a238bb20 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.inc,v 1.44 2010/04/21 07:05:44 webchick Exp $ +// $Id: update.inc,v 1.50 2010/05/21 11:53:26 dries Exp $ /** * @file @@ -99,6 +99,23 @@ function update_prepare_d7_bootstrap() { // that occur by creating a static list. $GLOBALS['conf']['blocked_ips'] = array(); + // Check that PDO is available and that the correct PDO database driver is + // loaded. Bootstrapping to DRUPAL_BOOTSTRAP_DATABASE will result in a fatal + // error otherwise. + $message = ''; + // Check that PDO is loaded. + if (!extension_loaded('pdo')) { + $message = '<h2>PDO is required!</h2><p>Drupal 7 requires PHP ' . DRUPAL_MINIMUM_PHP . ' or higher with the PHP Data Objects (PDO) extension enabled.</p>'; + } + // Check that the correct driver is loaded for the database being updated. + elseif (!in_array($databases['default']['default']['driver'], PDO::getAvailableDrivers())) { + $message = '<h2>A PDO database driver is required!</h2><p>You need to enable the PDO_' . strtoupper($databases['default']['default']['driver']) . ' database driver for PHP ' . DRUPAL_MINIMUM_PHP . ' or higher so that Drupal 7 can access the database.</p>'; + } + if ($message) { + print $message . '<p>See the <a href="http://drupal.org/requirements">system requirements page</a> for more information.</p>'; + exit(); + } + // Allow the database system to work even if the registry has not been // created yet. drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); @@ -164,11 +181,6 @@ function update_prepare_d7_bootstrap() { 'not null' => TRUE, 'default' => 0, ), - 'headers' => array( - 'description' => 'Any custom HTTP headers to be added to cached data.', - 'type' => 'text', - 'not null' => FALSE, - ), 'serialized' => array( 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', 'type' => 'int', @@ -184,6 +196,12 @@ function update_prepare_d7_bootstrap() { ); db_create_table('cache_bootstrap', $cache_bootstrap); } + + // Set a valid timezone for 6 -> 7 upgrade process. + drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); + if (is_numeric(variable_get('date_default_timezone', 0))) { + $GLOBALS['conf']['date_default_timezone'] = 'UTC'; + } } /** @@ -223,11 +241,11 @@ function update_fix_d7_block_deltas(&$sandbox, $renamed_deltas) { )) ->fetchField(); if ($block_exists) { - db_update($table) - ->fields(array('delta' => $new_delta)) - ->condition('module', $module) - ->condition('delta', $old_delta) - ->execute(); + db_update($table) + ->fields(array('delta' => $new_delta)) + ->condition('module', $module) + ->condition('delta', $old_delta) + ->execute(); } } } @@ -294,7 +312,7 @@ function update_fix_d7_requirements() { global $update_rewrite_settings, $db_url; if (!empty($update_rewrite_settings)) { $databases = update_parse_db_url($db_url); - $salt = sha1(drupal_random_bytes(64)); + $salt = drupal_hash_base64(drupal_random_bytes(55)); file_put_contents(conf_path() . '/settings.php', "\n" . '$databases = ' . var_export($databases, TRUE) . ";\n\$drupal_hash_salt = '$salt';", FILE_APPEND); } if (drupal_get_installed_schema_version('system') < 7000 && !variable_get('update_d7_requirements', FALSE)) { @@ -1100,16 +1118,10 @@ function update_build_dependency_graph($update_functions) { } // Now add any explicit update dependencies declared by modules. - $update_dependencies = update_invoke_all('update_dependencies'); + $update_dependencies = update_retrieve_dependencies(); foreach ($graph as $function => $data) { if (!empty($update_dependencies[$data['module']][$data['number']])) { foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) { - // If we have an explicit dependency on more than one update from a - // particular module, choose the highest one, since that contains the - // actual direct dependency. - if (is_array($number)) { - $number = max($number); - } $dependency = $module . '_update_' . $number; $graph[$dependency]['edges'][$function] = TRUE; $graph[$dependency]['module'] = $module; @@ -1158,39 +1170,66 @@ function update_already_performed($module, $number) { } /** - * Invoke an update system hook in all installed modules. - * - * This function is similar to module_invoke_all(), except it does not require - * that a module be enabled to invoke its hook, only that it be installed. This - * allows the update system to properly perform updates even on modules that - * are currently disabled. + * Invoke hook_update_dependencies() in all installed modules. * - * @param $hook - * The name of the hook to invoke. - * @param ... - * Arguments to pass to the hook. + * 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 + * that it be installed. This allows the update system to properly perform + * updates even on modules that are currently disabled. * * @return - * An array of return values of the hook implementations. If modules return - * arrays from their implementations, those are merged into one array. + * An array of return values obtained by merging the results of the + * hook_update_dependencies() implementations in all installed modules. * * @see module_invoke_all() + * @see hook_update_dependencies() */ -function update_invoke_all() { - $args = func_get_args(); - $hook = $args[0]; - unset($args[0]); +function update_retrieve_dependencies() { $return = array(); - $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version != :schema", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol(); + // Get a list of installed modules, arranged so that we invoke their hooks in + // the same order that module_invoke_all() does. + $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version != :schema ORDER BY weight ASC, name ASC", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol(); foreach ($modules as $module) { - $function = $module . '_' . $hook; + $function = $module . '_update_dependencies'; if (function_exists($function)) { - $result = call_user_func_array($function, $args); + $result = $function(); + // Each implementation of hook_update_dependencies() returns a + // multidimensional, associative array containing some keys that + // represent module names (which are strings) and other keys that + // represent update function numbers (which are integers). We cannot use + // array_merge_recursive() to properly merge these results, since it + // treats strings and integers differently. Therefore, we have to + // explicitly loop through the expected array structure here and perform + // the merge manually. if (isset($result) && is_array($result)) { - $return = array_merge_recursive($return, $result); - } - elseif (isset($result)) { - $return[] = $result; + foreach ($result as $module => $module_data) { + foreach ($module_data as $update => $update_data) { + foreach ($update_data as $module_dependency => $update_dependency) { + // If there are redundant dependencies declared for the same + // update function (so that it is declared to depend on more than + // one update from a particular module), record the dependency on + // the highest numbered update here, since that automatically + // implies the previous ones. For example, if one module's + // implementation of hook_update_dependencies() required this + // ordering: + // + // system_update_7001 ---> user_update_7000 + // + // but another module's implementation of the hook required this + // one: + // + // system_update_7002 ---> user_update_7000 + // + // we record the second one, since system_update_7001() is always + // guaranteed to run before system_update_7002() anyway (within + // an individual module, updates are always run in numerical + // order). + if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) { + $return[$module][$update][$module_dependency] = $update_dependency; + } + } + } + } } } } diff --git a/includes/xmlrpc.inc b/includes/xmlrpc.inc index 6edfe019d..334a8c3d7 100644 --- a/includes/xmlrpc.inc +++ b/includes/xmlrpc.inc @@ -1,5 +1,5 @@ <?php -// $Id: xmlrpc.inc,v 1.65 2010/03/31 15:56:53 dries Exp $ +// $Id: xmlrpc.inc,v 1.66 2010/05/06 05:59:30 webchick Exp $ /** * @file @@ -260,7 +260,7 @@ function xmlrpc_message_tag_close($parser, $tag) { // If no type is indicated, the type is string // We take special care for empty values if (trim($xmlrpc_message->current_tag_contents) != '' || (isset($xmlrpc_message->last_open) && ($xmlrpc_message->last_open == 'value'))) { - $value = (string)$xmlrpc_message->current_tag_contents; + $value = (string) $xmlrpc_message->current_tag_contents; $value_flag = TRUE; } unset($xmlrpc_message->last_open); diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc index 6055a19bb..a850eee48 100644 --- a/includes/xmlrpcs.inc +++ b/includes/xmlrpcs.inc @@ -1,5 +1,5 @@ <?php -// $Id: xmlrpcs.inc,v 1.38 2010/03/31 15:33:08 dries Exp $ +// $Id: xmlrpcs.inc,v 1.39 2010/04/29 05:35:21 webchick Exp $ /** * @file @@ -60,7 +60,8 @@ function xmlrpc_server($callbacks) { $data = file_get_contents('php://input'); if (!$data) { - exit('XML-RPC server accepts POST requests only.'); + print 'XML-RPC server accepts POST requests only.'; + drupal_exit(); } $xmlrpc_server->message = xmlrpc_message($data); if (!xmlrpc_message_parse($xmlrpc_server->message)) { diff --git a/misc/ajax.js b/misc/ajax.js index 2ea89e65c..463481bf4 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -1,4 +1,4 @@ -// $Id: ajax.js,v 1.15 2010/04/07 17:30:43 dries Exp $ +// $Id: ajax.js,v 1.17 2010/05/16 19:08:08 dries Exp $ (function ($) { /** @@ -93,7 +93,7 @@ Drupal.ajax = function (base, element, element_settings) { selector: '#' + base, effect: 'none', speed: 'slow', - method: 'replace', + method: 'replaceWith', progress: { type: 'bar', message: 'Please wait...' @@ -132,7 +132,7 @@ Drupal.ajax = function (base, element, element_settings) { // Sanity check for browser support (object expected). // When using iFrame uploads, responses must be returned as a string. if (typeof response == 'string') { - response = $.parseJson(response); + response = $.parseJSON(response); } return ajax.success(response, status); }, diff --git a/misc/autocomplete.js b/misc/autocomplete.js index bb648d14f..bda022e4c 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -1,4 +1,4 @@ -// $Id: autocomplete.js,v 1.35 2010/01/29 22:40:41 dries Exp $ +// $Id: autocomplete.js,v 1.36 2010/04/30 05:23:43 webchick Exp $ (function ($) { /** @@ -72,23 +72,23 @@ Drupal.jsAC.prototype.onkeyup = function (input, e) { e = window.event; } switch (e.keyCode) { - case 16: // shift. - case 17: // ctrl. - case 18: // alt. - case 20: // caps lock. - case 33: // page up. - case 34: // page down. - case 35: // end. - case 36: // home. - case 37: // left arrow. - case 38: // up arrow. - case 39: // right arrow. - case 40: // down arrow. + case 16: // Shift. + case 17: // Ctrl. + case 18: // Alt. + case 20: // Caps lock. + case 33: // Page up. + case 34: // Page down. + case 35: // End. + case 36: // Home. + case 37: // Left arrow. + case 38: // Up arrow. + case 39: // Right arrow. + case 40: // Down arrow. return true; - case 9: // tab. - case 13: // enter. - case 27: // esc. + case 9: // Tab. + case 13: // Enter. + case 27: // Esc. this.hidePopup(e.keyCode); return true; diff --git a/misc/collapse.js b/misc/collapse.js index 8f7e7cbca..c9cde7fd5 100644 --- a/misc/collapse.js +++ b/misc/collapse.js @@ -1,4 +1,4 @@ -// $Id: collapse.js,v 1.30 2010/04/09 12:24:53 dries Exp $ +// $Id: collapse.js,v 1.32 2010/05/14 16:45:56 dries Exp $ (function ($) { /** @@ -58,8 +58,10 @@ Drupal.behaviors.collapse = { attach: function (context, settings) { $('fieldset.collapsible', context).once('collapse', function () { var $fieldset = $(this); - // Expand if there are errors inside. - if ($('.error', $fieldset).length) { + // Expand fieldset if there are errors inside, or if it contains an + // element that is targeted by the uri fragment identifier. + var anchor = location.hash && location.hash != '#' ? ', ' + location.hash : ''; + if ($('.error' + anchor, $fieldset).length) { $fieldset.removeClass('collapsed'); } diff --git a/misc/displace.js b/misc/displace.js new file mode 100644 index 000000000..57431742d --- /dev/null +++ b/misc/displace.js @@ -0,0 +1,115 @@ +// $Id: displace.js,v 1.2 2010/05/14 16:44:37 dries Exp $ +(function ($) { + +/** + * Provides a generic method to position elements fixed to the viewport. + * + * Fixed positioning (CSS declaration position:fixed) is done relative to the + * viewport. This makes it hard to position multiple fixed positioned element + * relative to each other (e.g. multiple toolbars should come after each other, + * not on top of each other). + * + * To position an element fixed at the top of the viewport add the class + * "displace-top" to that element, and to position it to the bottom of the view- + * port add the class "displace-bottom". + * + * When a browser doesn't support position:fixed (like IE6) the element gets + * positioned absolutely by default, but this can be overridden by using the + * "displace-unsupported" class. + */ + +/** + * Attaches the displace behavior. + */ +Drupal.behaviors.displace = { + attach: function (context, settings) { + // Test for position:fixed support. + if (!Drupal.positionFixedSupported()) { + $(document.documentElement).addClass('displace-unsupported'); + } + + $(document.body).once('displace', function () { + $(window).bind('resize.drupal-displace', function () { + Drupal.displace.clearCache(); + + $(document.body).css({ + paddingTop: Drupal.displace.getDisplacement('top'), + paddingBottom: Drupal.displace.getDisplacement('bottom') + }); + }); + }); + + Drupal.displace.clearCache(true); + $(window).triggerHandler('resize'); + } +}; + +/** + * The displace object. + */ +Drupal.displace = Drupal.displace || {}; + +Drupal.displace.elements = []; +Drupal.displace.displacement = []; + +/** + * Get all displaced elements of given region. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * jQuery object containing all displaced elements of given region. + */ +Drupal.displace.getDisplacedElements = function (region) { + if (!this.elements[region]) { + this.elements[region] = $('.displace-' + region); + } + return this.elements[region]; +}; + +/** + * Get the total displacement of given region. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * The total displacement of given region in pixels. + */ +Drupal.displace.getDisplacement = function (region) { + if (!this.displacement[region]) { + var offset = 0; + var height = 0; + this.getDisplacedElements(region).each(function () { + offset = offset + height; + height = $(this).css(region, offset).outerHeight(); + + // In IE, Shadow filter adds some extra height, so we need to remove it + // from the returned height. + if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) { + height -= this.filters.item('DXImageTransform.Microsoft.Shadow').strength; + } + }); + + // Use offset of latest displaced element as the total displacement. + this.displacement[region] = offset + height; + } + + return this.displacement[region]; +}; + +/** + * Clear cache. + * + * @param selectorCache + * Boolean whether to also clear the selector cache. + */ +Drupal.displace.clearCache = function (selectorCache) { + if (selectorCache) { + this.elements = []; + } + this.displacement = []; +}; + +})(jQuery); diff --git a/misc/drupal.js b/misc/drupal.js index e8f61dff2..6980b980b 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -1,4 +1,4 @@ -// $Id: drupal.js,v 1.65 2010/03/10 15:14:38 dries Exp $ +// $Id: drupal.js,v 1.66 2010/05/14 16:44:37 dries Exp $ var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; @@ -299,6 +299,24 @@ Drupal.getSelection = function (element) { return { 'start': element.selectionStart, 'end': element.selectionEnd }; }; +/** + * Checks if position:fixed is supported. + * + * @return + * Boolean indicating whether or not position:fixed is supported. + * + * @see http://yura.thinkweb2.com/cft/#IS_POSITION_FIXED_SUPPORTED + */ +Drupal.positionFixedSupported = function () { + if (this._positionFixedSupported === undefined) { + var el = $('<div style="position:fixed; top:10px" />').appendTo(document.body); + this._positionFixedSupported = el[0].offsetTop === 10; + el.remove(); + } + + return this._positionFixedSupported; +}; + /** * Build an error message from an AJAX response. */ diff --git a/misc/farbtastic/farbtastic.css b/misc/farbtastic/farbtastic.css index cf8679258..675a5c072 100644 --- a/misc/farbtastic/farbtastic.css +++ b/misc/farbtastic/farbtastic.css @@ -1,4 +1,4 @@ -/* $Id: farbtastic.css,v 1.3 2007/04/13 07:33:23 dries Exp $ */ +/* $Id: farbtastic.css,v 1.4 2010/04/28 20:08:38 dries Exp $ */ .farbtastic { position: relative; @@ -7,11 +7,13 @@ position: absolute; cursor: crosshair; } -.farbtastic, .farbtastic .wheel { +.farbtastic, +.farbtastic .wheel { width: 195px; height: 195px; } -.farbtastic .color, .farbtastic .overlay { +.farbtastic .color, +.farbtastic .overlay { top: 47px; left: 47px; width: 101px; diff --git a/misc/states.js b/misc/states.js index cf2d1ae1d..25bc1d7b4 100644 --- a/misc/states.js +++ b/misc/states.js @@ -1,4 +1,4 @@ -// $Id: states.js,v 1.2 2010/02/18 19:24:55 webchick Exp $ +// $Id: states.js,v 1.4 2010/04/30 19:30:44 webchick Exp $ (function ($) { /** @@ -19,7 +19,7 @@ Drupal.behaviors.states = { attach: function (context, settings) { for (var selector in settings.states) { for (var state in settings.states[selector]) { - new states.Dependant({ + new states.Dependent({ element: $(selector), state: states.State.sanitize(state), dependees: settings.states[selector][state] @@ -39,12 +39,12 @@ Drupal.behaviors.states = { * * @param args * Object with the following keys (all of which are required): - * - element: A jQuery object of the dependant element - * - state: A State object describing the state that is dependant + * - element: A jQuery object of the dependent element + * - state: A State object describing the state that is dependent * - dependees: An object with dependency specifications. Lists all elements * that this element depends on. */ -states.Dependant = function (args) { +states.Dependent = function (args) { $.extend(this, { values: {}, oldValue: undefined }, args); for (var selector in this.dependees) { @@ -57,7 +57,7 @@ states.Dependant = function (args) { * specification from the dependency settings. If the object type can't be * found in this list, the === operator is used by default. */ -states.Dependant.comparisons = { +states.Dependent.comparisons = { 'RegExp': function (reference, value) { return reference.test(value); }, @@ -67,9 +67,9 @@ states.Dependant.comparisons = { } }; -states.Dependant.prototype = { +states.Dependent.prototype = { /** - * Initializes one of the elements this dependant depends on. + * Initializes one of the elements this dependent depends on. * * @param selector * The CSS selector describing the dependee. @@ -111,9 +111,9 @@ states.Dependant.prototype = { * true, undefined or false. */ compare: function (reference, value) { - if (reference.constructor.name in states.Dependant.comparisons) { + if (reference.constructor.name in states.Dependent.comparisons) { // Use a custom compare function for certain reference value types. - return states.Dependant.comparisons[reference.constructor.name](reference, value); + return states.Dependent.comparisons[reference.constructor.name](reference, value); } else { // Do a plain comparison otherwise. @@ -228,7 +228,7 @@ states.Trigger.prototype = { * This list of states contains functions that are used to monitor the state * of an element. Whenever an element depends on the state of another element, * one of these trigger functions is added to the dependee so that the - * dependant element can be updated. + * dependent element can be updated. */ states.Trigger.states = { // 'empty' describes the state to be monitored @@ -247,11 +247,22 @@ states.Trigger.states = { } }, + // For radio buttons, only return the value if the radio button is selected. value: { 'keyup': function () { + // Radio buttons share the same :input[name="key"] selector. + if (this.length > 1) { + // Initial checked value of radios is undefined, so we return false. + return this.filter(':checked').val() || false; + } return this.val(); }, 'change': function () { + // Radio buttons share the same :input[name="key"] selector. + if (this.length > 1) { + // Initial checked value of radios is undefined, so we return false. + return this.filter(':checked').val() || false; + } return this.val(); } }, diff --git a/misc/tabledrag.js b/misc/tabledrag.js index 91585d632..7220b784c 100644 --- a/misc/tabledrag.js +++ b/misc/tabledrag.js @@ -1,4 +1,4 @@ -// $Id: tabledrag.js,v 1.36 2010/03/31 19:22:00 dries Exp $ +// $Id: tabledrag.js,v 1.38 2010/05/18 06:46:45 dries Exp $ (function ($) { /** @@ -516,7 +516,7 @@ Drupal.tableDrag.prototype.getMouseOffset = function (target, event) { * The y coordinate of the mouse on the page (not the screen). */ Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { - var rows = this.table.tBodies[0].rows; + var rows = $(this.table.tBodies[0].rows).not(':hidden'); for (var n = 0; n < rows.length; n++) { var row = rows[n]; var indentDiff = 0; @@ -998,7 +998,7 @@ Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) { var siblings = []; var directions = ['prev', 'next']; var rowIndentation = this.indents; - for (var d in directions) { + for (var d = 0; d < directions.length; d++) { var checkRow = $(this.element)[directions[d]](); while (checkRow.length) { // Check that the sibling contains a similar target field. diff --git a/misc/tableheader.js b/misc/tableheader.js index 642710546..17fed66cd 100644 --- a/misc/tableheader.js +++ b/misc/tableheader.js @@ -1,4 +1,4 @@ -// $Id: tableheader.js,v 1.29 2009/12/18 08:17:26 dries Exp $ +// $Id: tableheader.js,v 1.30 2010/05/14 07:45:53 dries Exp $ (function ($) { Drupal.tableHeaderDoScroll = function () { @@ -42,7 +42,7 @@ Drupal.behaviors.tableHeader = { // Track positioning and visibility. function tracker(e) { // Reset top position of sticky table headers to the current top offset. - var topOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; + var topOffset = Drupal.displace ? Drupal.displace.getDisplacement('top') : 0; $('.sticky-header').css('top', topOffset + 'px'); // Save positioning data. var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; diff --git a/misc/vertical-tabs.js b/misc/vertical-tabs.js index 5fa13e5fe..a604c4b9f 100644 --- a/misc/vertical-tabs.js +++ b/misc/vertical-tabs.js @@ -1,4 +1,4 @@ -// $Id: vertical-tabs.js,v 1.12 2010/04/09 12:24:53 dries Exp $ +// $Id: vertical-tabs.js,v 1.13 2010/04/28 20:25:21 dries Exp $ (function ($) { @@ -129,7 +129,7 @@ Drupal.verticalTab.prototype = { * Updates the tab's summary. */ updateSummary: function () { - this.summary.html(this.fieldset.drupalGetSummary()); + this.summary.html(this.fieldset.drupalGetSummary()); }, /** diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index 0ffa70b1e..c6741695b 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: aggregator.admin.inc,v 1.52 2010/04/24 14:49:13 dries Exp $ +// $Id: aggregator.admin.inc,v 1.53 2010/05/18 20:53:29 dries Exp $ /** * @file @@ -395,6 +395,16 @@ function aggregator_admin_refresh_feed($feed) { * @ingroup forms */ function aggregator_admin_form($form, $form_state) { + // Global aggregator settings. + $form['aggregator_allowed_html_tags'] = array( + '#type' => 'textfield', + '#title' => t('Allowed HTML tags'), + '#size' => 80, + '#maxlength' => 255, + '#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), + '#description' => t('A space-separated list of HTML tags allowed in the content of feed items. Disallowed tags are stripped from the content.'), + ); + // Make sure configuration is sane. aggregator_sanitize_configuration(); diff --git a/modules/aggregator/aggregator.css b/modules/aggregator/aggregator.css index b289271a3..b50fc8d4f 100644 --- a/modules/aggregator/aggregator.css +++ b/modules/aggregator/aggregator.css @@ -1,4 +1,4 @@ -/* $Id: aggregator.css,v 1.2 2007/05/27 17:57:47 goba Exp $ */ +/* $Id: aggregator.css,v 1.3 2010/04/28 20:08:38 dries Exp $ */ #aggregator .feed-source .feed-title { margin-top: 0; @@ -17,7 +17,8 @@ margin-bottom: 0; font-size: 1.3em; } -#aggregator .feed-item-meta, #aggregator .feed-item-body { +#aggregator .feed-item-meta, +#aggregator .feed-item-body { margin-bottom: 0.5em; } #aggregator .feed-item-categories { diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info index 14e94d5b1..8d455f786 100644 --- a/modules/aggregator/aggregator.info +++ b/modules/aggregator/aggregator.info @@ -14,8 +14,8 @@ files[] = aggregator.install files[] = aggregator.test configure = admin/config/services/aggregator/settings -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/aggregator/aggregator.install b/modules/aggregator/aggregator.install index 402774e2d..a1c165955 100644 --- a/modules/aggregator/aggregator.install +++ b/modules/aggregator/aggregator.install @@ -1,5 +1,5 @@ <?php -// $Id: aggregator.install,v 1.29 2010/02/26 00:11:58 webchick Exp $ +// $Id: aggregator.install,v 1.30 2010/05/01 08:12:22 dries Exp $ /** * @file @@ -170,10 +170,10 @@ function aggregator_schema() { ), 'hash' => array( 'type' => 'varchar', - 'length' => 32, + 'length' => 64, 'not null' => TRUE, 'default' => '', - 'description' => 'Calculated md5 hash of the feed data, used for validating cache.', + 'description' => 'Calculated hash of the feed data, used for validating cache.', ), 'etag' => array( 'type' => 'varchar', @@ -275,7 +275,7 @@ function aggregator_schema() { * Add hash column to aggregator_feed table. */ function aggregator_update_7000() { - db_add_field('aggregator_feed', 'hash', array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '')); + db_add_field('aggregator_feed', 'hash', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); } /** @@ -297,3 +297,4 @@ function aggregator_update_7002() { )); db_add_index('aggregator_feed', 'queued', array('queued')); } + diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 467423adf..672d078f2 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -1,5 +1,5 @@ <?php -// $Id: aggregator.module,v 1.438 2010/04/13 15:23:02 dries Exp $ +// $Id: aggregator.module,v 1.439 2010/05/01 08:12:22 dries Exp $ /** * @file @@ -614,12 +614,12 @@ function aggregator_refresh($feed) { list($fetcher, $parser, $processors) = _aggregator_get_variables(); $success = module_invoke($fetcher, 'aggregator_fetch', $feed); - // We store the md5 hash of feed data in the database. When refreshing a + // We store the hash of feed data in the database. When refreshing a // feed we compare stored hash and new hash calculated from downloaded // data. If both are equal we say that feed is not updated. - $md5 = md5($feed->source_string); + $hash = hash('sha256', $feed->source_string); - if ($success && ($feed->hash != $md5)) { + if ($success && ($feed->hash != $hash)) { // Parse the feed. if (module_invoke($parser, 'aggregator_parse', $feed)) { // Update feed with parsed data. @@ -630,7 +630,7 @@ function aggregator_refresh($feed) { 'link' => empty($feed->link) ? $feed->url : $feed->link, 'description' => empty($feed->description) ? '' : $feed->description, 'image' => empty($feed->image) ? '' : $feed->image, - 'hash' => $md5, + 'hash' => $hash, 'etag' => empty($feed->etag) ? '' : $feed->etag, 'modified' => empty($feed->modified) ? 0 : $feed->modified, )) diff --git a/modules/aggregator/aggregator.parser.inc b/modules/aggregator/aggregator.parser.inc index 906180478..72842d5f5 100644 --- a/modules/aggregator/aggregator.parser.inc +++ b/modules/aggregator/aggregator.parser.inc @@ -1,5 +1,5 @@ <?php -// $Id: aggregator.parser.inc,v 1.8 2010/01/30 07:59:24 dries Exp $ +// $Id: aggregator.parser.inc,v 1.9 2010/05/20 08:51:24 dries Exp $ /** * @file @@ -24,7 +24,7 @@ function aggregator_aggregator_parse($feed) { // Filter the input data. if (aggregator_parse_feed($feed->source_string, $feed)) { - $modified = empty($feed->http_headers['Last-Modified']) ? 0 : strtotime($feed->http_headers['Last-Modified']); + $modified = empty($feed->http_headers['last-modified']) ? 0 : strtotime($feed->http_headers['last-modified']); // Prepare the channel data. foreach ($channel as $key => $value) { @@ -43,7 +43,7 @@ function aggregator_aggregator_parse($feed) { $image = ''; } - $etag = empty($feed->http_headers['ETag']) ? '' : $feed->http_headers['ETag']; + $etag = empty($feed->http_headers['etag']) ? '' : $feed->http_headers['etag']; // Add parsed data to the feed object. $feed->link = !empty($channel['LINK']) ? $channel['LINK'] : ''; diff --git a/modules/aggregator/tests/aggregator_test.info b/modules/aggregator/tests/aggregator_test.info index 6114806ac..9b75fc653 100644 --- a/modules/aggregator/tests/aggregator_test.info +++ b/modules/aggregator/tests/aggregator_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = aggregator_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/aggregator/tests/aggregator_test.module b/modules/aggregator/tests/aggregator_test.module index 35d626aab..f77e38af6 100644 --- a/modules/aggregator/tests/aggregator_test.module +++ b/modules/aggregator/tests/aggregator_test.module @@ -1,5 +1,5 @@ <?php -// $Id: aggregator_test.module,v 1.7 2010/03/06 06:31:24 dries Exp $ +// $Id: aggregator_test.module,v 1.8 2010/05/01 08:12:22 dries Exp $ /** * Implements hook_menu(). @@ -25,7 +25,7 @@ function aggregator_test_menu() { */ function aggregator_test_feed($use_last_modified = FALSE, $use_etag = FALSE) { $last_modified = strtotime('Sun, 19 Nov 1978 05:00:00 GMT'); - $etag = md5($last_modified); + $etag = drupal_hash_base64($last_modified); $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; diff --git a/modules/block/block.admin.inc b/modules/block/block.admin.inc index 3fbbe75a7..d07f06e8e 100644 --- a/modules/block/block.admin.inc +++ b/modules/block/block.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: block.admin.inc,v 1.78 2010/04/24 14:49:13 dries Exp $ +// $Id: block.admin.inc,v 1.80 2010/05/13 07:53:02 dries Exp $ /** * @file @@ -170,10 +170,12 @@ function _block_compare($a, $b) { if ((!empty($a['region']) && !empty($b['region'])) && ($place = ($regions[$a['region']] - $regions[$b['region']]))) { return $place; } - // Sort by weight. - $weight = $a['weight'] - $b['weight']; - if ($weight) { - return $weight; + // Sort by weight, unless disabled. + if ($a['region'] != BLOCK_REGION_NONE) { + $weight = $a['weight'] - $b['weight']; + if ($weight) { + return $weight; + } } // Sort by title. return strcmp($a['info'], $b['info']); @@ -440,9 +442,9 @@ function block_add_block_form_validate($form, &$form_state) { function block_add_block_form_submit($form, &$form_state) { $delta = db_insert('block_custom') ->fields(array( - 'body' => $form_state['values']['body'], + 'body' => $form_state['values']['body']['value'], 'info' => $form_state['values']['info'], - 'format' => $form_state['values']['format'], + 'format' => $form_state['values']['body']['format'], )) ->execute(); // Store block delta to allow other modules to work with new block. diff --git a/modules/block/block.api.php b/modules/block/block.api.php index 8bdab9f7d..8fedbbc56 100644 --- a/modules/block/block.api.php +++ b/modules/block/block.api.php @@ -1,5 +1,5 @@ <?php -// $Id: block.api.php,v 1.11 2010/04/22 09:12:35 webchick Exp $ +// $Id: block.api.php,v 1.12 2010/04/28 12:36:26 dries Exp $ /** * @file @@ -192,7 +192,7 @@ function hook_block_view($delta = '') { * - delta: The identifier for the block within that module, as defined within * hook_block_info(). * - * @see hook_block_view_alter() + * @see hook_block_view_MODULE_DELTA_alter() * @see hook_block_view() */ function hook_block_view_alter(&$data, $block) { @@ -213,10 +213,6 @@ function hook_block_view_alter(&$data, $block) { * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a * specific block, rather than implementing hook_block_view_alter(). * - * Note that this hook fires before hook_block_view_alter(). Therefore, all - * implementations of hook_block_view_MODULE_DELTA_alter() will run before all - * implementations of hook_block_view_alter(), regardless of the module order. - * * @param $data * An array of data, as returned from the hook_block_view() implementation of * the module that defined the block: diff --git a/modules/block/block.info b/modules/block/block.info index 5c23f4399..037493e6a 100644 --- a/modules/block/block.info +++ b/modules/block/block.info @@ -10,8 +10,8 @@ files[] = block.install files[] = block.test configure = admin/structure/block -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/block/block.install b/modules/block/block.install index 1d1496243..fafc39ea1 100644 --- a/modules/block/block.install +++ b/modules/block/block.install @@ -1,5 +1,5 @@ <?php -// $Id: block.install,v 1.40 2010/03/28 11:16:29 dries Exp $ +// $Id: block.install,v 1.41 2010/05/19 19:14:43 dries Exp $ /** * @file @@ -387,3 +387,10 @@ function block_update_7004() { // Rebuild theme data, so the new 'help' region is identified. system_rebuild_theme_data(); } + +/** + * Remove {cache_block}.headers column. + */ +function block_update_7005() { + db_drop_field('cache_block', 'headers'); +} diff --git a/modules/block/block.js b/modules/block/block.js index ccfb47542..290106919 100644 --- a/modules/block/block.js +++ b/modules/block/block.js @@ -1,4 +1,4 @@ -// $Id: block.js,v 1.15 2010/04/09 12:24:53 dries Exp $ +// $Id: block.js,v 1.16 2010/05/09 13:57:59 dries Exp $ (function ($) { /** @@ -64,8 +64,8 @@ Drupal.behaviors.blockSettingsSummary = { */ Drupal.behaviors.blockDrag = { attach: function (context, settings) { - // tableDrag is required for this behavior. - if (typeof Drupal.tableDrag == 'undefined') { + // tableDrag is required and we should be on the blocks admin page. + if (typeof Drupal.tableDrag == 'undefined' || typeof Drupal.tableDrag.blocks == 'undefined') { return; } diff --git a/modules/block/block.module b/modules/block/block.module index b6abb66b0..03ec5152a 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -1,5 +1,5 @@ <?php -// $Id: block.module,v 1.419 2010/04/26 14:10:40 dries Exp $ +// $Id: block.module,v 1.421 2010/05/13 07:53:02 dries Exp $ /** * @file @@ -469,8 +469,9 @@ function block_custom_block_form($edit = array()) { * @param $edit * Associative array of fields to save. Array keys: * - info: Block description. - * - body: Block contents. - * - format: Filter ID of the filter format for the body. + * - body: Associative array of body value and format. Array keys: + * - value: Block contents. + * - format: Filter ID of the filter format for the body. * @param $delta * Block ID of the block to save. * @return @@ -479,9 +480,9 @@ function block_custom_block_form($edit = array()) { function block_custom_block_save($edit, $delta) { db_update('block_custom') ->fields(array( - 'body' => $edit['body'], + 'body' => $edit['body']['value'], 'info' => $edit['info'], - 'format' => $edit['format'], + 'format' => $edit['body']['format'], )) ->condition('bid', $delta) ->execute(); @@ -762,9 +763,8 @@ function _block_render_blocks($region_blocks) { $array = module_invoke($block->module, 'block_view', $block->delta); // Allow modules to modify the block before it is viewed, via either - // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter(). - drupal_alter("block_view_{$block->module}_{$block->delta}", $array, $block); - drupal_alter('block_view', $array, $block); + // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter(). + drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block); if (isset($cid)) { cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY); diff --git a/modules/block/tests/block_test.info b/modules/block/tests/block_test.info index 5deccb00c..6d4d48fa9 100644 --- a/modules/block/tests/block_test.info +++ b/modules/block/tests/block_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = block_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/blog/blog.info b/modules/blog/blog.info index a9f3bf281..bdb83120e 100644 --- a/modules/blog/blog.info +++ b/modules/blog/blog.info @@ -9,8 +9,8 @@ files[] = blog.module files[] = blog.pages.inc files[] = blog.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/blog/blog.install b/modules/blog/blog.install new file mode 100644 index 000000000..322c21b91 --- /dev/null +++ b/modules/blog/blog.install @@ -0,0 +1,17 @@ +<?php +// $Id: blog.install,v 1.1 2010/05/05 15:11:51 webchick Exp $ + +/** + * @file + * Install, update and uninstall functions for the blog module. + */ + +/** + * Implements hook_install(). + */ +function blog_install() { + // Ensure the blog node type is available. + node_types_rebuild(); + $types = node_type_get_types(); + node_add_body_field($types['blog']); +} diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index ffdcf8812..819765d36 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: book.admin.inc,v 1.33 2010/04/13 15:23:02 dries Exp $ +// $Id: book.admin.inc,v 1.35 2010/05/01 08:12:22 dries Exp $ /** * @file @@ -154,7 +154,7 @@ function _book_admin_table($node, &$form) { $tree = book_menu_subtree_data($node->book); $tree = array_shift($tree); // Do not include the book item itself. if ($tree['below']) { - $hash = sha1(serialize($tree['below'])); + $hash = drupal_hash_base64(serialize($tree['below'])); // Store the hash value as a hidden form element so that we can detect // if another user changed the book hierarchy. $form['tree_hash'] = array( @@ -246,8 +246,8 @@ function theme_book_admin_table($variables) { drupal_render($form[$key]['weight']), drupal_render($form[$key]['plid']) . drupal_render($form[$key]['mlid']), l(t('view'), $href), - $access ? l(t('edit'), 'node/' . $nid . '/edit', array('query' => $destination)) : ' ', - $access ? l(t('delete'), 'node/' . $nid . '/delete', array('query' => $destination) ) : ' ', + $access ? l(t('edit'), 'node/' . $nid . '/edit', array('query' => $destination)) : ' ', + $access ? l(t('delete'), 'node/' . $nid . '/delete', array('query' => $destination) ) : ' ', ); $row = array('data' => $data); if (isset($form[$key]['#attributes'])) { diff --git a/modules/book/book.info b/modules/book/book.info index 72fd0f306..e13d43e58 100644 --- a/modules/book/book.info +++ b/modules/book/book.info @@ -11,8 +11,8 @@ files[] = book.install files[] = book.test configure = admin/content/book/settings -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/book/book.install b/modules/book/book.install index 4195442a2..3f0a14ebe 100644 --- a/modules/book/book.install +++ b/modules/book/book.install @@ -1,5 +1,5 @@ <?php -// $Id: book.install,v 1.34 2009/12/04 16:49:45 dries Exp $ +// $Id: book.install,v 1.35 2010/05/05 06:55:25 webchick Exp $ /** * @file @@ -37,6 +37,7 @@ function _book_install_type_create() { $book_node_type = node_type_set_defaults($book_node_type); node_type_save($book_node_type); + node_add_body_field($book_node_type); // Default to not promoted. variable_set('node_options_book', array('status')); // Use this default type for adding content to books. diff --git a/modules/book/book.module b/modules/book/book.module index 2f6dfc0ba..35529981b 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -1,5 +1,5 @@ <?php -// $Id: book.module,v 1.539 2010/04/13 15:23:02 dries Exp $ +// $Id: book.module,v 1.542 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -540,7 +540,7 @@ function _book_add_form_elements(&$form, &$form_state, $node) { '#title' => t('Book'), '#default_value' => $node->book['bid'], '#options' => $options, - '#access' => (bool)$options, + '#access' => (bool) $options, '#description' => t('Your page will be a part of the selected book.'), '#weight' => -5, '#attributes' => array('class' => array('book-title-select')), @@ -1019,7 +1019,26 @@ function template_preprocess_book_navigation(&$variables) { } /** - * A recursive helper function for book_toc(). + * Recursively processes and formats menu items for book_toc(). + * + * This helper function recursively modifies the $toc array for each item in + * $tree, ignoring items in the exclude array or at a depth greater than the + * limit. Truncates titles over thirty characters and appends an indentation + * string incremented by depth. + * + * @param $tree + * The data structure of the book's menu tree. Includes hidden links. + * @param $indent + * A string appended to each menu item title. Increments by '--' per depth + * level. + * @param $toc + * 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 mlid values. Any link whose mlid is in this array will be + * excluded (along with its children). + * @param $depth_limit + * Any link deeper than this value will be excluded (along with its children). */ function _book_toc_recurse($tree, $indent, &$toc, $exclude, $depth_limit) { foreach ($tree as $data) { @@ -1254,7 +1273,7 @@ function book_menu_subtree_data($link) { $data['node_links'] = array(); menu_tree_collect_node_links($data['tree'], $data['node_links']); // Compute the real cid for book subtree data. - $tree_cid = 'links:' . $item['menu_name'] . ':subtree-data:' . md5(serialize($data)); + $tree_cid = 'links:' . $item['menu_name'] . ':subtree-data:' . hash('sha256', serialize($data)); // Cache the data, if it is not already in the cache. if (!cache_get($tree_cid, 'cache_menu')) { diff --git a/modules/color/color-rtl.css b/modules/color/color-rtl.css index daf4257d3..beb1b2cc2 100644 --- a/modules/color/color-rtl.css +++ b/modules/color/color-rtl.css @@ -1,4 +1,4 @@ -/* $Id: color-rtl.css,v 1.3 2008/05/19 19:36:41 dries Exp $ */ +/* $Id: color-rtl.css,v 1.4 2010/04/28 20:08:38 dries Exp $ */ #placeholder { left: 0; @@ -14,7 +14,8 @@ float: right; clear: right; } -.color-form .form-text, .color-form .form-select { +.color-form .form-text, +.color-form .form-select { float: right; } .color-form .form-text { @@ -24,7 +25,9 @@ #palette .hook { float: right; } -#palette .down, #palette .up, #palette .both { +#palette .down, +#palette .up, +#palette .both { background: url(images/hook-rtl.png) no-repeat 0 0; } #palette .up { diff --git a/modules/color/color.css b/modules/color/color.css index 745e7d4c2..c388d9f36 100644 --- a/modules/color/color.css +++ b/modules/color/color.css @@ -1,4 +1,4 @@ -/* $Id: color.css,v 1.4 2007/05/27 17:57:48 goba Exp $ */ +/* $Id: color.css,v 1.5 2010/04/28 20:08:38 dries Exp $ */ /* Farbtastic placement */ .color-form { @@ -23,7 +23,8 @@ clear: left; /* LTR */ width: 10em; } -.color-form .form-text, .color-form .form-select { +.color-form .form-text, +.color-form .form-select { float: left; /* LTR */ } .color-form .form-text { @@ -38,7 +39,9 @@ width: 16px; height: 16px; } -#palette .down, #palette .up, #palette .both { +#palette .down, +#palette .up, +#palette .both { background: url(images/hook.png) no-repeat 100% 0; /* LTR */ } #palette .up { diff --git a/modules/color/color.info b/modules/color/color.info index 0319923ca..6c4cfa56c 100644 --- a/modules/color/color.info +++ b/modules/color/color.info @@ -9,8 +9,8 @@ files[] = color.module files[] = color.install files[] = color.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/color/color.module b/modules/color/color.module index 8282c3a75..0b5a88984 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -1,5 +1,5 @@ <?php -// $Id: color.module,v 1.85 2010/04/22 23:25:32 dries Exp $ +// $Id: color.module,v 1.86 2010/05/01 08:12:23 dries Exp $ /** * Implements hook_help(). @@ -329,7 +329,7 @@ function color_scheme_form_submit($form, &$form_state) { } // Prepare target locations for generated files. - $id = $theme . '-' . substr(md5(serialize($palette) . microtime()), 0, 8); + $id = $theme . '-' . substr(hash('sha256', serialize($palette) . microtime()), 0, 8); $paths['color'] = 'public://color'; $paths['target'] = $paths['color'] . '/' . $id; foreach ($paths as $path) { diff --git a/modules/color/preview.js b/modules/color/preview.js index f90181e64..c5e47a08d 100644 --- a/modules/color/preview.js +++ b/modules/color/preview.js @@ -1,15 +1,15 @@ -// $Id: preview.js,v 1.1 2010/04/22 05:18:21 webchick Exp $ +// $Id: preview.js,v 1.2 2010/04/28 20:25:21 dries Exp $ (function ($) { Drupal.color = { - callback: function(context, settings, form, farb, height, width) { + callback: function(context, settings, form, farb, height, width) { // Solid background. $('#preview', form).css('backgroundColor', $('#palette input[name="palette[base]"]', form).val()); - + // Text preview $('#text', form).css('color', $('#palette input[name="palette[text]"]', form).val()); $('#text a, #text h2', form).css('color', $('#palette input[name="palette[link]"]', form).val()); - + // Set up gradients if there are some. var color_start, color_end; for (i in settings.gradients) { @@ -32,4 +32,4 @@ } } }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/modules/comment/comment-wrapper.tpl.php b/modules/comment/comment-wrapper.tpl.php index 7b9119e80..d17205cae 100644 --- a/modules/comment/comment-wrapper.tpl.php +++ b/modules/comment/comment-wrapper.tpl.php @@ -1,9 +1,9 @@ <?php -// $Id: comment-wrapper.tpl.php,v 1.9 2010/02/23 09:51:21 dries Exp $ +// $Id: comment-wrapper.tpl.php,v 1.10 2010/05/05 06:41:22 webchick Exp $ /** * @file - * Default theme implementation to wrap comments. + * Default theme implementation to provide an HTML container for comments. * * Available variables: * - $content: The array of content-related elements for the node. Use @@ -13,6 +13,12 @@ * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default value has the following: * - comment-wrapper: The current template type, i.e., "theming hook". + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. * * The following variables are provided for contextual information. * - $node: Node object the comments are attached to. @@ -32,7 +38,9 @@ ?> <div id="comments" class="<?php print $classes; ?>"<?php print $attributes; ?>> <?php if ($content['comments'] && $node->type != 'forum'): ?> + <?php print render($title_prefix); ?> <h2 class="title"><?php print t('Comments'); ?></h2> + <?php print render($title_suffix); ?> <?php endif; ?> <?php print render($content['comments']); ?> diff --git a/modules/comment/comment.info b/modules/comment/comment.info index e70dd7bf4..d646a442e 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -12,8 +12,8 @@ files[] = comment.test files[] = comment.tokens.inc configure = admin/content/comment -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/comment/comment.module b/modules/comment/comment.module index b761ce805..4ac23e9ba 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -1,5 +1,5 @@ <?php -// $Id: comment.module,v 1.871 2010/04/26 14:26:46 dries Exp $ +// $Id: comment.module,v 1.875 2010/05/10 20:12:21 webchick Exp $ /** * @file @@ -422,7 +422,7 @@ function comment_block_configure($delta = '') { * Implements hook_block_save(). */ function comment_block_save($delta = '', $edit = array()) { - variable_set('comment_block_count', (int)$edit['comment_block_count']); + variable_set('comment_block_count', (int) $edit['comment_block_count']); } /** @@ -576,12 +576,15 @@ function theme_comment_block() { $items = array(); $number = variable_get('comment_block_count', 10); foreach (comment_get_recent($number) as $comment) { - $items[] = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)) . '<br />' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->changed))); + $items[] = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)) .'<span>'. t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->changed))) .'</span>'; } if ($items) { return theme('item_list', array('items' => $items)); } + else { + return t('No comments available.'); + } } /** @@ -606,7 +609,7 @@ function comment_node_view($node, $view_mode) { // is open to new comments, and there currently are none. if (user_access('access comments')) { if (!empty($node->comment_count)) { - $links['comment_comments'] = array( + $links['comment-comments'] = array( 'title' => format_plural($node->comment_count, '1 comment', '@count comments'), 'href' => "node/$node->nid", 'attributes' => array('title' => t('Jump to the first comment of this posting.')), @@ -615,8 +618,8 @@ function comment_node_view($node, $view_mode) { ); $new = comment_num_new($node->nid); - if ($new) { - $links['comment_new_comments'] = array( + if (!$new) { + $links['comment-new-comments'] = array( 'title' => format_plural($new, '1 new comment', '@count new comments'), 'href' => "node/$node->nid", 'query' => comment_new_page_count($node->comment_count, $new, $node), @@ -629,7 +632,7 @@ function comment_node_view($node, $view_mode) { else { if ($node->comment == COMMENT_NODE_OPEN) { if (user_access('post comments')) { - $links['comment_add'] = array( + $links['comment-add'] = array( 'title' => t('Add new comment'), 'href' => "comment/reply/$node->nid", 'attributes' => array('title' => t('Add a new comment to this page.')), @@ -651,17 +654,17 @@ function comment_node_view($node, $view_mode) { // indexing. if ($node->comment == COMMENT_NODE_OPEN) { if (user_access('post comments')) { - $links['comment_add'] = array( + $links['comment-add'] = array( 'title' => t('Add new comment'), 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), 'fragment' => 'comment-form', 'html' => TRUE, ); if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment_add']['href'] = "comment/reply/$node->nid"; + $links['comment-add']['href'] = "comment/reply/$node->nid"; } else { - $links['comment_add']['href'] = "node/$node->nid"; + $links['comment-add']['href'] = "node/$node->nid"; } } else { @@ -984,23 +987,23 @@ function comment_links($comment, $node) { $links = array(); if ($node->comment == COMMENT_NODE_OPEN) { if (user_access('administer comments') && user_access('post comments')) { - $links['comment_delete'] = array( + $links['comment-delete'] = array( 'title' => t('delete'), 'href' => "comment/$comment->cid/delete", 'html' => TRUE, ); - $links['comment_edit'] = array( + $links['comment-edit'] = array( 'title' => t('edit'), 'href' => "comment/$comment->cid/edit", 'html' => TRUE, ); - $links['comment_reply'] = array( + $links['comment-reply'] = array( 'title' => t('reply'), 'href' => "comment/reply/$comment->nid/$comment->cid", 'html' => TRUE, ); if ($comment->status == COMMENT_NOT_PUBLISHED) { - $links['comment_approve'] = array( + $links['comment-approve'] = array( 'title' => t('approve'), 'href' => "comment/$comment->cid/approve", 'html' => TRUE, @@ -1010,13 +1013,13 @@ function comment_links($comment, $node) { } elseif (user_access('post comments')) { if (comment_access('edit', $comment)) { - $links['comment_edit'] = array( + $links['comment-edit'] = array( 'title' => t('edit'), 'href' => "comment/$comment->cid/edit", 'html' => TRUE, ); } - $links['comment_reply'] = array( + $links['comment-reply'] = array( 'title' => t('reply'), 'href' => "comment/reply/$comment->nid/$comment->cid", 'html' => TRUE, @@ -2089,7 +2092,7 @@ function comment_submit($comment) { $comment['subject'] = t('(No subject)'); } } - return (object)$comment; + return (object) $comment; } /** @@ -2100,7 +2103,7 @@ function comment_form_submit_build_comment($form, &$form_state) { field_attach_submit('comment', $comment, $form, $form_state); - $form_state['comment'] = (array)$comment; + $form_state['comment'] = (array) $comment; $form_state['rebuild'] = TRUE; return $comment; } @@ -2363,7 +2366,7 @@ function _comment_update_node_statistics($nid) { * 31000, 31001, ... */ function int2vancode($i = 0) { - $num = base_convert((int)$i, 10, 36); + $num = base_convert((int) $i, 10, 36); $length = strlen($num); return chr($length + ord('0') - 1) . $num; diff --git a/modules/comment/comment.test b/modules/comment/comment.test index ecab2569d..5e036e734 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -1,5 +1,5 @@ <?php -// $Id: comment.test,v 1.77 2010/04/22 10:12:25 webchick Exp $ +// $Id: comment.test,v 1.80 2010/05/06 05:59:31 webchick Exp $ class CommentHelperCase extends DrupalWebTestCase { protected $admin_user; @@ -879,6 +879,11 @@ class CommentApprovalTest extends CommentHelperCase { )); $this->drupalLogin($this->admin_user); $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. + + // Test that the comments page loads correctly when there are no comments + $this->drupalGet('admin/content/comment'); + $this->assertText(t('No comments available.')); + $this->drupalLogout(); // Post anonymous comment without contact info. @@ -1141,7 +1146,7 @@ class CommentRdfaTestCase 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"]'); + $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.')); // Tests number of comments in full node view. @@ -1243,15 +1248,15 @@ class CommentRdfaTestCase extends CommentHelperCase { $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.")); $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, t("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.")); // 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"]'); $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, t("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, t("RDFa markup for the comment body found.")); } } diff --git a/modules/contact/contact.info b/modules/contact/contact.info index ce836de9f..63e15e8dd 100644 --- a/modules/contact/contact.info +++ b/modules/contact/contact.info @@ -11,8 +11,8 @@ files[] = contact.install files[] = contact.test configure = admin/structure/contact -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/contextual/contextual.css b/modules/contextual/contextual.css index 17249ddfb..b09bd3f2f 100644 --- a/modules/contextual/contextual.css +++ b/modules/contextual/contextual.css @@ -1,4 +1,4 @@ -/* $Id: contextual.css,v 1.4 2010/01/20 04:25:13 dries Exp $ */ +/* $Id: contextual.css,v 1.5 2010/05/05 06:38:57 webchick Exp $ */ /** * Contextual links regions. @@ -63,6 +63,7 @@ div.contextual-links-wrapper ul.contextual-links { padding: 0.25em 0; position: absolute; right: 0; + text-align: left; top: 19px; white-space: nowrap; -moz-border-radius: 4px; diff --git a/modules/contextual/contextual.info b/modules/contextual/contextual.info index 09308f775..4fd93e074 100644 --- a/modules/contextual/contextual.info +++ b/modules/contextual/contextual.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = contextual.module -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/dashboard/dashboard.css b/modules/dashboard/dashboard.css index 07fdb0060..80fde4c73 100644 --- a/modules/dashboard/dashboard.css +++ b/modules/dashboard/dashboard.css @@ -1,4 +1,4 @@ -/* $Id: dashboard.css,v 1.12 2010/03/28 11:29:27 dries Exp $ */ +/* $Id: dashboard.css,v 1.13 2010/04/28 20:08:38 dries Exp $ */ #dashboard div.dashboard-region { float: left; @@ -26,7 +26,8 @@ float: none; } -#dashboard #disabled-blocks .block, #dashboard .block-placeholder { +#dashboard #disabled-blocks .block, +#dashboard .block-placeholder { background: #e2e1dc; padding: 6px 4px 6px 8px; margin: 3px 3px 3px 0; @@ -92,7 +93,8 @@ background: #0074BD; } -#dashboard #disabled-blocks .block .content, #dashboard .ui-sortable-helper .content { +#dashboard #disabled-blocks .block .content, +#dashboard .ui-sortable-helper .content { display: none; } diff --git a/modules/dashboard/dashboard.info b/modules/dashboard/dashboard.info index ac82e9322..a92e8f279 100644 --- a/modules/dashboard/dashboard.info +++ b/modules/dashboard/dashboard.info @@ -8,8 +8,8 @@ files[] = dashboard.module dependencies[] = block configure = admin/dashboard/customize -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/dashboard/dashboard.js b/modules/dashboard/dashboard.js index af41205a3..f97218735 100644 --- a/modules/dashboard/dashboard.js +++ b/modules/dashboard/dashboard.js @@ -1,4 +1,4 @@ -// $Id: dashboard.js,v 1.9 2010/03/28 11:29:27 dries Exp $ +// $Id: dashboard.js,v 1.11 2010/05/18 12:07:39 dries Exp $ (function ($) { /** @@ -8,9 +8,6 @@ Drupal.behaviors.dashboard = { attach: function () { $('#dashboard').prepend('<div class="customize"><ul class="action-links"><li><a href="#">' + Drupal.t('Customize dashboard') + '</a></li></ul><div class="canvas"></div></div>'); $('#dashboard .customize .action-links a').click(Drupal.behaviors.dashboard.enterCustomizeMode); - if ($('#dashboard .region .block').length == 0) { - Drupal.settings.dashboard.launchCustomize = true; - } Drupal.behaviors.dashboard.addPlaceholders(); if (Drupal.settings.dashboard.launchCustomize) { Drupal.behaviors.dashboard.enterCustomizeMode(); @@ -172,16 +169,15 @@ Drupal.behaviors.dashboard = { // Load the newly enabled block's content. $.get(Drupal.settings.dashboard.blockContent + '/' + module + '/' + delta, {}, function (block) { - var blockContent = ""; if (block) { - blockContent = $("div.content", $(block)); + item.html(block); } - if (!blockContent) { - blockContent = $('<div class="content">' + Drupal.settings.dashboard.emptyBlockText + '</div>'); + if (item.find('div.content').is(':empty')) { + item.find('div.content').html(Drupal.settings.dashboard.emptyBlockText); } - $("div.content", item).after(blockContent).remove(); + Drupal.attachBehaviors(item); }, 'html' ); diff --git a/modules/dashboard/dashboard.module b/modules/dashboard/dashboard.module index 25318e1cc..ecd9fa5d9 100644 --- a/modules/dashboard/dashboard.module +++ b/modules/dashboard/dashboard.module @@ -1,5 +1,5 @@ <?php -// $Id: dashboard.module,v 1.27 2010/04/22 09:12:35 webchick Exp $ +// $Id: dashboard.module,v 1.29 2010/05/18 12:07:39 dries Exp $ /** * Implements hook_help(). @@ -161,9 +161,10 @@ function dashboard_page_build(&$page) { function dashboard_system_info_alter(&$info, $file, $type) { if ($type == 'theme') { $info['regions'] += dashboard_region_descriptions(); - if (module_exists('overlay')) { - $info['overlay_regions'] = !empty($info['overlay_regions']) ? array_merge($info['overlay_regions'], dashboard_regions()) : dashboard_regions(); - } + // Indicate that these regions are intended to be displayed whenever the + // dashboard is displayed in an overlay. This information is provided for + // any module that might need to use it, not just the core Overlay module. + $info['overlay_regions'] = !empty($info['overlay_regions']) ? array_merge($info['overlay_regions'], dashboard_regions()) : dashboard_regions(); } } @@ -197,6 +198,14 @@ function dashboard_theme() { * Whether to launch in customization mode right away. TRUE or FALSE. */ function dashboard_admin($launch_customize = FALSE) { + // Only continue if provided arguments are expected. This function serves + // as the callback for the top-level admin/ page, so any unexpected arguments + // are likely the result of someone typing in the URL of an administrative + // page that doesn't actually exist; for example, admin/some/random/page. + if (!is_bool($launch_customize)) { + return MENU_NOT_FOUND; + } + $js_settings = array( 'dashboard' => array( 'drawer' => url('admin/dashboard/drawer'), diff --git a/modules/dblog/dblog-rtl.css b/modules/dblog/dblog-rtl.css index 3e5ab2c67..40415c7c6 100644 --- a/modules/dblog/dblog-rtl.css +++ b/modules/dblog/dblog-rtl.css @@ -1,6 +1,7 @@ -/* $Id: dblog-rtl.css,v 1.5 2009/08/24 03:11:34 webchick Exp $ */ +/* $Id: dblog-rtl.css,v 1.6 2010/04/28 20:08:38 dries Exp $ */ -.form-item-type, .form-item-severity { +.form-item-type, +.form-item-severity { float: right; padding-right: 0; padding-left: .8em; diff --git a/modules/dblog/dblog.css b/modules/dblog/dblog.css index d419ebfb2..954f28857 100644 --- a/modules/dblog/dblog.css +++ b/modules/dblog/dblog.css @@ -1,6 +1,7 @@ -/* $Id: dblog.css,v 1.8 2010/01/03 21:01:04 webchick Exp $ */ +/* $Id: dblog.css,v 1.9 2010/04/28 20:08:38 dries Exp $ */ -.form-item-type, .form-item-severity { +.form-item-type, +.form-item-severity { float: left; /* LTR */ padding-right: .8em; /* LTR */ margin: 0.1em; @@ -31,10 +32,12 @@ tr.dblog-content { tr.dblog-content .active { background: #cce; } -tr.dblog-page-not-found, tr.dblog-access-denied { +tr.dblog-page-not-found, +tr.dblog-access-denied { background: #dfd; } -tr.dblog-page-not-found .active, tr.dblog-access-denied .active { +tr.dblog-page-not-found .active, +tr.dblog-access-denied .active { background: #cec; } tr.dblog-error { diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info index be2bfd20e..3b61d593a 100644 --- a/modules/dblog/dblog.info +++ b/modules/dblog/dblog.info @@ -9,8 +9,8 @@ files[] = dblog.admin.inc files[] = dblog.install files[] = dblog.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install index 2189b98ca..112d4412c 100644 --- a/modules/dblog/dblog.install +++ b/modules/dblog/dblog.install @@ -1,5 +1,5 @@ <?php -// $Id: dblog.install,v 1.20 2009/12/04 16:49:46 dries Exp $ +// $Id: dblog.install,v 1.21 2010/04/28 05:29:55 webchick Exp $ /** * @file @@ -92,24 +92,6 @@ function dblog_schema() { return $schema; } -/** - * @defgroup updates-6.x-extra Extra database logging updates for 6.x - * @{ - */ - -/** - * Allow longer referrers. - */ -function dblog_update_6000() { - db_change_field('watchdog', 'referer', 'referer', array('type' => 'text', 'not null' => FALSE)); -} - -/** - * @} End of "defgroup updates-6.x-extra" - * The next series of updates should start at 7000. - */ - - /** * @defgroup updates-6.x-to-7.x database logging updates from 6.x to 7.x * @{ diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 382247e39..0f4078d9e 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -1,5 +1,5 @@ <?php -// $Id: field.api.php,v 1.75 2010/04/24 07:19:09 dries Exp $ +// $Id: field.api.php,v 1.81 2010/05/18 18:30:49 dries Exp $ /** * @ingroup field_fieldable_type @@ -10,8 +10,8 @@ * Expose "pseudo-field" components on fieldable entities. * * Field UI's 'Manage fields' page lets users re-order fields, but also - * non-field components. For nodes, that would be title, menu settings, or - * other elements exposed by contributed modules through hook_form() or + * non-field components. For nodes, these include the title, menu settings, and + * other elements exposed by contributed modules through hook_form() and * hook_form_alter(). * * Fieldable entities or contributed modules that want to have their components @@ -93,6 +93,7 @@ function hook_field_extra_fields_alter(&$info) { * can be attached to a fieldable entity. hook_field_info() defines the basic * properties of a field type, and a variety of other field hooks are called by * the Field Attach API to perform field-type-specific actions. + * * @see hook_field_info() * @see hook_field_info_alter() * @see hook_field_schema() @@ -109,8 +110,9 @@ function hook_field_extra_fields_alter(&$info) { * The Field Types API also defines two kinds of pluggable handlers: widgets * and formatters, which specify how the field appears in edit forms and in * displayed entities. Widgets and formatters can be implemented by a field-type - * module for it's own field types, or by a third-party module to extend the + * module for its own field types, or by a third-party module to extend the * behavior of existing field types. + * * @see hook_field_widget_info() * @see hook_field_formatter_info() * @@ -131,13 +133,12 @@ function hook_field_extra_fields_alter(&$info) { * settings. * - instance_settings: An array whose keys are the names of the settings * available for instances of the field type, and whose values are the - * default values for those settings. - * Instance-level settings can have different values on each field - * instance, and thus allow greater flexibility than field-level settings. - * It is recommended to put settings at the instance level whenever - * possible. Notable exceptions: settings acting on the schema definition, - * or settings that Views needs to use across field instances (e.g. list of - * allowed values). + * default values for those settings. Instance-level settings can have + * different values on each field instance, and thus allow greater + * flexibility than field-level settings. It is recommended to put settings + * at the instance level whenever possible. Notable exceptions: settings + * acting on the schema definition, or settings that Views needs to use + * across field instances (for example, the list of allowed values). * - default_widget: The machine name of the default widget to be used by * instances of this field type, when no widget is specified in the * instance definition. This widget must be available whenever the field @@ -148,6 +149,12 @@ function hook_field_extra_fields_alter(&$info) { * instance definition. This formatter must be available whenever the field * type is available (i.e. provided by the field type module, or by a module * the field type module depends on). + * - no_ui: (optional) A boolean specifying that users should not be allowed + * to create fields and instances of this field type through the UI. Such + * fields can only be created programmatically with field_create_field() + * and field_create_instance(). Defaults to FALSE. + * + * @see hook_field_info_alter() */ function hook_field_info() { return array( @@ -182,7 +189,7 @@ function hook_field_info() { * Perform alterations on Field API field types. * * @param $info - * Array of informations on widget types exposed by hook_field_info() + * Array of information on field types exposed by hook_field_info() * implementations. */ function hook_field_info_alter(&$info) { @@ -204,23 +211,22 @@ function hook_field_info_alter(&$info) { * * @param $field * A field structure. + * * @return * An associative array with the following keys: - * - columns: An array of Schema API column specifications, keyed by column name. - * This specifies what comprises a value for a given field. - * For example, a value for a number field is simply 'value', while a - * value for a formatted text field is the combination of 'value' and - * 'format'. - * It is recommended to avoid having the columns definitions depend on - * field settings when possible. - * No assumptions should be made on how storage engines internally use the - * original column name to structure their storage. + * - columns: An array of Schema API column specifications, keyed by column + * name. This specifies what comprises a value for a given field. For + * example, a value for a number field is simply 'value', while a value for + * a formatted text field is the combination of 'value' and 'format'. It is + * recommended to avoid having the column definitions depend on field + * settings when possible. No assumptions should be made on how storage + * engines internally use the original column name to structure their + * storage. * - indexes: An array of Schema API indexes definitions. Only columns that - * appear in the 'columns' array are allowed. - * Those indexes will be used as default indexes. Callers of - * field_create_field() can specify additional indexes, or, at their own - * risk, modify the default indexes specified by the field-type module. - * Some storage engines might not support indexes. + * appear in the 'columns' array are allowed. Those indexes will be used as + * default indexes. Callers of field_create_field() can specify additional + * indexes, or, at their own risk, modify the default indexes specified by + * the field-type module. Some storage engines might not support indexes. */ function hook_field_schema($field) { if ($field['type'] == 'text_long') { @@ -257,10 +263,10 @@ function hook_field_schema($field) { } /** - * Defines custom load behavior for this module's field types. + * Define custom load behavior for this module's field types. * * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity id. + * $entities, $instances and $items parameters are arrays keyed by entity ID. * For performance reasons, information for all available entity should be * loaded in a single query where possible. * @@ -270,25 +276,26 @@ function hook_field_schema($field) { * hook_field_load() is run on those as well. Use * hook_field_formatter_prepare_view() instead. * + * Make changes or additions to field values by altering the $items parameter by + * reference. There is no return value. + * * @param $entity_type * The type of $entity. * @param $entities - * Array of entities being loaded, keyed by entity id. + * Array of entities being loaded, keyed by entity ID. * @param $field * The field structure for the operation. * @param $instances * Array of instance structures for $field for each entity, keyed by entity - * id. + * ID. * @param $langcode - * The language associated to $items. + * The language code associated with $items. * @param $items - * Array of field values already loaded for the entities, keyed by entity id. + * Array of field values already loaded for the entities, keyed by entity ID. + * Store your changes in this parameter (passed by reference). * @param $age * FIELD_LOAD_CURRENT to load the most recent revision for all fields, or * FIELD_LOAD_REVISION to load the version indicated by each entity. - * @return - * Changes or additions to field values are done by altering the $items - * parameter by reference. */ function hook_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { // Sample code from text.module: precompute sanitized strings so they are @@ -308,27 +315,29 @@ function hook_field_load($entity_type, $entities, $field, $instances, $langcode, } /** - * Prepares field values prior to display. + * Prepare field values prior to display. * * This hook is invoked before the field values are handed to formatters * for display, and runs before the formatters' own * hook_field_formatter_prepare_view(). - * @see hook_field_formatter_prepare_view() * * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity id. + * $entities, $instances and $items parameters are arrays keyed by entity ID. * For performance reasons, information for all available entities should be * loaded in a single query where possible. * + * Make changes or additions to field values by altering the $items parameter by + * reference. There is no return value. + * * @param $entity_type * The type of $entity. * @param $entities - * Array of entities being displayed, keyed by entity id. + * Array of entities being displayed, keyed by entity ID. * @param $field * The field structure for the operation. * @param $instances * Array of instance structures for $field for each entity, keyed by entity - * id. + * ID. * @param $langcode * The language associated to $items. * @param $items @@ -336,7 +345,7 @@ function hook_field_load($entity_type, $entities, $field, $instances, $langcode, */ function hook_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { // Sample code from image.module: if there are no images specified at all, - // use the default. + // use the default image. foreach ($entities as $id => $entity) { if (empty($items[$id]) && $field['settings']['default_image']) { if ($file = file_load($field['settings']['default_image'])) { @@ -351,7 +360,10 @@ function hook_field_prepare_view($entity_type, $entities, $field, $instances, $l } /** - * Define custom validate behavior for this module's field types. + * Validate this module's field data. + * + * If there are validation problems, add to the $errors array (passed by + * reference). There is no return value. * * @param $entity_type * The type of $entity. @@ -362,7 +374,7 @@ function hook_field_prepare_view($entity_type, $entities, $field, $instances, $l * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. * @param $errors @@ -370,10 +382,11 @@ function hook_field_prepare_view($entity_type, $entities, $field, $instances, $l * already been reported for the entity. The function should add its errors * to this array. Each error is an associative array, with the following * keys and values: - * - 'error': an error code (should be a string, prefixed with the module name) - * - 'message': the human readable message to be displayed. + * - error: An error code (should be a string, prefixed with the module + * name). + * - message: The human readable message to be displayed. */ -function hook_field_validate($entity_type, $entity, $field, $instance, $langcode, &$items, &$errors) { +function hook_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { if (!empty($item['value'])) { if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) { @@ -389,6 +402,9 @@ function hook_field_validate($entity_type, $entity, $field, $instance, $langcode /** * Define custom presave behavior for this module's field types. * + * Make changes or additions to field values by altering the $items parameter by + * reference. There is no return value. + * * @param $entity_type * The type of $entity. * @param $entity @@ -398,7 +414,7 @@ function hook_field_validate($entity_type, $entity, $field, $instance, $langcode * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. */ @@ -417,6 +433,8 @@ function hook_field_presave($entity_type, $entity, $field, $instance, $langcode, /** * Define custom insert behavior for this module's field types. * + * Invoked from field_attach_insert(). + * * @param $entity_type * The type of $entity. * @param $entity @@ -426,16 +444,19 @@ function hook_field_presave($entity_type, $entity, $field, $instance, $langcode, * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. */ function hook_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + // @todo Needs function body. } /** * Define custom update behavior for this module's field types. * + * Invoked from field_attach_update(). + * * @param $entity_type * The type of $entity. * @param $entity @@ -445,17 +466,55 @@ function hook_field_insert($entity_type, $entity, $field, $instance, $langcode, * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. */ function hook_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + // @todo Needs function body. +} + +/** + * Update the storage information for a field. + * + * This is invoked on the field's storage module from field_update_field(), + * before the new field information is saved to the database. The field storage + * module should update its storage tables to agree with the new field + * information. If there is a problem, the field storage module should throw an + * exception. + * + * @param $field + * The updated field structure to be saved. + * @param $prior_field + * The previously-saved field structure. + * @param $has_data + * TRUE if the field has data in storage currently. + */ +function hook_field_storage_update_field($field, $prior_field, $has_data) { + if (!$has_data) { + // There is no data. Re-create the tables completely. + $prior_schema = _field_sql_storage_schema($prior_field); + foreach ($prior_schema as $name => $table) { + db_drop_table($name, $table); + } + $schema = _field_sql_storage_schema($field); + foreach ($schema as $name => $table) { + db_create_table($name, $table); + } + } + else { + // There is data. See field_sql_storage_field_storage_update_field() for + // an example of what to do to modify the schema in place, preserving the + // old data as much as possible. + } + drupal_get_schema(NULL, TRUE); } /** * Define custom delete behavior for this module's field types. * - * This hook is invoked just before the data is deleted from field storage. + * This hook is invoked just before the data is deleted from field storage + * in field_attach_delete(). * * @param $entity_type * The type of $entity. @@ -466,18 +525,20 @@ function hook_field_update($entity_type, $entity, $field, $instance, $langcode, * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. */ function hook_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { + // @todo Needs function body. } /** - * Define custom delete_revision behavior for this module's field types. + * Define custom revision delete behavior for this module's field types. * - * This hook is invoked just before the data is deleted from field storage, - * and will only be called for fieldable types that are versioned. + * This hook is invoked just before the data is deleted from field storage + * in field_attach_delete_revision(), and will only be called for fieldable + * types that are versioned. * * @param $entity_type * The type of $entity. @@ -488,11 +549,12 @@ function hook_field_delete($entity_type, $entity, $field, $instance, $langcode, * @param $instance * The instance structure for $field on $entity's bundle. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. */ function hook_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { + // @todo Needs function body. } /** @@ -523,12 +585,13 @@ function hook_field_prepare_translation($entity_type, $entity, $field, $instance * An item that may or may not be empty. * @param $field * The field to which $item belongs. + * * @return * TRUE if $field's type considers $item not to contain any data; * FALSE otherwise. */ function hook_field_is_empty($item, $field) { - if (empty($item['value']) && (string)$item['value'] !== '0') { + if (empty($item['value']) && (string) $item['value'] !== '0') { return TRUE; } return FALSE; @@ -540,16 +603,15 @@ function hook_field_is_empty($item, $field) { * Widgets are Form API elements with additional processing capabilities. * Widget hooks are typically called by the Field Attach API during the * creation of the field form structure with field_attach_form(). + * * @see hook_field_widget_info_alter() * @see hook_field_widget_form() * @see hook_field_widget_error() * * @return * An array describing the widget types implemented by the module. - * * The keys are widget type names. To avoid name clashes, widget type * names should be prefixed with the name of the module that exposes them. - * * The values are arrays describing the widget type, with the following * key/value pairs: * - label: The human-readable name of the widget type. @@ -558,17 +620,19 @@ function hook_field_is_empty($item, $field) { * - settings: An array whose keys are the names of the settings available * for the widget type, and whose values are the default values for those * settings. - * - behaviors: (optional) An array describing behaviors of the widget. - * - multiple values: - * FIELD_BEHAVIOR_DEFAULT (default) if the widget allows the input of one - * single field value (most common case). The widget will be repeated for - * each value input. - * FIELD_BEHAVIOR_CUSTOM if one single copy of the widget can receive - * several field values. Examples: checkboxes, multiple select, - * comma-separated textfield... - * - default value: - * FIELD_BEHAVIOR_DEFAULT (default) if the widget accepts default values. - * FIELD_BEHAVIOR_NONE if the widget does not support default values. + * - behaviors: (optional) An array describing behaviors of the widget, with + * the following elements: + * - multiple values: One of the following constants: + * - FIELD_BEHAVIOR_DEFAULT: (default) If the widget allows the input of + * one single field value (most common case). The widget will be + * repeated for each value input. + * - FIELD_BEHAVIOR_CUSTOM: If one single copy of the widget can receive + * several field values. Examples: checkboxes, multiple select, + * comma-separated textfield. + * - default value: One of the following constants: + * - FIELD_BEHAVIOR_DEFAULT: (default) If the widget accepts default + * values. + * - FIELD_BEHAVIOR_NONE: if the widget does not support default values. */ function hook_field_widget_info() { return array( @@ -602,7 +666,6 @@ function hook_field_widget_info() { ); } - /** * Perform alterations on Field API widget types. * @@ -621,7 +684,7 @@ function hook_field_widget_info_alter(&$info) { } /** - * Return a single form element for a field widget. + * Return the form for a single field widget. * * Field widget form elements should be based on the passed in $element, which * contains the base form element properties derived from the field @@ -629,11 +692,11 @@ function hook_field_widget_info_alter(&$info) { * * Field API will set the weight, field name and delta values for each form * element. If there are multiple values for this field, the Field API will - * call this function as many times as needed. + * invoke this hook as many times as needed. * * Note that, depending on the context in which the widget is being included * (regular entity edit form, 'default value' input in the field settings form, - * etc...), the passed in values for $field and $instance might be different + * etc.), the passed in values for $field and $instance might be different * from the official definitions returned by field_info_field() and * field_info_instance(). If the widget uses Form API callbacks (like * #element_validate, #value_callback...) that need to access the $field or @@ -651,7 +714,7 @@ function hook_field_widget_info_alter(&$info) { * @param $instance * The field instance. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * Array of default values for this field. * @param $delta @@ -672,6 +735,7 @@ function hook_field_widget_info_alter(&$info) { * required. * - #delta: The order of this item in the array of subelements; see $delta * above. + * * @return * The form elements for a single widget for this field. */ @@ -693,9 +757,9 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco * @param $error * An associative array with the following key-value pairs, as returned by * hook_field_validate(): - * - 'error': the error code. Complex widgets might need to report different + * - error: the error code. Complex widgets might need to report different * errors to different form elements inside the widget. - * - 'message': the human readable message to be displayed. + * - message: the human readable message to be displayed. * @param $form * The form array. * @param $form_state @@ -712,17 +776,10 @@ function hook_field_widget_error($element, $error, $form, &$form_state) { * called by the Field Attach API field_attach_prepare_view() and * field_attach_view() functions. * - * @see hook_field_formatter_info() - * @see hook_field_formatter_info_alter() - * @see hook_field_formatter_view() - * @see hook_field_formatter_prepare_view() - * * @return * An array describing the formatter types implemented by the module. - * * The keys are formatter type names. To avoid name clashes, formatter type * names should be prefixed with the name of the module that exposes them. - * * The values are arrays describing the formatter type, with the following * key/value pairs: * - label: The human-readable name of the formatter type. @@ -731,6 +788,10 @@ function hook_field_widget_error($element, $error, $form, &$form_state) { * - settings: An array whose keys are the names of the settings available * for the formatter type, and whose values are the default values for * those settings. + * + * @see hook_field_formatter_info_alter() + * @see hook_field_formatter_view() + * @see hook_field_formatter_prepare_view() */ function hook_field_formatter_info() { return array( @@ -764,7 +825,6 @@ function hook_field_formatter_info() { ); } - /** * Perform alterations on Field API formatter types. * @@ -790,39 +850,39 @@ function hook_field_formatter_info_alter(&$info) { * which displays properties of the referenced entities such as name or type. * * This hook is called after the field type's own hook_field_prepare_view(). - * @see hook_field_prepare_view() * * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity id. + * $entities, $instances and $items parameters are arrays keyed by entity ID. * For performance reasons, information for all available entities should be * loaded in a single query where possible. * * @param $entity_type * The type of $entity. * @param $entities - * Array of entities being displayed, keyed by entity id. + * Array of entities being displayed, keyed by entity ID. * @param $field * The field structure for the operation. * @param $instances * Array of instance structures for $field for each entity, keyed by entity - * id. + * ID. * @param $langcode * The language the field values are to be shown in. If no language is * provided the current language is used. * @param $items - * Array of field values for the entities, keyed by entity id. + * Array of field values for the entities, keyed by entity ID. * @param $displays - * Array of display settings to use for each entity, keyed by entity id. + * Array of display settings to use for each entity, keyed by entity ID. + * * @return * Changes or additions to field values are done by altering the $items * parameter by reference. */ function hook_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { - + // @todo Needs function body. } /** - * Builds a renderable array for a field value. + * Build a renderable array for a field value. * * @param $entity_type * The type of $entity. @@ -833,7 +893,7 @@ function hook_field_formatter_prepare_view($entity_type, $entities, $field, $ins * @param $instance * The field instance. * @param $langcode - * The language associated to $items. + * The language associated with $items. * @param $items * Array of values for this field. * @param $display @@ -903,8 +963,20 @@ function hook_field_formatter_view($entity_type, $entity, $field, $instance, $la * Act on field_attach_form. * * This hook is invoked after the field module has performed the operation. + * Implementing modules should alter the $form or $form_state parameters. * - * See field_attach_form() for details and arguments. + * @param $entity_type + * The type of $entity; for example, 'node' or 'user'. + * @param $entity + * The entity for which to load form elements, used to initialize + * default form values. + * @param $form + * The form structure to fill in. + * @param $form_state + * An associative array containing the current state of the form. + * @param $langcode + * The language the field values are going to be entered in. If no language + * is provided the default site language will be used. */ function hook_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { $tids = array(); @@ -945,13 +1017,13 @@ function hook_field_attach_form($entity_type, $entity, &$form, &$form_state, $la } /** - * Act on field_attach_load. + * Act on field_attach_load(). * * This hook is invoked after the field module has performed the operation. * * Unlike other field_attach hooks, this hook accounts for 'multiple loads'. * Instead of the usual $entity parameter, it accepts an array of entities, - * indexed by entity id. For performance reasons, information for all available + * indexed by entity ID. For performance reasons, information for all available * entities should be loaded in a single query where possible. * * The changes made to the entities' field values get cached by the field cache @@ -959,106 +1031,116 @@ function hook_field_attach_form($entity_type, $entity, &$form, &$form_state, $la * * See field_attach_load() for details and arguments. */ -function hook_field_attach_load($entity_type, $entities, $age) { +function hook_field_attach_load($entity_type, &$entities, $age, $options) { + // @todo Needs function body. } /** - * Act on field_attach_validate. + * Act on field_attach_validate(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_validate() for details and arguments. */ function hook_field_attach_validate($entity_type, $entity, &$errors) { + // @todo Needs function body. } /** - * Act on field_attach_submit. + * Act on field_attach_submit(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_submit() for details and arguments. */ function hook_field_attach_submit($entity_type, $entity, $form, &$form_state) { + // @todo Needs function body. } /** - * Act on field_attach_presave. + * Act on field_attach_presave(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_presave() for details and arguments. */ function hook_field_attach_presave($entity_type, $entity) { + // @todo Needs function body. } /** - * Act on field_attach_insert. + * Act on field_attach_insert(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_insert() for details and arguments. */ function hook_field_attach_insert($entity_type, $entity) { + // @todo Needs function body. } /** - * Act on field_attach_update. + * Act on field_attach_update(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_update() for details and arguments. */ function hook_field_attach_update($entity_type, $entity) { + // @todo Needs function body. } /** - * Act on field_attach_preprocess. + * Alter field_attach_preprocess() variables. * - * This hook is invoked while preprocessing the field.tpl.php template file. + * This hook is invoked while preprocessing the field.tpl.php template file + * in field_attach_preprocess(). * * @param $variables * The variables array is passed by reference and will be populated with field * values. * @param $context * An associative array containing: - * - entity_type: The type of $entity; e.g. 'node' or 'user'. - * - object: The entity with fields to render. + * - entity_type: The type of $entity; for example, 'node' or 'user'. + * - entity: The entity with fields to render. * - element: The structured array containing the values ready for rendering. */ function hook_field_attach_preprocess_alter(&$variables, $context) { + // @todo Needs function body. } /** - * Act on field_attach_delete. + * Act on field_attach_delete(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_delete() for details and arguments. */ function hook_field_attach_delete($entity_type, $entity) { + // @todo Needs function body. } /** - * Act on field_attach_delete_revision. + * Act on field_attach_delete_revision(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_delete_revision() for details and arguments. */ function hook_field_attach_delete_revision($entity_type, $entity) { + // @todo Needs function body. } /** - * Act on field_purge_data. + * Act on field_purge_data(). * * This hook is invoked in field_purge_data() and allows modules to act on - * purging data from a single field pseudo-entity. For example, if a module + * purging data from a single field pseudo-entity. For example, if a module * relates data in the field with its own data, it may purge its own data * during this process as well. * * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. + * The type of $entity; for example, 'node' or 'user'. * @param $entity * The pseudo-entity whose field data is being purged. * @param $field @@ -1077,23 +1159,24 @@ function hook_field_attach_purge($entity_type, $entity, $field, $instance) { } /** - * Act on field_attach_view. + * Perform alterations on field_attach_view(). * * This hook is invoked after the field module has performed the operation. * - * @param &$output - * The structured content array tree for all of $entity's fields. + * @param $output + * The structured content array tree for all of the entity's fields. * @param $context * An associative array containing: - * - entity_type: The type of $entity; e.g. 'node' or 'user'. - * - object: The entity with fields to render. - * - view_mode: View mode, e.g. 'full', 'teaser'... + * - entity_type: The type of $entity; for example, 'node' or 'user'. + * - entity: The entity with fields to render. + * - view_mode: View mode, for example, 'full' or 'teaser'. */ function hook_field_attach_view_alter(&$output, $context) { + // @todo Needs function body. } /** - * Act on field_language(). + * Perform alterations on field_language() values. * * This hook is invoked to alter the array of display languages for the given * entity. @@ -1107,13 +1190,14 @@ function hook_field_attach_view_alter(&$output, $context) { * - langcode: The language code $entity has to be displayed in. */ function hook_field_language_alter(&$display_language, $context) { + // @todo Needs function body. } /** - * Act on field_available_languages(). + * Alter field_available_languages() values. * - * This hook is invoked to alter the array of available languages for the given - * field. + * This hook is invoked from field_available_languages() to allow modules to + * alter the array of available languages for the given field. * * @param &$languages * A reference to an array of language codes to be made available. @@ -1123,26 +1207,29 @@ function hook_field_language_alter(&$display_language, $context) { * - field: A field data structure. */ function hook_field_available_languages_alter(&$languages, $context) { + // @todo Needs function body. } /** - * Act on field_attach_create_bundle. + * Act on field_attach_create_bundle(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_create_bundle() for details and arguments. */ function hook_field_attach_create_bundle($entity_type, $bundle) { + // @todo Needs function body. } /** - * Act on field_attach_rename_bundle. + * Act on field_attach_rename_bundle(). * * This hook is invoked after the field module has performed the operation. * * See field_attach_rename_bundle() for details and arguments. */ function hook_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { + // @todo Needs function body. } /** @@ -1151,7 +1238,7 @@ function hook_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) * This hook is invoked after the field module has performed the operation. * * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. + * The type of entity; for example, 'node' or 'user'. * @param $bundle * The bundle that was just deleted. * @param $instances @@ -1159,6 +1246,7 @@ function hook_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) * deleted. */ function hook_field_attach_delete_bundle($entity_type, $bundle, $instances) { + // @todo Needs function body. } /** @@ -1225,14 +1313,18 @@ function hook_field_storage_info_alter(&$info) { * * @param $field * A field structure. + * * @return * An array of details. * - The first dimension is a store type (sql, solr, etc). * - The second dimension indicates the age of the values in the store * FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION. * - Other dimensions are specific to the field storage module. + * + * @see hook_field_storage_details_alter() */ function hook_field_storage_details($field) { + // @todo Needs function body. } /** @@ -1243,13 +1335,19 @@ function hook_field_storage_details($field) { * hook_field_storage_details() implementations. * @param $field * A field structure. + * + * @see hook_field_storage_details() */ function hook_field_storage_details_alter(&$details, $field) { + // @todo Needs function body. } /** * Load field data for a set of entities. * + * This hook is invoked from field_attach_load() to ask the field storage + * module to load field data. + * * Modules implementing this hook should load field values and add them to * objects in $entities. Fields with no values should be added as empty * arrays. @@ -1267,16 +1365,20 @@ function hook_field_storage_details_alter(&$details, $field) { * depending on the $age parameter) to add each field to. * @param $options * An associative array of additional options, with the following keys: - * - 'deleted': If TRUE, deleted fields should be loaded as well as + * - deleted: If TRUE, deleted fields should be loaded as well as * non-deleted fields. If unset or FALSE, only non-deleted fields should be * loaded. */ -function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { +function hook_field_storage_load($entity_type, &$entities, $age, $fields, $options) { + // @todo Needs function body. } /** * Write field data for an entity. * + * This hook is invoked from field_attach_insert() and field_attach_update(), + * to ask the field storage module to save field data. + * * @param $entity_type * The entity type of entity, such as 'node' or 'user'. * @param $entity @@ -1286,28 +1388,36 @@ function hook_field_storage_load($entity_type, $entities, $age, $fields, $option * FIELD_STORAGE_INSERT when inserting a new entity. * @param $fields * An array listing the fields to be written. The keys and values of the - * array are field ids. + * array are field IDs. */ function hook_field_storage_write($entity_type, $entity, $op, $fields) { + // @todo Needs function body. } /** * Delete all field data for an entity. * + * This hook is invoked from field_attach_delete() to ask the field storage + * module to delete field data. + * * @param $entity_type * The entity type of entity, such as 'node' or 'user'. * @param $entity * The entity on which to operate. * @param $fields * An array listing the fields to delete. The keys and values of the - * array are field ids. + * array are field IDs. */ function hook_field_storage_delete($entity_type, $entity, $fields) { + // @todo Needs function body. } /** * Delete a single revision of field data for an entity. * + * This hook is invoked from field_attach_delete_revision() to ask the field + * storage module to delete field revision data. + * * Deleting the current (most recently written) revision is not * allowed as has undefined results. * @@ -1315,58 +1425,76 @@ function hook_field_storage_delete($entity_type, $entity, $fields) { * The entity type of entity, such as 'node' or 'user'. * @param $entity * The entity on which to operate. The revision to delete is - * indicated by the entity's revision id property, as identified by + * indicated by the entity's revision ID property, as identified by * hook_fieldable_info() for $entity_type. * @param $fields * An array listing the fields to delete. The keys and values of the - * array are field ids. + * array are field IDs. */ function hook_field_storage_delete_revision($entity_type, $entity, $fields) { + // @todo Needs function body. } /** * Handle a field query. * + * This hook is invoked from field_attach_query() to ask the field storage + * module to handle a field query. + * * @param $field_name * The name of the field to query. * @param $conditions - * See field_attach_query(). - * A storage module that doesn't support querying a given column should raise - * a FieldQueryException. Incompatibilities should be mentioned on the module - * project page. + * See field_attach_query(). A storage module that doesn't support querying a + * given column should raise a FieldQueryException. Incompatibilities should + * be mentioned on the module project page. * @param $options * See field_attach_query(). All option keys are guaranteed to be specified. + * * @return * See field_attach_query(). */ function hook_field_storage_query($field_name, $conditions, $options) { + // @todo Needs function body } /** * Act on creation of a new field. * + * This hook is invoked from field_create_field() to ask the field storage + * module to save field information and prepare for storing field instances. + * If there is a problem, the field storage module should throw an exception. + * * @param $field * The field structure being created. */ function hook_field_storage_create_field($field) { + // @todo Needs function body. } /** * Act on deletion of a field. * + * This hook is invoked from field_delete_field() to ask the field storage + * module to mark all information stored in the field for deletion. + * * @param $field * The field being deleted. */ function hook_field_storage_delete_field($field) { + // @todo Needs function body. } /** * Act on deletion of a field instance. * + * This hook is invoked from field_delete_instance() to ask the field storage + * module to mark all information stored for the field instance for deletion. + * * @param $instance * The instance being deleted. */ function hook_field_storage_delete_instance($instance) { + // @todo Needs function body. } /** @@ -1396,13 +1524,14 @@ function hook_field_storage_delete_instance($instance) { * that your module has already loaded a field. * @param $options * An associative array of additional options, with the following keys: - * - 'field_id': The field ID that should be loaded. If unset, all fields + * - field_id: The field ID that should be loaded. If unset, all fields * should be loaded. - * - 'deleted': If TRUE, deleted fields should be loaded as well as + * - deleted: If TRUE, deleted fields should be loaded as well as * non-deleted fields. If unset or FALSE, only non-deleted fields should be * loaded. */ function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_fields, $options) { + // @todo Needs function body. } /** @@ -1412,15 +1541,15 @@ function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_field * optionally preventing the field storage module from doing so. * * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. + * The type of $entity; for example, 'node' or 'user'. * @param $entity * The entity with fields to save. * @param $skip_fields - * An array keyed by field ids whose data has already been written and - * therefore should not be written again. The values associated to these keys - * are not specified. + * An array keyed by field IDs whose data has already been written and + * therefore should not be written again. The values associated with these + * keys are not specified. * @return - * Saved field ids are set set as keys in $skip_fields. + * Saved field IDs are set set as keys in $skip_fields. */ function hook_field_storage_pre_insert($entity_type, $entity, &$skip_fields) { if ($entity_type == 'node' && $entity->status && _forum_node_check_node_type($entity)) { @@ -1449,15 +1578,15 @@ function hook_field_storage_pre_insert($entity_type, $entity, &$skip_fields) { * optionally preventing the field storage module from doing so. * * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. + * The type of $entity; for example, 'node' or 'user'. * @param $entity * The entity with fields to save. * @param $skip_fields - * An array keyed by field ids whose data has already been written and - * therefore should not be written again. The values associated to these keys - * are not specified. + * An array keyed by field IDs whose data has already been written and + * therefore should not be written again. The values associated with these + * keys are not specified. * @return - * Saved field ids are set set as keys in $skip_fields. + * Saved field IDs are set set as keys in $skip_fields. */ function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) { $first_call = &drupal_static(__FUNCTION__, array()); @@ -1519,6 +1648,7 @@ function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) { * handled. */ function hook_field_storage_pre_query($field_name, $conditions, $options, &$skip_field) { + // @todo Needs function body. } /** @@ -1537,27 +1667,27 @@ function hook_field_storage_pre_query($field_name, $conditions, $options, &$skip /** * Act on a field being created. * - * This hook is invoked after the field is created and so it cannot modify the - * field itself. - * - * TODO: Not implemented. + * This hook is invoked from field_create_field() after the field is created, to + * allow modules to act on field creation. * * @param $field * The field just created. */ function hook_field_create_field($field) { + // @todo Needs function body. } /** * Act on a field instance being created. * - * This hook is invoked after the instance record is saved and so it cannot - * modify the instance itself. + * This hook is invoked from field_create_instance() after the instance record + * is saved, so it cannot be used to modify the instance itself. * * @param $instance * The instance just created. */ function hook_field_create_instance($instance) { + // @todo Needs function body. } /** @@ -1570,16 +1700,16 @@ function hook_field_create_instance($instance) { * semantics, or if there are external dependencies on field settings * that cannot be updated. * + * To forbid the update from occurring, throw a FieldUpdateForbiddenException. + * * @param $field * The field as it will be post-update. * @param $prior_field * The field as it is pre-update. * @param $has_data * Whether any data already exists for this field. - * @return - * Throws a FieldUpdateForbiddenException to prevent the update from occurring. */ -function hook_field_update_field_forbid($field, $prior_field, $has_data) { +function hook_field_update_forbid($field, $prior_field, $has_data) { // A 'list' field stores integer keys mapped to display values. If // the new field will have fewer values, and any data exists for the // abandoned keys, the field will have no way to display them. So, @@ -1598,7 +1728,7 @@ function hook_field_update_field_forbid($field, $prior_field, $has_data) { /** * Act on a field being updated. * - * This hook is invoked just after field is updated. + * This hook is invoked just after field is updated in field_update_field(). * * @param $field * The field as it is post-update. @@ -1608,24 +1738,26 @@ function hook_field_update_field_forbid($field, $prior_field, $has_data) { * Whether any data already exists for this field. */ function hook_field_update_field($field, $prior_field, $has_data) { + // @todo Needs function body. } /** * Act on a field being deleted. * - * This hook is invoked just after field is deleted. + * This hook is invoked just after a field is deleted by field_delete_field(). * * @param $field * The field just deleted. */ function hook_field_delete_field($field) { + // @todo Needs function body. } /** * Act on a field instance being updated. * - * This hook is invoked after the instance record is saved and so it cannot - * modify the instance itself. + * This hook is invoked from field_update_instance() after the instance record + * is saved, so it cannot be used by a module to modify the instance itself. * * @param $instance * The instance as it is post-update. @@ -1633,35 +1765,44 @@ function hook_field_delete_field($field) { * The instance as it was pre-update. */ function hook_field_update_instance($instance, $prior_instance) { + // @todo Needs function body. } /** * Act on a field instance being deleted. * - * This hook is invoked just after the instance is deleted. + * This hook is invoked from field_delete_instance() after the instance is + * deleted. * * @param $instance * The instance just deleted. */ function hook_field_delete_instance($instance) { + // @todo Needs function body. } /** * Act on field records being read from the database. * + * This hook is invoked from field_read_fields() on each field being read. + * * @param $field * The field record just read from the database. */ -function hook_field_read_field($field) { +function hook_field_read_field(&$field) { + // @todo Needs function body. } /** * Act on a field record being read from the database. * + * This hook is invoked from field_read_instances() on each instance being read. + * * @param $instance * The instance record just read from the database. */ function hook_field_read_instance($instance) { + // @todo Needs function body. } /** @@ -1739,7 +1880,7 @@ function hook_field_storage_purge_field_instance($instance) { * module to delete field data information. * * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. + * The type of $entity; for example, 'node' or 'user'. * @param $entity * The pseudo-entity whose field data to delete. * @param $field @@ -1774,21 +1915,23 @@ function hook_field_storage_purge($entity_type, $entity, $field, $instance) { /** * Determine whether the user has access to a given field. * + * This hook is invoked from field_access() to let modules block access to + * operations on fields. If no module returns FALSE, the operation is allowed. + * * @param $op - * The operation to be performed. Possible values: - * - "edit" - * - "view" + * The operation to be performed. Possible values: 'edit', 'view'. * @param $field * The field on which the operation is to be performed. * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. + * The type of $entity; for example, 'node' or 'user'. * @param $entity * (optional) The entity for the operation. * @param $account - * (optional) The account to check, if not given use currently logged in user. + * (optional) The account to check; if not given use currently logged in user. + * * @return - * TRUE if the operation is allowed; - * FALSE if the operation is denied. + * TRUE if the operation is allowed, and FALSE if the operation is denied. */ function hook_field_access($op, $field, $entity_type, $entity, $account) { + // @todo Needs function body. } diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index ab4bf81d4..573c58bb8 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.attach.inc,v 1.86 2010/04/04 12:48:18 dries Exp $ +// $Id: field.attach.inc,v 1.87 2010/05/23 07:30:56 dries Exp $ /** * @file @@ -555,6 +555,9 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod $form['#pre_render'][] = '_field_extra_weights_pre_render'; $form['#extra_fields'] = field_extra_fields($entity_type, $bundle); + // Save the original entity to allow later re-use. + $form_state['entity'] = $entity; + // Let other modules make changes to the form. // Avoid module_invoke_all() to let parameters be taken by reference. foreach (module_implements('field_attach_form') as $module) { diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index 071b58aa2..71aa31d11 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.crud.inc,v 1.57 2010/04/24 07:19:09 dries Exp $ +// $Id: field.crud.inc,v 1.59 2010/05/09 13:40:48 dries Exp $ /** * @file @@ -127,6 +127,8 @@ * field_attach_insert(), or field_attach_update(). * - default_value_function (string) * The name of the function, if any, that will provide a default value. + * - default_value (array) + * If default_value_function is not set, then fixed values can be provided. * - deleted (integer, read-only) * TRUE if this instance has been deleted, FALSE otherwise. * Deleted instances are ignored by the Field Attach API. @@ -225,6 +227,8 @@ * The $field array with the id property filled in. * @throw * FieldException + * + * See: @link field_structs Field API data structures @endlink. */ function field_create_field($field) { // Field name is required. @@ -613,6 +617,8 @@ function field_delete_field($field_name) { * The $instance array with the id property filled in. * @throw * FieldException + * + * See: @link field_structs Field API data structures @endlink. */ function field_create_instance($instance) { $field = field_read_field($instance['field_name']); @@ -1099,7 +1105,7 @@ function field_purge_instance($instance) { function field_purge_field($field) { $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); if (count($instances) > 0) { - throw new FieldException("Attempt to purge a field that still has instances."); + throw new FieldException("Attempt to purge a field, @field_name that still has instances.", array('@field_name' => $field['field_name'])); } db_delete('field_config') diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index 3371ee833..b47f866dc 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.default.inc,v 1.34 2010/03/27 05:52:49 webchick Exp $ +// $Id: field.default.inc,v 1.35 2010/05/23 07:30:56 dries Exp $ /** * @file @@ -67,13 +67,23 @@ function field_default_validate($entity_type, $entity, $field, $instance, $langc function field_default_submit($entity_type, $entity, $field, $instance, $langcode, &$items, $form, &$form_state) { $field_name = $field['field_name']; - // Reorder items to account for drag-n-drop reordering. - if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $items = _field_sort_items($field, $items); + if (isset($form_state['values'][$field_name][$langcode])) { + // Reorder items to account for drag-n-drop reordering. + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $items = _field_sort_items($field, $items); + } + // Filter out empty values. + $items = _field_filter_items($field, $items); + } + elseif (!empty($entity->revision) && isset($form_state['entity']->{$field_name}[$langcode])) { + // To ensure new revisions are created with all field values in all + // languages, populate values not included in the form with the ones from + // the original object. This covers: + // - partial forms including only a subset of the fields, + // - fields for which the user has no edit access, + // - languages not involved in the form. + $items = $form_state['entity']->{$field_name}[$langcode]; } - - // Filter out empty values. - $items = _field_filter_items($field, $items); } /** diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index cbc39e05d..77bd86118 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.form.inc,v 1.47 2010/04/13 15:23:02 dries Exp $ +// $Id: field.form.inc,v 1.49 2010/05/23 07:30:56 dries Exp $ /** * @file @@ -16,19 +16,10 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, list($id, , ) = entity_extract_ids($entity_type, $entity); } + $addition = array(); $field_name = $field['field_name']; $addition[$field_name] = array(); - // Store field information in $form_state['storage']. - $form_state['field'][$field_name][$langcode] = array( - 'field' => $field, - 'instance' => $instance, - // This entry will be populated at form build time. - 'array_parents' => array(), - // This entry will be populated at form validation time. - 'errors' => array(), - ); - // Populate widgets with default values when creating a new entity. if (empty($items) && empty($id)) { $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode); @@ -79,6 +70,16 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, } if ($elements) { + // Store field information in $form_state. + $form_state['field'][$field_name][$langcode] = array( + 'field' => $field, + 'instance' => $instance, + // This entry will be populated at form build time. + 'array_parents' => array(), + // This entry will be populated at form validation time. + 'errors' => array(), + ); + // Also aid in theming of field widgets by rendering a classified // container. $addition[$field_name] = array( @@ -93,16 +94,6 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, '#weight' => $instance['widget']['weight'], ); } - else { - // The field is not accessible, or the widget did not return anything. Make - // sure the items are available in the submitted form values. - foreach ($items as $delta => $item) { - $elements[$delta] = array( - '#type' => 'value', - '#value' => $item, - ); - } - } // Populate the 'array_parents' information in $form_state['field'] after // the form is built, so that we catch changes in the form structure performed @@ -215,7 +206,6 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form, '#ajax' => array( 'callback' => 'field_add_more_js', 'wrapper' => $wrapper_id, - 'method' => 'replace', 'effect' => 'fade', ), // The field_add_more_submit() and field_add_more_js() handlers will diff --git a/modules/field/field.info b/modules/field/field.info index 9f3e2453c..c4178aef6 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -16,8 +16,8 @@ files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" 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 60f6a00c2..34b1358a2 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -9,8 +9,8 @@ files[] = field_sql_storage.install files[] = field_sql_storage.test required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" 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 4ce68ad92..a3c03d241 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -1,5 +1,5 @@ <?php -// $Id: field_sql_storage.module,v 1.44 2010/03/27 05:52:49 webchick Exp $ +// $Id: field_sql_storage.module,v 1.46 2010/05/06 15:29:51 dries Exp $ /** * @file @@ -40,7 +40,12 @@ function field_sql_storage_field_storage_info() { * A string containing the generated name for the database table */ function _field_sql_storage_tablename($field) { - return "field_data_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : ''); + if ($field['deleted']) { + return "field_deleted_data_{$field['id']}"; + } + else { + return "field_data_{$field['field_name']}"; + } } /** @@ -52,7 +57,12 @@ function _field_sql_storage_tablename($field) { * A string containing the generated name for the database table */ function _field_sql_storage_revision_tablename($field) { - return "field_revision_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : ''); + if ($field['deleted']) { + return "field_deleted_revision_{$field['id']}"; + } + else { + return "field_revision_{$field['field_name']}"; + } } /** @@ -218,7 +228,7 @@ function field_sql_storage_field_storage_create_field($field) { } /** - * Implements hook_field_update_field_forbid(). + * Implements hook_field_update_forbid(). * * Forbid any field update that changes column definitions if there is * any data. diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index 9f0b8adab..ad0f9e838 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -8,8 +8,8 @@ files[]=list.module files[]=tests/list.test required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index 2a17b44f4..d3d7b75b7 100644 --- a/modules/field/modules/list/list.module +++ b/modules/field/modules/list/list.module @@ -1,5 +1,5 @@ <?php -// $Id: list.module,v 1.30 2010/03/27 12:49:32 dries Exp $ +// $Id: list.module,v 1.32 2010/05/12 08:55:47 dries Exp $ /** * @file @@ -73,7 +73,6 @@ function list_field_schema($field) { $columns = array( 'value' => array( 'type' => 'float', - 'unsigned' => TRUE, 'not null' => FALSE, ), ); @@ -82,7 +81,6 @@ function list_field_schema($field) { $columns = array( 'value' => array( 'type' => 'int', - 'unsigned' => TRUE, 'not null' => FALSE, ), ); @@ -310,7 +308,7 @@ function list_field_validate($entity_type, $entity, $field, $instance, $langcode * Implements hook_field_is_empty(). */ function list_field_is_empty($item, $field) { - if (empty($item['value']) && (string)$item['value'] !== '0') { + if (empty($item['value']) && (string) $item['value'] !== '0') { return TRUE; } return FALSE; diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index cb448199c..afd5ddbf4 100644 --- a/modules/field/modules/list/tests/list_test.info +++ b/modules/field/modules/list/tests/list_test.info @@ -7,8 +7,8 @@ files[] = list_test.module version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index 65fee6a94..20cf84eb2 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -7,8 +7,8 @@ core = 7.x files[]=number.module required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 454e2b501..8a377e475 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -1,5 +1,5 @@ <?php -// $Id: number.module,v 1.38 2010/04/23 05:48:13 webchick Exp $ +// $Id: number.module,v 1.39 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -214,7 +214,7 @@ function number_field_presave($entity_type, $entity, $field, $instance, $langcod * Implements hook_field_is_empty(). */ function number_field_is_empty($item, $field) { - if (empty($item['value']) && (string)$item['value'] !== '0') { + if (empty($item['value']) && (string) $item['value'] !== '0') { return TRUE; } return FALSE; diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 423355b2b..64c9f8883 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -8,8 +8,8 @@ files[]=options.module files[]=options.test required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index a6c4aea7f..c242cb79f 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -8,8 +8,8 @@ files[] = text.module files[] = text.test required = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 3d41bffca..83bee6df3 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -1,5 +1,5 @@ <?php -// $Id: text.module,v 1.53 2010/04/13 15:16:27 dries Exp $ +// $Id: text.module,v 1.54 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -221,7 +221,7 @@ function text_field_load($entity_type, $entities, $field, $instances, $langcode, * Implements hook_field_is_empty(). */ function text_field_is_empty($item, $field) { - if (empty($item['value']) && (string)$item['value'] !== '0') { + if (empty($item['value']) && (string) $item['value'] !== '0') { return TRUE; } return FALSE; diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index a9b4bd21f..dbcdaaab2 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -1,5 +1,5 @@ <?php -// $Id: field.test,v 1.27 2010/04/11 18:33:43 dries Exp $ +// $Id: field.test,v 1.30 2010/05/23 07:30:56 dries Exp $ /** * @file @@ -1576,7 +1576,7 @@ class FieldFormTestCase extends FieldTestCase { // We'll need three slightly different formats to check the values. $values[$delta] = $value; $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string)$value; + $field_values[$weight]['value'] = (string) $value; $pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*"; } @@ -1644,7 +1644,7 @@ class FieldFormTestCase extends FieldTestCase { // We'll need three slightly different formats to check the values. $values[$delta] = $value; $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string)$value; + $field_values[$weight]['value'] = (string) $value; $pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*"; } // Press 'add more' button through AJAX, and place the expected HTML result @@ -2715,7 +2715,7 @@ class FieldTranslationsTestCase extends FieldTestCase { $results = _field_invoke('test_op', $entity_type, $entity); foreach ($results as $langcode => $result) { - $hash = md5(serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); + $hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); // Check whether the parameters passed to _field_invoke() were correctly // forwarded to the callback function. $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode))); @@ -2757,7 +2757,7 @@ class FieldTranslationsTestCase extends FieldTestCase { $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities); foreach ($grouped_results as $id => $results) { foreach ($results as $langcode => $result) { - $hash = md5(serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); + $hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); // Check whether the parameters passed to _field_invoke() were correctly // forwarded to the callback function. $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); @@ -2878,6 +2878,51 @@ class FieldTranslationsTestCase extends FieldTestCase { $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language); $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); } + + /** + * Tests field translations when creating a new revision. + */ + function testFieldFormTranslationRevisions() { + $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->drupalLogin($web_user); + + // Prepare the field translations. + field_test_entity_info_translatable($this->entity_type, TRUE); + $eid = 1; + $entity = field_test_create_stub_entity($eid, $eid, $this->instance['bundle']); + $available_languages = array_flip(field_available_languages($this->entity_type, $this->field)); + unset($available_languages[LANGUAGE_NONE]); + $field_name = $this->field['field_name']; + + // Store the field translations. + $entity->is_new = TRUE; + foreach ($available_languages as $langcode => $value) { + $entity->{$field_name}[$langcode][0]['value'] = $value + 1; + } + field_test_entity_save($entity); + + // Create a new revision. + $langcode = field_valid_language(NULL); + $edit = array("{$field_name}[$langcode][0][value]" => $entity->{$field_name}[$langcode][0]['value'], 'revision' => TRUE); + $this->drupalPost('test-entity/' . $eid . '/edit', $edit, t('Save')); + + // Check translation revisions. + $this->checkTranslationRevisions($eid, $eid, $available_languages); + $this->checkTranslationRevisions($eid, $eid + 1, $available_languages); + } + + /** + * Check if the field translation attached to the entity revision identified + * by the passed arguments were correctly stored. + */ + private function checkTranslationRevisions($eid, $evid, $available_languages) { + $field_name = $this->field['field_name']; + $entity = field_test_entity_test_load($eid, $evid); + foreach ($available_languages as $langcode => $value) { + $passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1; + $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); + } + } } /** diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc index 77203a6de..066e7e687 100644 --- a/modules/field/tests/field_test.entity.inc +++ b/modules/field/tests/field_test.entity.inc @@ -1,5 +1,5 @@ <?php -// $Id: field_test.entity.inc,v 1.7 2010/03/27 18:41:14 dries Exp $ +// $Id: field_test.entity.inc,v 1.9 2010/05/23 07:30:56 dries Exp $ /** * @file @@ -164,11 +164,19 @@ function field_test_create_stub_entity($id = 1, $vid = 1, $bundle = 'test_bundle function field_test_entity_test_load($ftid, $ftvid = NULL) { // Load basic strucure. $query = db_select('test_entity', 'fte', array()) - ->fields('fte') - ->condition('ftid', $ftid); + ->condition('fte.ftid', $ftid); + if ($ftvid) { - $query->condition('ftvid', $ftvid); + $query->join('test_entity_revision', 'fter', 'fte.ftid = fter.ftid'); + $query->addField('fte', 'ftid'); + $query->addField('fte', 'fttype'); + $query->addField('fter', 'ftvid'); + $query->condition('fter.ftvid', $ftvid); + } + else { + $query->fields('fte'); } + $entities = $query->execute()->fetchAllAssoc('ftid'); // Attach fields. @@ -255,9 +263,9 @@ function field_test_entity_edit($entity) { */ function field_test_entity_form($form, &$form_state, $entity, $add = FALSE) { if (isset($form_state['test_entity'])) { - $entity = $form_state['test_entity'] + (array)$entity; + $entity = $form_state['test_entity'] + (array) $entity; } - $entity = (object)$entity; + $entity = (object) $entity; foreach (array('ftid', 'ftvid', 'fttype') as $key) { $form[$key] = array( @@ -325,7 +333,7 @@ function field_test_entity_form_submit_builder($form, &$form_state) { $entity->revision = !empty($form_state['values']['revision']); field_attach_submit('test_entity', $entity, $form, $form_state); - $form_state['test_entity'] = (array)$entity; + $form_state['test_entity'] = (array) $entity; $form_state['rebuild'] = TRUE; return $entity; diff --git a/modules/field/tests/field_test.field.inc b/modules/field/tests/field_test.field.inc index 8bd55c2e1..50d96811e 100644 --- a/modules/field/tests/field_test.field.inc +++ b/modules/field/tests/field_test.field.inc @@ -1,5 +1,5 @@ <?php -// $Id: field_test.field.inc,v 1.9 2010/03/12 19:51:40 dries Exp $ +// $Id: field_test.field.inc,v 1.10 2010/05/18 18:30:49 dries Exp $ /** * @file @@ -12,7 +12,7 @@ function field_test_field_info() { return array( 'test_field' => array( - 'label' => t('Test Field'), + 'label' => t('Test field'), 'description' => t('Dummy field type used for tests.'), 'settings' => array( 'test_field_setting' => 'dummy test string', @@ -26,6 +26,15 @@ function field_test_field_info() { 'default_widget' => 'test_field_widget', 'default_formatter' => 'field_test_default', ), + 'hidden_test_field' => array( + 'no_ui' => TRUE, + 'label' => t('Hidden from UI test field'), + 'description' => t('Dummy hidden field type used for tests.'), + 'settings' => array(), + 'instance_settings' => array(), + 'default_widget' => 'test_field_widget', + 'default_formatter' => 'field_test_default', + ), ); } @@ -139,7 +148,7 @@ function field_test_field_widget_info() { return array( 'test_field_widget' => array( 'label' => t('Test field'), - 'field types' => array('test_field'), + 'field types' => array('test_field', 'hidden_test_field'), 'settings' => array('test_widget_setting' => 'dummy test string'), ), 'test_field_widget_multiple' => array( diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index 325e8abb4..09940f99c 100644 --- a/modules/field/tests/field_test.info +++ b/modules/field/tests/field_test.info @@ -11,8 +11,8 @@ files[] = field_test.install version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module index 7cd2e3266..3900a0b7b 100644 --- a/modules/field/tests/field_test.module +++ b/modules/field/tests/field_test.module @@ -1,5 +1,5 @@ <?php -// $Id: field_test.module,v 1.6 2010/03/25 11:46:20 dries Exp $ +// $Id: field_test.module,v 1.7 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -69,7 +69,7 @@ function field_test_menu() { * This simulates a field operation callback to be invoked by _field_invoke(). */ function field_test_field_test_op($entity_type, $entity, $field, $instance, $langcode, &$items) { - return array($langcode => md5(serialize(array($entity_type, $entity, $field['field_name'], $langcode, $items)))); + return array($langcode => hash('sha256', serialize(array($entity_type, $entity, $field['field_name'], $langcode, $items)))); } /** @@ -81,7 +81,7 @@ function field_test_field_test_op($entity_type, $entity, $field, $instance, $lan function field_test_field_test_op_multiple($entity_type, $entities, $field, $instances, $langcode, &$items) { $result = array(); foreach ($entities as $id => $entity) { - $result[$id] = array($langcode => md5(serialize(array($entity_type, $entity, $field['field_name'], $langcode, $items[$id])))); + $result[$id] = array($langcode => hash('sha256', serialize(array($entity_type, $entity, $field['field_name'], $langcode, $items[$id])))); } return $result; } diff --git a/modules/field/theme/field-rtl.css b/modules/field/theme/field-rtl.css index 90bcc73e8..723eac495 100644 --- a/modules/field/theme/field-rtl.css +++ b/modules/field/theme/field-rtl.css @@ -1,4 +1,4 @@ -/* $Id: field-rtl.css,v 1.1 2009/03/30 05:28:41 webchick Exp $ */ +/* $Id: field-rtl.css,v 1.2 2010/05/22 20:23:01 dries Exp $ */ form .field-multiple-table th.field-label { padding-right: 0; @@ -9,3 +9,7 @@ form .field-multiple-table td.field-multiple-drag { form .field-multiple-table td.field-multiple-drag a.tabledrag-handle{ padding-left: .5em; } +.field-label-inline .field-label, +.field-label-inline .field-items { + float: right; +} diff --git a/modules/field/theme/field.css b/modules/field/theme/field.css index 44518619b..e23f50d21 100644 --- a/modules/field/theme/field.css +++ b/modules/field/theme/field.css @@ -1,4 +1,4 @@ -/* $Id: field.css,v 1.8 2009/11/11 08:32:35 dries Exp $ */ +/* $Id: field.css,v 1.9 2010/05/22 20:23:01 dries Exp $ */ /* Field display */ .field .field-label { @@ -6,7 +6,7 @@ } .field-label-inline .field-label, .field-label-inline .field-items { - float:left; + float:left; /*LTR*/ } /* Form display */ diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index f8070016a..629abcc34 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: field_ui.admin.inc,v 1.49 2010/04/24 14:49:13 dries Exp $ +// $Id: field_ui.admin.inc,v 1.50 2010/05/18 18:30:49 dries Exp $ /** * @file @@ -703,8 +703,9 @@ function field_ui_field_type_options() { $field_types = field_info_field_types(); $field_type_options = array(); foreach ($field_types as $name => $field_type) { - // Skip field types which have no widget types. - if (field_ui_widget_type_options($name)) { + // Skip field types which have no widget types, or should not be add via + // uesr interface. + if (field_ui_widget_type_options($name) && empty($field_type['no_ui'])) { $options[$name] = $field_type['label']; } } @@ -793,10 +794,13 @@ function field_ui_existing_field_options($entity_type, $bundle) { // Don't show // - locked fields, // - fields already in the current bundle, - // - field that cannot be added to the entity type. + // - fields that cannot be added to the entity type, + // - fields that that shoud not be added via user interface. + if (empty($field['locked']) && !field_info_instance($entity_type, $field['field_name'], $bundle) - && (empty($field['entity_types']) || in_array($entity_type, $field['entity_types']))) { + && (empty($field['entity_types']) || in_array($entity_type, $field['entity_types'])) + && empty($field_types[$field['type']]['no_ui'])) { $text = t('@type: @field (@label)', array( '@type' => $field_types[$field['type']]['label'], '@label' => t($instance['label']), '@field' => $instance['field_name'], diff --git a/modules/field_ui/field_ui.api.php b/modules/field_ui/field_ui.api.php index 04eb99cc1..f5ed10256 100644 --- a/modules/field_ui/field_ui.api.php +++ b/modules/field_ui/field_ui.api.php @@ -1,5 +1,5 @@ <?php -// $Id: field_ui.api.php,v 1.5 2010/04/26 14:40:47 dries Exp $ +// $Id: field_ui.api.php,v 1.6 2010/05/04 16:11:08 dries Exp $ /** * @file @@ -12,11 +12,12 @@ */ /** - * Field settings form. + * Add settings to a field settings form. * - * The field type module is responsible for not returning form elements that - * cannot be changed. It may not always be possible to tell in advance, but - * modules should attempt to inform the user what is going/likely to work. + * Invoked from field_ui_field_settings_form() to allow the module defining the + * field to add global settings (i.e. settings that do not depend on the bundle + * or instance) to the field settings form. If the field already has data, only + * include settings that are safe to change. * * @todo: Only the field type module knows which settings will affect the * field's schema, but only the field storage module knows what schema @@ -30,7 +31,8 @@ * @param $instance * The instance structure being configured. * @param $has_data - * Whether the field already has data. + * TRUE if the field already has data, FALSE if not. + * * @return * The form definition for the field settings. */ @@ -48,12 +50,16 @@ function hook_field_settings_form($field, $instance, $has_data) { } /** - * Instance settings form. + * Add settings to an instance field settings form. + * + * Invoked from field_ui_field_edit_form() to allow the module defining the + * field to add settings for a field instance. * * @param $field * The field structure being configured. * @param $instance * The instance structure being configured. + * * @return * The form definition for the field instance settings. */ @@ -86,12 +92,16 @@ function hook_field_instance_settings_form($field, $instance) { } /** - * Widget settings form. + * Add settings to a widget settings form. + * + * Invoked from field_ui_field_edit_form() to allow the module defining the + * widget to add settings for a widget instance. * * @param $field * The field structure being configured. * @param $instance * The instance structure being configured. + * * @return * The form definition for the widget settings. */ @@ -121,27 +131,6 @@ function hook_field_widget_settings_form($field, $instance) { return $form; } -/** - * Formatter settings form. - * - * @todo Not implemented yet. The signature below is only prospective, but - * providing $instance is not enough, since one $instance holds several display - * settings. - * - * @param $formatter - * The type of the formatter being configured. - * @param $settings - * The current values of the formatter settings. - * @param $field - * The field structure being configured. - * @param $instance - * The instance structure being configured. - * @return - * The form definition for the formatter settings. - */ -function hook_field_formatter_settings_form($formatter, $settings, $field, $instance) { -} - /** * Provide information on view mode tabs for an entity type. * diff --git a/modules/field_ui/field_ui.info b/modules/field_ui/field_ui.info index b2331b32a..989fcc6d3 100644 --- a/modules/field_ui/field_ui.info +++ b/modules/field_ui/field_ui.info @@ -8,8 +8,8 @@ files[] = field_ui.module files[] = field_ui.admin.inc files[] = field_ui.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test index d56016352..337dba1bc 100644 --- a/modules/field_ui/field_ui.test +++ b/modules/field_ui/field_ui.test @@ -1,5 +1,5 @@ <?php -// $Id: field_ui.test,v 1.15 2010/04/07 17:30:43 dries Exp $ +// $Id: field_ui.test,v 1.16 2010/05/18 18:30:49 dries Exp $ /** * @file @@ -125,7 +125,7 @@ class FieldUITestCase extends DrupalWebTestCase { */ function addExistingField() { // Check "Add existing field" appears. - $this->drupalGet(('admin/structure/types/manage/page/fields')); + $this->drupalGet('admin/structure/types/manage/page/fields'); $this->assertRaw(t('Add existing field'), t('"Add existing field" was found.')); // Check that the list of options respects entity type restrictions on @@ -263,6 +263,41 @@ class FieldUITestCase extends DrupalWebTestCase { $this->assertNull(field_info_field($this->field_name), t('Field was deleted.')); } + /** + * Test that Field UI respects the 'no_ui' option in hook_field_info(). + */ + function testHiddenFields() { + $bundle_path = 'admin/structure/types/manage/' . $this->hyphen_type . '/fields/'; + + // Check that the field type is not available in the 'add new field' row. + $this->drupalGet($bundle_path); + $this->assertFalse($this->xpath('//select[@id="edit--add-new-field-type"]//option[@value="hidden_test_field"]'), t("The 'add new field' select respects field types 'no_ui' property.")); + + // Create a field and an instance programmatically. + $field_name = 'hidden_test_field'; + field_create_field(array('field_name' => $field_name, 'type' => $field_name)); + $instance = array( + 'field_name' => $field_name, + 'bundle' => $this->type, + 'entity_type' => 'node', + 'label' => t('Hidden field'), + 'widget_type' => 'test_field_widget', + ); + field_create_instance($instance); + $this->assertTrue(field_read_instance('node', $field_name, $this->type), t('An instance of the field %field was created programmatically.', array('%field' => $field_name))); + + // Check that the newly added instance appears on the 'Manage Fields' + // screen. + $this->drupalGet($bundle_path); + $this->assertFieldByXPath('//table[@id="field-overview"]//span[@class="label-field"]', $instance['label'], t('Field was created and appears in the overview page.')); + + // Check that the instance does not appear in the 'add existing field' row + // on other bundles. + $bundle_path = 'admin/structure/types/manage/article/fields/'; + $this->drupalGet($bundle_path); + $this->assertFalse($this->xpath('//select[@id="edit--add-existing-field-field-name"]//option[@value=:field_name]', array(':field_name' => $field_name)), t("The 'add existing field' select respects field types 'no_ui' property.")); + } + /** * Create a new field through the Field UI. * diff --git a/modules/file/file.info b/modules/file/file.info index 5e8edc90e..8e37c4cab 100644 --- a/modules/file/file.info +++ b/modules/file/file.info @@ -9,8 +9,8 @@ files[] = file.field.inc files[] = file.install files[] = tests/file.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/file/file.js b/modules/file/file.js index 837e15c4c..f89ab8a79 100644 --- a/modules/file/file.js +++ b/modules/file/file.js @@ -1,4 +1,4 @@ -// $Id: file.js,v 1.2 2010/04/21 07:40:37 webchick Exp $ +// $Id: file.js,v 1.3 2010/05/01 21:55:13 dries Exp $ /** * @file @@ -31,7 +31,7 @@ Drupal.behaviors.fileButtons = { $('input.form-submit', context).bind('mousedown', Drupal.file.disableFields); $('div.form-managed-file input.form-submit', context).bind('mousedown', Drupal.file.progressBar); }, - unattach: function (context) { + detach: function (context) { $('input.form-submit', context).unbind('mousedown', Drupal.file.disableFields); $('div.form-managed-file input.form-submit', context).unbind('mousedown', Drupal.file.progressBar); } diff --git a/modules/file/file.module b/modules/file/file.module index b8f78b777..738aa5d9b 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -1,5 +1,5 @@ <?php -// $Id: file.module,v 1.25 2010/04/13 15:23:03 dries Exp $ +// $Id: file.module,v 1.28 2010/05/09 19:34:26 dries Exp $ /** * @file @@ -351,7 +351,6 @@ function file_managed_file_process($element, &$form_state, $form) { $ajax_settings = array( 'path' => 'file/ajax/' . implode('/', $element['#parents']) . '/' . $form['form_build_id']['#value'], 'wrapper' => $element['#id'] . '-ajax-wrapper', - 'method' => 'replace', 'effect' => 'fade', 'progress' => array( 'type' => $element['#progress_indicator'], @@ -411,7 +410,7 @@ function file_managed_file_process($element, &$form_state, $form) { // Add progress bar support to the upload if possible. if ($element['#progress_indicator'] == 'bar' && $implementation = file_progress_implementation()) { - $upload_progress_key = md5(mt_rand()); + $upload_progress_key = mt_rand(); if ($implementation == 'uploadprogress') { $element['UPLOAD_IDENTIFIER'] = array( @@ -651,10 +650,10 @@ function theme_file_link($variables) { // Use the description as the link text if available. if (empty($file->description)) { - $link_text = check_plain($file->filename); + $link_text = $file->filename; } else { - $link_text = check_plain($file->description); + $link_text = $file->description; $options['attributes']['title'] = check_plain($file->filename); } diff --git a/modules/file/tests/file_module_test.info b/modules/file/tests/file_module_test.info index 45a8f9930..5a9f9fbdc 100644 --- a/modules/file/tests/file_module_test.info +++ b/modules/file/tests/file_module_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = file_module_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/filter/filter.info b/modules/filter/filter.info index 067d223b9..960b0d53f 100644 --- a/modules/filter/filter.info +++ b/modules/filter/filter.info @@ -12,8 +12,8 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/filter/filter.install b/modules/filter/filter.install index c4cf99fd7..03e370d46 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -1,5 +1,5 @@ <?php -// $Id: filter.install,v 1.36 2010/04/22 05:44:41 webchick Exp $ +// $Id: filter.install,v 1.37 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -98,7 +98,7 @@ function filter_schema() { ); $schema['cache_filter'] = drupal_get_schema_unprocessed('system', 'cache'); - $schema['cache_filter']['description'] = 'Cache table for the Filter module to store already filtered pieces of text, identified by text format and md5 hash of the text.'; + $schema['cache_filter']['description'] = 'Cache table for the Filter module to store already filtered pieces of text, identified by text format and hash of the text.'; return $schema; } diff --git a/modules/filter/filter.js b/modules/filter/filter.js index 6e3758318..7c09fd46d 100644 --- a/modules/filter/filter.js +++ b/modules/filter/filter.js @@ -1,4 +1,4 @@ -// $Id: filter.js,v 1.1 2010/03/07 23:59:20 webchick Exp $ +// $Id: filter.js,v 1.2 2010/04/30 07:48:07 dries Exp $ (function ($) { /** @@ -12,7 +12,7 @@ Drupal.behaviors.filterGuidelines = { .bind('change', function () { $(this).parents('.filter-wrapper') .find('.filter-guidelines-item').hide() - .siblings('#filter-guidelines-' + this.value).show(); + .siblings('.filter-guidelines-' + this.value).show(); }) .change(); } diff --git a/modules/filter/filter.module b/modules/filter/filter.module index bcd8605ae..16e13fa70 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -1,5 +1,5 @@ <?php -// $Id: filter.module,v 1.328 2010/04/24 14:53:59 dries Exp $ +// $Id: filter.module,v 1.331 2010/05/13 07:53:02 dries Exp $ /** * @file @@ -681,7 +681,7 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) $cache = $cache && !empty($format->cache); $cache_id = ''; if ($cache) { - $cache_id = $format->format . ':' . $langcode . ':' . md5($text); + $cache_id = $format->format . ':' . $langcode . ':' . hash('sha256', $text); if ($cached = cache_get($cache_id, 'cache_filter')) { return $cached->data; } @@ -730,29 +730,12 @@ 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. * - * Since most modules expect the value of the new 'format' element *next* to the - * original element, filter_process_format() utilizes an #after_build to move - * the values of the children of the 'text_format' element so as to let the - * submitted form values appear as if they were located on the same level. - * For example, considering the input values: + * 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['input']['body']['value'] = 'foo'; - * $form_state['input']['body']['format'] = 'foo'; + * $form_state['values']['body']['value'] = 'foo'; + * $form_state['values']['body']['format'] = 'foo'; * @endcode - * The #after_build will process them into: - * @code - * $form_state['values']['body'] = 'foo'; - * $form_state['values']['format'] = 'foo'; - * @endcode - * - * If multiple text format-enabled elements are required on the same level of - * the form structure, modules can set custom #parents on the original element. - * Alternatively, the #after_build may be unset through a subsequent #process - * callback. If the default #after_build is not invoked and no custom processing - * occurs, then the submitted form values will appear like in the - * $form_state['input'] array above. - * - * @see filter_form_after_build() * * @param $element * The form element to process. Properties used: @@ -804,9 +787,6 @@ function filter_process_format($element) { $element['#attached']['js'][] = $path . '/filter.js'; $element['#attached']['css'][] = $path . '/filter.css'; - // Apply default #after_build behavior. - $element['#after_build'][] = 'filter_form_after_build'; - // Setup child container for the text format widget. $element['format'] = array( '#type' => 'fieldset', @@ -886,38 +866,6 @@ function filter_process_format($element) { return $element; } -/** - * After build callback to move #type 'text_format' values up in $form_state. - */ -function filter_form_after_build($element, &$form_state) { - // For text fields, the additional subkeys map 1:1 to field schema columns. - if (isset($element['#columns'])) { - return $element; - } - - $parents = $element['#parents']; - array_pop($parents); - - foreach (element_children($element) as $key) { - $current_parents = $parents; - switch ($key) { - case 'value': - form_set_value(array('#parents' => $element['#parents']), $element[$key]['#value'], $form_state); - break; - - case 'format': - $current_parents[] = $key; - form_set_value(array('#parents' => $current_parents), $element['format']['format']['#value'], $form_state); - break; - - default: - $current_parents[] = $key; - form_set_value(array('#parents' => $current_parents), $element[$key]['#value'], $form_state); - } - } - return $element; -} - /** * #pre_render callback for #type 'text_format' to hide field value from prying eyes. * @@ -1122,7 +1070,9 @@ function theme_filter_guidelines($variables) { $format = $variables['format']; $name = isset($format->name) ? '<label>' . $format->name . ':</label>' : ''; - return '<div id="filter-guidelines-' . $format->format . '" class="filter-guidelines-item">' . $name . theme('filter_tips', array('tips' => _filter_tips($format->format, FALSE))) . '</div>'; + $attributes['class'][] = 'filter-guidelines-item'; + $attributes['class'][] = 'filter-guidelines-' . $format->format; + return '<div' . drupal_attributes($attributes) . '>' . $name . theme('filter_tips', array('tips' => _filter_tips($format->format, FALSE))) . '</div>'; } /** diff --git a/modules/forum/forum-topic-list.tpl.php b/modules/forum/forum-topic-list.tpl.php index 9c8ce3e41..89566c468 100644 --- a/modules/forum/forum-topic-list.tpl.php +++ b/modules/forum/forum-topic-list.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: forum-topic-list.tpl.php,v 1.9 2010/03/26 17:14:45 dries Exp $ +// $Id: forum-topic-list.tpl.php,v 1.10 2010/04/28 20:25:21 dries Exp $ /** * @file @@ -7,7 +7,7 @@ * * Available variables: * - $header: The table header. This is pre-generated with click-sorting - * information. If you need to change this, see + * information. If you need to change this, see * template_preprocess_forum_topic_list(). * - $pager: The pager to display beneath the table. * - $topics: An array of topics to be displayed. diff --git a/modules/forum/forum.css b/modules/forum/forum.css index 530b6a39f..19d75ea58 100644 --- a/modules/forum/forum.css +++ b/modules/forum/forum.css @@ -1,10 +1,15 @@ -/* $Id: forum.css,v 1.7 2009/08/22 15:26:04 dries Exp $ */ +/* $Id: forum.css,v 1.8 2010/04/28 20:08:38 dries Exp $ */ #forum .description { font-size: 0.9em; margin: 0.5em; } -#forum td.created, #forum td.posts, #forum td.topics, #forum td.last-reply, #forum td.replies, #forum td.pager { +#forum td.created, +#forum td.posts, +#forum td.topics, +#forum td.last-reply, +#forum td.replies, +#forum td.pager { white-space: nowrap; } #forum tr td.forum { diff --git a/modules/forum/forum.info b/modules/forum/forum.info index 7b7a88c57..177f519eb 100644 --- a/modules/forum/forum.info +++ b/modules/forum/forum.info @@ -13,8 +13,8 @@ files[] = forum.install files[] = forum.test configure = admin/structure/forum -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/forum/forum.install b/modules/forum/forum.install index 49aa336bb..2931135e3 100644 --- a/modules/forum/forum.install +++ b/modules/forum/forum.install @@ -1,5 +1,5 @@ <?php -// $Id: forum.install,v 1.45 2010/04/26 14:32:27 dries Exp $ +// $Id: forum.install,v 1.46 2010/05/05 06:55:25 webchick Exp $ /** * @file @@ -80,6 +80,10 @@ function forum_enable() { $term = (object) $edit; taxonomy_term_save($term); } + // Ensure the forum node type is available. + node_types_rebuild(); + $types = node_type_get_types(); + node_add_body_field($types['forum']); } /** diff --git a/modules/forum/forum.module b/modules/forum/forum.module index a9088ed08..d2c72b796 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -1,5 +1,5 @@ <?php -// $Id: forum.module,v 1.564 2010/04/26 14:32:27 dries Exp $ +// $Id: forum.module,v 1.565 2010/04/28 05:54:55 webchick Exp $ /** * @file @@ -94,6 +94,13 @@ function forum_menu() { 'access arguments' => array('access content'), 'file' => 'forum.pages.inc', ); + $items['forum/%forum_forum'] = array( + 'title' => 'Forums', + 'page callback' => 'forum_page', + 'page arguments' => array(1), + 'access arguments' => array('access content'), + 'file' => 'forum.pages.inc', + ); $items['admin/structure/forum'] = array( 'title' => 'Forums', 'description' => 'Control forum hierarchy settings.', @@ -163,9 +170,8 @@ function forum_menu_local_tasks_alter(&$data, $router_item, $root_path) { // Add action link to 'node/add/forum' on 'forum' page. if ($root_path == 'forum') { $tid = (isset($router_item['page_arguments'][0]) ? $router_item['page_arguments'][0] : 0); - $forums = forum_get_forums($tid); - $parents = taxonomy_get_parents_all($tid); - if ($forums || $parents) { + $forum_term = forum_forum_load($tid); + if ($forum_term) { $vid = variable_get('forum_nav_vocabulary', 0); $vocabulary = taxonomy_vocabulary_load($vid); @@ -178,7 +184,7 @@ function forum_menu_local_tasks_alter(&$data, $router_item, $root_path) { '#theme' => 'menu_local_action', '#link' => array( 'title' => t('Add new @node_type', array('@node_type' => node_type_get_name($type))), - 'href' => 'node/add/' . str_replace('_', '-', $type) . '/' . $tid, + 'href' => 'node/add/' . str_replace('_', '-', $type) . '/' . $forum_term->tid, ), ); } @@ -723,27 +729,54 @@ function forum_form($node, $form_state) { } /** - * Returns a list of all forums for a given taxonomy id - * - * Forum objects contain the following fields - * -num_topics Number of topics in the forum - * -num_posts Total number of posts in all topics - * -last_post Most recent post for the forum + * Returns a tree of all forums for a given taxonomy term ID. * * @param $tid - * Taxonomy ID of the vocabulary that holds the forum list. + * (optional) Taxonomy ID of the forum, if not givin all forums will be returned. * @return - * Array of object containing the forum information. + * A tree of taxonomy objects, with the following additional properties: + * - 'num_topics': Number of topics in the forum + * - 'num_posts': Total number of posts in all topics + * - 'last_post': Most recent post for the forum + * - 'forums': An array of child forums */ -function forum_get_forums($tid = 0) { +function forum_forum_load($tid = NULL) { $cache = &drupal_static(__FUNCTION__, array()); + // Return a cached forum tree if available. + if (!isset($tid)) { + $tid = 0; + } if (isset($cache[$tid])) { return $cache[$tid]; } - $forums = array(); $vid = variable_get('forum_nav_vocabulary', 0); + + // Load and validate the parent term. + if ($tid) { + $forum_term = taxonomy_term_load($tid); + if (!$forum_term || ($forum_term->vid != $vid)) { + return $cache[$tid] = FALSE; + } + } + // If $tid is 0, create an empty object to hold the child terms. + else if ($tid === 0) { + $forum_term = (object) array( + 'tid' => 0, + ); + } + + // Determine if the requested term is a container. + if (!$forum_term->tid || in_array($forum_term->tid, variable_get('forum_containers', array()))) { + $forum_term->container = 1; + } + + // Load parent terms. + $forum_term->parents = taxonomy_get_parents_all($forum_term->tid); + + // Load the tree below. + $forums = array(); $_forums = taxonomy_get_tree($vid, $tid); if (count($_forums)) { @@ -762,10 +795,12 @@ function forum_get_forums($tid = 0) { } foreach ($_forums as $forum) { + // Determine if the child term is a container. if (in_array($forum->tid, variable_get('forum_containers', array()))) { $forum->container = 1; } + // Merge in the topic and post counters. if (!empty($counts[$forum->tid])) { $forum->num_topics = $counts[$forum->tid]->topic_count; $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count; @@ -775,6 +810,7 @@ function forum_get_forums($tid = 0) { $forum->num_posts = 0; } + // Query "Last Post" information for this forum. $query = db_select('node', 'n'); $query->join('users', 'u1', 'n.uid = u1.uid'); $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid)); @@ -791,6 +827,7 @@ function forum_get_forums($tid = 0) { ->execute() ->fetchObject(); + // Merge in the "Last Post" information. $last_post = new stdClass(); if (!empty($topic->last_comment_timestamp)) { $last_post->created = $topic->last_comment_timestamp; @@ -802,9 +839,10 @@ function forum_get_forums($tid = 0) { $forums[$forum->tid] = $forum; } - $cache[$tid] = $forums; - - return $forums; + // Cache the result, and return the tree. + $forum_term->forums = $forums; + $cache[$tid] = $forum_term; + return $forum_term; } /** diff --git a/modules/forum/forum.pages.inc b/modules/forum/forum.pages.inc index 2dc002f97..6acdb23ae 100644 --- a/modules/forum/forum.pages.inc +++ b/modules/forum/forum.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: forum.pages.inc,v 1.3 2009/10/09 00:59:59 dries Exp $ +// $Id: forum.pages.inc,v 1.4 2010/04/28 05:54:55 webchick Exp $ /** * @file @@ -9,16 +9,21 @@ /** * Menu callback; prints a forum listing. */ -function forum_page($tid = 0) { - $topics = ''; +function forum_page($forum_term = NULL) { + if (!isset($forum_term)) { + // On the main page, display all the top-level forums. + $forum_term = forum_forum_load(0); + } + $forum_per_page = variable_get('forum_per_page', 25); $sortby = variable_get('forum_order', 1); - $forums = forum_get_forums($tid); - $parents = taxonomy_get_parents_all($tid); - if ($tid && !in_array($tid, variable_get('forum_containers', array()))) { - $topics = forum_get_topics($tid, $sortby, $forum_per_page); + if (empty($forum_term->container)) { + $topics = forum_get_topics($forum_term->tid, $sortby, $forum_per_page); + } + else { + $topics = ''; } - return theme('forums', array('forums' => $forums, 'topics' => $topics, 'parents' => $parents, 'tid' => $tid, 'sortby' => $sortby, 'forums_per_page' => $forum_per_page)); + return theme('forums', array('forums' => $forum_term->forums, 'topics' => $topics, 'parents' => $forum_term->parents, 'tid' => $forum_term->tid, 'sortby' => $sortby, 'forums_per_page' => $forum_per_page)); } diff --git a/modules/forum/forum.test b/modules/forum/forum.test index caaabb950..7860937ea 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -1,5 +1,5 @@ <?php -// $Id: forum.test,v 1.55 2010/04/26 14:32:27 dries Exp $ +// $Id: forum.test,v 1.56 2010/04/28 05:54:55 webchick Exp $ class ForumTestCase extends DrupalWebTestCase { protected $admin_user; @@ -268,7 +268,7 @@ class ForumTestCase extends DrupalWebTestCase { // Assert that the forum no longer exists. $this->drupalGet('forum/' . $tid); - $this->assertRaw(t('No forums defined'), 'The forum was not found'); + $this->assertResponse(404, 'The forum was not found'); } /** diff --git a/modules/help/help.info b/modules/help/help.info index c73eda75d..ce75a2da3 100644 --- a/modules/help/help.info +++ b/modules/help/help.info @@ -8,8 +8,8 @@ files[] = help.module files[] = help.admin.inc files[] = help.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/image/image.effects.inc b/modules/image/image.effects.inc index 94d92961e..401061f91 100644 --- a/modules/image/image.effects.inc +++ b/modules/image/image.effects.inc @@ -1,5 +1,5 @@ <?php -// $Id: image.effects.inc,v 1.4 2010/03/26 17:14:45 dries Exp $ +// $Id: image.effects.inc,v 1.5 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -232,7 +232,7 @@ function image_rotate_effect(&$image, $data) { } if (!empty($data['random'])) { - $degrees = abs((float)$data['degrees']); + $degrees = abs((float) $data['degrees']); $data['degrees'] = rand(-1 * $degrees, $degrees); } diff --git a/modules/image/image.field.inc b/modules/image/image.field.inc index 9c89707ca..43f5a2dbc 100644 --- a/modules/image/image.field.inc +++ b/modules/image/image.field.inc @@ -1,5 +1,5 @@ <?php -// $Id: image.field.inc,v 1.20 2010/04/13 15:23:03 dries Exp $ +// $Id: image.field.inc,v 1.21 2010/04/30 12:53:47 dries Exp $ /** * @file @@ -511,8 +511,11 @@ function theme_image_formatter($variables) { $image = array( 'path' => $item['uri'], 'alt' => $item['alt'], - 'title' => $item['title'], ); + // Do not output an empty 'title' attribute. + if (drupal_strlen($item['title']) > 0) { + $image['title'] = $item['title']; + } if ($variables['image_style']) { $image['style_name'] = $variables['image_style']; diff --git a/modules/image/image.info b/modules/image/image.info index 8e29c076d..be5083cd6 100644 --- a/modules/image/image.info +++ b/modules/image/image.info @@ -13,8 +13,8 @@ files[] = image.install files[] = image.test configure = admin/config/media/image-styles -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/image/image.module b/modules/image/image.module index c21b13730..e5bc1c620 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -1,5 +1,5 @@ <?php -// $Id: image.module,v 1.40 2010/04/23 07:54:44 webchick Exp $ +// $Id: image.module,v 1.42 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -178,7 +178,7 @@ function image_theme() { 'style_name' => NULL, 'path' => NULL, 'alt' => '', - 'title' => '', + 'title' => NULL, 'attributes' => array(), 'getsize' => TRUE, ), @@ -632,19 +632,19 @@ function image_style_generate() { $path = implode('/', $args); $path = $scheme . '://' . $path; - $path_md5 = md5($path); + $path_hash = drupal_hash_base64($path); $destination = image_style_path($style['name'], $path); // Check that it's a defined style and that access was granted by // image_style_url(). - if (!$style || !cache_get('access:' . $style_name . ':' . $path_md5, 'cache_image')) { + if (!$style || !cache_get('access:' . $style_name . ':' . $path_hash, 'cache_image')) { drupal_access_denied(); drupal_exit(); } // Don't start generating the image if the derivate already exists or if // generation is in progress in another thread. - $lock_name = 'image_style_generate:' . $style_name . ':' . $path_md5; + $lock_name = 'image_style_generate:' . $style_name . ':' . $path_hash; if (!file_exists($destination)) { $lock_acquired = lock_acquire($lock_name); if (!$lock_acquired) { @@ -783,7 +783,7 @@ function image_style_url($style_name, $path) { // Set a cache entry to grant access to this style/image path. This will be // checked by image_style_generate(). - cache_set('access:' . $style_name . ':' . md5($path), 1, 'cache_image', REQUEST_TIME + 600); + cache_set('access:' . $style_name . ':' . drupal_hash_base64($path), 1, 'cache_image', REQUEST_TIME + 600); $scheme = file_uri_scheme($path); $target = file_uri_target($path); diff --git a/modules/image/image.test b/modules/image/image.test index 340c65f8e..625ba2b60 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -1,5 +1,5 @@ <?php -// $Id: image.test,v 1.20 2010/04/22 21:43:59 webchick Exp $ +// $Id: image.test,v 1.21 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -190,7 +190,7 @@ class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase { // Fetch the URL that generates the file while another process appears to // be generating the same file (this is signaled using a lock). - $lock_name = 'image_style_generate:' . $this->style_name . ':' . md5($original_uri); + $lock_name = 'image_style_generate:' . $this->style_name . ':' . drupal_hash_base64($original_uri); $this->assertTrue(lock_acquire($lock_name), t('Lock was acquired.')); $this->drupalGet($expected_generate_url); $this->assertResponse(503, t('Service Unavailable response received.')); diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index 437f31b40..bd7b1b950 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: locale.admin.inc,v 1.12 2010/04/24 14:49:14 dries Exp $ +// $Id: locale.admin.inc,v 1.13 2010/05/11 10:49:37 dries Exp $ /** * @file @@ -337,13 +337,23 @@ function locale_languages_predefined_form_submit($form, &$form_state) { } $form_state['redirect'] = 'admin/config/regional/language'; - return; } /** * Validate the language editing form. Reused for custom language addition too. */ function locale_languages_edit_form_validate($form, &$form_state) { + // Ensure sane field values for langcode, name, and native. + if (!isset($form['langcode_view']) && preg_match('@[^a-zA-Z_-]@', $form_state['values']['langcode'])) { + form_set_error('langcode', t('%field may only contain characters a-z, underscores, or hyphens.', array('%field' => $form['langcode']['#title']))); + } + if ($form_state['values']['name'] != check_plain($form_state['values']['name'])) { + form_set_error('name', t('%field cannot contain any markup.', array('%field' => $form['name']['#title']))); + } + if ($form_state['values']['native'] != check_plain($form_state['values']['native'])) { + form_set_error('native', t('%field cannot contain any markup.', array('%field' => $form['native']['#title']))); + } + if (!empty($form_state['values']['domain']) && !empty($form_state['values']['prefix'])) { form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.')); } diff --git a/modules/locale/locale.api.php b/modules/locale/locale.api.php index d54b4b4d0..a09535b01 100644 --- a/modules/locale/locale.api.php +++ b/modules/locale/locale.api.php @@ -1,5 +1,5 @@ <?php -// $Id: locale.api.php,v 1.10 2010/04/07 05:15:51 dries Exp $ +// $Id: locale.api.php,v 1.11 2010/05/12 08:26:14 dries Exp $ /** * @file @@ -151,7 +151,7 @@ function hook_language_negotiation_info() { 'types' => array('custom_language_type'), 'name' => t('Custom language provider'), 'description' => t('This is a custom language provider.'), - 'cache' => CACHE_DISABLED, + 'cache' => 0, ), ); } diff --git a/modules/locale/locale.css b/modules/locale/locale.css index be5746052..ea5676a0b 100644 --- a/modules/locale/locale.css +++ b/modules/locale/locale.css @@ -1,11 +1,13 @@ -/* $Id: locale.css,v 1.6 2010/01/03 21:01:04 webchick Exp $ */ +/* $Id: locale.css,v 1.7 2010/04/28 20:08:38 dries Exp $ */ .locale-untranslated { font-style: normal; text-decoration: line-through; } -.form-item-language, .form-item-translation, .form-item-group { +.form-item-language, +.form-item-translation, +.form-item-group { float: left; /* LTR */ padding-right: .8em; /* LTR */ margin: 0.1em; diff --git a/modules/locale/locale.datepicker.js b/modules/locale/locale.datepicker.js new file mode 100644 index 000000000..681a2cc0e --- /dev/null +++ b/modules/locale/locale.datepicker.js @@ -0,0 +1,70 @@ +// $Id: locale.datepicker.js,v 1.1 2010/04/29 05:15:43 webchick Exp $ +(function ($) { + +$.datepicker.regional['drupal-locale'] = { + closeText: Drupal.t('Done'), + prevText: Drupal.t('Prev'), + nextText: Drupal.t('Next'), + currentText: Drupal.t('Today'), + monthNames: [ + Drupal.t('January'), + Drupal.t('February'), + Drupal.t('March'), + Drupal.t('April'), + Drupal.t('May'), + Drupal.t('June'), + Drupal.t('July'), + Drupal.t('August'), + Drupal.t('September'), + Drupal.t('October'), + Drupal.t('November'), + Drupal.t('December') + ], + monthNamesShort: [ + Drupal.t('Jan'), + Drupal.t('Feb'), + Drupal.t('Mar'), + Drupal.t('Apr'), + Drupal.t('May'), + Drupal.t('Jun'), + Drupal.t('Jul'), + Drupal.t('Aug'), + Drupal.t('Sep'), + Drupal.t('Oct'), + Drupal.t('Nov'), + Drupal.t('Dec') + ], + dayNames: [ + Drupal.t('Sunday'), + Drupal.t('Monday') + Drupal.t('Tuesday') + Drupal.t('Wednesday') + Drupal.t('Thursday') + Drupal.t('Friday') + Drupal.t('Saturday') + ], + dayNamesShort: [ + Drupal.t('Sun') + Drupal.t('Mon') + Drupal.t('Tue') + Drupal.t('Wed') + Drupal.t('Thu') + Drupal.t('Fri') + Drupal.t('Sat') + ], + dayNamesMin: [ + Drupal.t('Su') + Drupal.t('Mo') + Drupal.t('Tu') + Drupal.t('We') + Drupal.t('Th') + Drupal.t('Fr') + Drupal.t('Sa') + ], + dateFormat: Drupal.t('mm/dd/yy'), + firstDay: Drupal.settings.jqueryuidatepicker.firstDay, + isRTL: Drupal.settings.jqueryuidatepicker.rtl +}; +$.datepicker.setDefaults($.datepicker.regional['drupal-locale']); + +})(jQuery); diff --git a/modules/locale/locale.info b/modules/locale/locale.info index 8b2b1a9d7..6558742d1 100644 --- a/modules/locale/locale.info +++ b/modules/locale/locale.info @@ -10,8 +10,8 @@ files[] = locale.admin.inc files[] = locale.test configure = admin/config/regional/language -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/locale/locale.install b/modules/locale/locale.install index 93fb79349..73d35370a 100644 --- a/modules/locale/locale.install +++ b/modules/locale/locale.install @@ -1,5 +1,5 @@ <?php -// $Id: locale.install,v 1.57 2010/03/25 11:46:21 dries Exp $ +// $Id: locale.install,v 1.58 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -81,6 +81,13 @@ function locale_update_7001() { return array(); } +/** + * Allow longer javascript file names. + */ +function locale_update_7002() { + db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); +} + /** * @} End of "defgroup updates-6.x-to-7.x" */ @@ -207,7 +214,7 @@ function locale_schema() { ), 'javascript' => array( 'type' => 'varchar', - 'length' => 32, + 'length' => 64, 'not null' => TRUE, 'default' => '', 'description' => 'Location of JavaScript translation file.', diff --git a/modules/locale/locale.module b/modules/locale/locale.module index b3cfac9b0..e396cefbe 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -1,5 +1,5 @@ <?php -// $Id: locale.module,v 1.291 2010/04/08 18:16:43 dries Exp $ +// $Id: locale.module,v 1.293 2010/05/12 08:26:14 dries Exp $ /** * @file @@ -589,7 +589,7 @@ function locale_language_negotiation_info() { 'callbacks' => array('language' => 'locale_language_from_browser'), 'file' => $file, 'weight' => -2, - 'cache' => CACHE_DISABLED, + 'cache' => 0, 'name' => t('Browser'), 'description' => t("Determine the language from the browser's language settings."), ); @@ -892,6 +892,28 @@ function locale_css_alter(&$css) { } } + /** + * Implement hook_library_alter(). + * + * Provides the language support for the jQuery UI Date Picker. + */ +function locale_library_alter(&$libraries, $module) { + global $language; + if ($module == 'system' && isset($libraries['system']['ui.datepicker'])) { + $datepicker = drupal_get_path('module', 'locale') . '/locale.datepicker.js'; + $libraries['system']['ui.datepicker']['js'][$datepicker] = array('weight' => JS_THEME); + $libraries['system']['ui.datepicker']['js'][] = array( + 'data' => array( + 'jqueryuidatepicker' => array( + 'rtl' => $language->direction == LANGUAGE_RTL, + 'firstDay' => variable_get('date_first_day', 0), + ), + ), + 'type' => 'setting', + ); + } +} + // --------------------------------------------------------------------------------- // Language switcher block diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 1f31b7f25..5ef82f811 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1,5 +1,5 @@ <?php -// $Id: locale.test,v 1.69 2010/03/31 20:05:06 dries Exp $ +// $Id: locale.test,v 1.70 2010/04/28 05:12:43 webchick Exp $ /** * @file @@ -957,6 +957,35 @@ EOF; } +/** + * Tests for the st() function. + */ +class LocaleInstallTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'String translation using st()', + 'description' => 'Tests that st() works like t().', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // st() lives in install.inc, so ensure that it is loaded for all tests. + require_once DRUPAL_ROOT . '/includes/install.inc'; + } + + /** + * Verify that function signatures of t() and st() are equal. + */ + function testFunctionSignatures() { + $reflector_t = new ReflectionFunction('t'); + $reflector_st = new ReflectionFunction('st'); + $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), t('Function signatures of t() and st() are equal.')); + } +} + /** * Locale uninstall with English UI functional test. */ @@ -1106,7 +1135,7 @@ class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest /** * Functional tests for the language switching feature. */ -class LanguageSwitchingFunctionalTest extends DrupalWebTestCase { +class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { public static function getInfo() { return array( @@ -1280,7 +1309,7 @@ class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase { /** * Functional test for language handling during user creation. */ -class LocalUserCreationTest extends DrupalWebTestCase { +class LocaleUserCreationTest extends DrupalWebTestCase { public static function getInfo() { return array( @@ -1606,7 +1635,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { * http://example.cn/admin/config * UI language in Chinese */ -class UILanguageNegotiationTest extends DrupalWebTestCase { +class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'UI language negotiation', @@ -1904,7 +1933,7 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { /** * Functional tests for localizing date formats. */ -class LocalizeDateFormatsFunctionalTest extends DrupalWebTestCase { +class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase { public static function getInfo() { return array( @@ -1969,3 +1998,4 @@ class LocalizeDateFormatsFunctionalTest extends DrupalWebTestCase { $this->assertText($french_date, t('French date format appears')); } } + diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index 395f532f2..4cbb417e0 100644 --- a/modules/locale/tests/locale_test.info +++ b/modules/locale/tests/locale_test.info @@ -7,8 +7,8 @@ files[] = locale_test.module version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 76bbc80f3..5e391ae65 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: menu.admin.inc,v 1.78 2010/04/24 14:49:14 dries Exp $ +// $Id: menu.admin.inc,v 1.79 2010/05/04 20:31:53 dries Exp $ /** * @file @@ -181,6 +181,7 @@ function menu_overview_form_submit($form, &$form_state) { $item['customized'] = 1; menu_link_save($item); } + drupal_set_message(t('Your configuration has been saved.')); } /** @@ -403,6 +404,9 @@ function menu_edit_item_submit($form, &$form_state) { if (!menu_link_save($item)) { drupal_set_message(t('There was an error saving the menu link.'), 'error'); } + else { + drupal_set_message(t('Your configuration has been saved.')); + } $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name']; } @@ -605,6 +609,7 @@ function menu_edit_menu_submit($form, &$form_state) { menu_link_save($link); } } + drupal_set_message(t('Your configuration has been saved.')); $form_state['redirect'] = $path . $menu['menu_name']; } diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 5f629f0e8..12ccabd61 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -1,5 +1,5 @@ <?php -// $Id: menu.api.php,v 1.24 2010/01/30 07:59:25 dries Exp $ +// $Id: menu.api.php,v 1.25 2010/04/28 05:08:24 webchick Exp $ /** * @file @@ -11,393 +11,6 @@ * @{ */ -/** - * Define menu items and page callbacks. - * - * This hook enables modules to register paths in order to define how URL - * requests are handled. Paths may be registered for URL handling only, or they - * can register a link to be placed in a menu (usually the Navigation menu). A - * path and its associated information is commonly called a "menu router item". - * This hook is rarely called (for example, when modules are enabled), and - * its results are cached in the database. - * - * hook_menu() implementations return an associative array whose keys define - * paths and whose values are an associative array of properties for each - * path. (The complete list of properties is in the return value section below.) - * - * The definition for each path may include a page callback function, which is - * invoked when the registered path is requested. If there is no other - * registered path that fits the requested path better, any further path - * components are passed to the callback function. For example, your module - * could register path 'abc/def': - * @code - * function mymodule_menu() { - * $items['abc/def'] = array( - * 'page callback' => 'mymodule_abc_view', - * ); - * } - * - * function mymodule_abc_view($ghi = 0, $jkl = '') { - * // ... - * } - * @endcode - * When path 'abc/def' is requested, no further path components are in the - * request, and no additional arguments are passed to the callback function (so - * $ghi and $jkl would take the default values as defined in the function - * signature). When 'abc/def/123/foo' is requested, $ghi will be '123' and - * $jkl will be 'foo'. Note that this automatic passing of optional path - * arguments applies only to page and theme callback functions. - * - * In addition to optional path arguments, the page callback and other callback - * functions may specify argument lists as arrays. These argument lists may - * contain both fixed/hard-coded argument values and integers that correspond - * to path components. When integers are used and the callback function is - * called, the corresponding path components will be substituted for the - * integers. That is, the integer 0 in an argument list will be replaced with - * the first path component, integer 1 with the second, and so on (path - * components are numbered starting from zero). This substitution feature allows - * you to re-use a callback function for several different paths. For example: - * @code - * function mymodule_menu() { - * $items['abc/def'] = array( - * 'page callback' => 'mymodule_abc_view', - * 'page arguments' => array(1, 'foo'), - * ); - * } - * @endcode - * When path 'abc/def' is requested, the page callback function will get 'def' - * as the first argument and (always) 'foo' as the second argument. - * - * Note that if a page or theme callback function has an argument list array, - * these arguments will be passed first to the function, followed by any - * any arguments generated by optional path arguments as described above. - * - * Special care should be taken for the page callback drupal_get_form(), because - * your specific form callback function will always receive $form and - * &$form_state as the first function arguments: - * @code - * function mymodule_abc_form($form, &$form_state) { - * // ... - * return $form; - * } - * @endcode - * See @link form_api Form API documentation @endlink for details. - * - * Wildcards within paths also work with integer substitution. For example, - * your module could register path 'my-module/%/edit': - * @code - * $items['my-module/%/edit'] = array( - * 'page callback' => 'mymodule_abc_edit', - * 'page arguments' => array(1), - * ); - * @endcode - * When path 'my-module/foo/edit' is requested, integer 1 will be replaced - * with 'foo' and passed to the callback function. - * - * Registered paths may also contain special "auto-loader" wildcard components - * in the form of '%mymodule_abc', where the '%' part means that this path - * component is a wildcard, and the 'mymodule_abc' part defines the prefix for a - * load function, which here would be named mymodule_abc_load(). When a matching - * path is requested, your load function will receive as its first argument the - * path component in the position of the wildcard; load functions may also be - * passed additional arguments (see "load arguments" in the return value - * section below). For example, your module could register path - * 'my-module/%mymodule_abc/edit': - * @code - * $items['my-module/%mymodule_abc/edit'] = array( - * 'page callback' => 'mymodule_abc_edit', - * 'page arguments' => array(1), - * ); - * @endcode - * When path 'my-module/123/edit' is requested, your load function - * mymodule_abc_load() will be invoked with the argument '123', and should - * load and return an "abc" object with internal id 123: - * @code - * function mymodule_abc_load($abc_id) { - * return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject(); - * } - * @endcode - * This 'abc' object will then be passed into the page callback function - * mymodule_abc_edit() to replace the integer 1 in the page arguments. - * - * You can also make groups of menu items to be rendered (by default) as tabs - * on a page. To do that, first create one menu item of type MENU_NORMAL_ITEM, - * with your chosen path, such as 'foo'. Then duplicate that menu item, using a - * subdirectory path, such as 'foo/tab1', and changing the type to - * MENU_DEFAULT_LOCAL_TASK to make it the default tab for the group. Then add - * the additional tab items, with paths such as "foo/tab2" etc., with type - * MENU_LOCAL_TASK. Example: - * @code - * // Make "Foo settings" appear on the admin Config page - * $items['admin/config/foo'] = array( - * 'title' => 'Foo settings', - * 'type' => MENU_NORMAL_ITEM, - * // page callback, etc. need to be added here - * ); - * // Make "Global settings" the main tab on the "Foo settings" page - * $items['admin/config/foo/global'] = array( - * 'title' => 'Global settings', - * 'type' => MENU_DEFAULT_LOCAL_TASK, - * // access callback, page callback, and theme callback will be inherited - * // from 'admin/config/foo', if not specified here to override - * ); - * // Make an additional tab called "Node settings" on "Foo settings" - * $items['admin/config/foo/node'] = array( - * 'title' => 'Node settings', - * 'type' => MENU_LOCAL_TASK, - * // access callback, page callback, and theme callback will be inherited - * // from 'admin/config/foo', if not specified here to override - * ); - * @endcode - * - * @return - * An array of menu items. Each menu item has a key corresponding to the - * Drupal path being registered. The corresponding array value is an - * associative array that may contain the following key-value pairs: - * - "title": Required. The untranslated title of the menu item. - * - "title callback": Function to generate the title; defaults to t(). - * If you require only the raw string to be output, set this to FALSE. - * - "title arguments": Arguments to send to t() or your custom callback, - * with path component substitution as described above. - * - "description": The untranslated description of the menu item. - * - "page callback": The function to call to display a web page when the user - * visits the path. If omitted, the parent menu item's callback will be used - * instead. - * - "page arguments": An array of arguments to pass to the page callback - * function, with path component substitution as described above. - * - "delivery callback": The function to call to package the result of the - * page callback function and send it to the browser. Defaults to - * drupal_deliver_html_page() unless a value is inherited from a parent menu - * item. - * - "access callback": A function returning a boolean value that determines - * whether the user has access rights to this menu item. Defaults to - * user_access() unless a value is inherited from a parent menu item. - * - "access arguments": An array of arguments to pass to the access callback - * function, with path component substitution as described above. - * - "theme callback": Optional. A function returning the machine-readable - * name of the default theme that will be used to render the page. If this - * function is provided, it is expected to return a currently-active theme - * on the site (otherwise, the main site theme will be used instead). If no - * function is provided, the main site theme will also be used, unless a - * value is inherited from a parent menu item. In all cases, the results of - * this function can be dynamically overridden for a particular page - * request by modules which implement hook_custom_theme(). - * - "theme arguments": An array of arguments to pass to the theme callback - * function, with path component substitution as described above. - * - "file": A file that will be included before the page callback is called; - * this allows page callback functions to be in separate files. The file - * should be relative to the implementing module's directory unless - * otherwise specified by the "file path" option. Does not apply to other - * callbacks (only page callback). - * - "file path": The path to the directory containing the file specified in - * "file". This defaults to the path to the module implementing the hook. - * - "load arguments": An array of arguments to be passed to each of the - * wildcard object loaders in the path, after the path argument itself. - * For example, if a module registers path node/%node/revisions/%/view - * with load arguments set to array(3), the '%node' in the path indicates - * that the loader function node_load() will be called with the second - * path component as the first argument. The 3 in the load arguments - * indicates that the fourth path component will also be passed to - * node_load() (numbering of path components starts at zero). So, if path - * node/12/revisions/29/view is requested, node_load(12, 29) will be called. - * There are also two "magic" values that can be used in load arguments. - * "%index" indicates the index of the wildcard path component. "%map" - * indicates the path components as an array. For example, if a module - * registers for several paths of the form 'user/%user_category/edit/*', all - * of them can use the same load function user_category_load(), by setting - * the load arguments to array('%map', '%index'). For instance, if the user - * is editing category 'foo' by requesting path 'user/32/edit/foo', the load - * function user_category_load() will be called with 32 as its first - * argument, the array ('user', 32, 'edit', 'foo') as the map argument, - * and 1 as the index argument (because %user_category is the second path - * component and numbering starts at zero). user_category_load() can then - * use these values to extract the information that 'foo' is the category - * being requested. - * - "weight": An integer that determines the relative position of items in - * the menu; higher-weighted items sink. Defaults to 0. Menu items with the - * 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. - * - "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 - * in page region containers outside of the parent menu item's primary page - * context should be registered using one of the following contexts: - * - MENU_CONTEXT_PAGE: (default) The tab is displayed as local task for the - * page context only. - * - MENU_CONTEXT_INLINE: The tab is displayed as contextual link outside of - * the primary page context only. - * Contexts can be combined. For example, to display a tab both on a page - * and inline, a menu router item may specify: - * @code - * 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, - * @endcode - * - "tab_parent": For local task menu items, the path of the task's parent - * item; defaults to the same path without the last component (e.g., the - * default parent for 'admin/people/create' is 'admin/people'). - * - "tab_root": For local task menu items, the path of the closest non-tab - * item; same default as "tab_parent". - * - "block callback": Name of a function used to render the block on the - * system administration page for this item (called with no arguments). - * If not provided, system_admin_menu_block() is used to generate it. - * - "position": Position of the block ('left' or 'right') on the system - * administration page for this item. - * - "type": A bitmask of flags describing properties of the menu item. - * Many shortcut bitmasks are provided as constants in menu.inc: - * - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be - * moved/hidden by the administrator. - * - MENU_CALLBACK: Callbacks simply register a path so that the correct - * information is generated when the path is accessed. - * - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the - * administrator may enable. - * - MENU_LOCAL_ACTION: Local actions are menu items that describe actions - * on the parent item such as adding a new user or block, and are - * rendered in the action-links list in your theme. - * - MENU_LOCAL_TASK: Local tasks are menu items that describe different - * displays of data, and are generally rendered as tabs. - * - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one - * "default" task, which should display the same page as the parent item. - * If the "type" element is omitted, MENU_NORMAL_ITEM is assumed. - * - * For a detailed usage example, see page_example.module. - * For comprehensive documentation on the menu system, see - * http://drupal.org/node/102338. - */ -function hook_menu() { - $items['blog'] = array( - 'title' => 'blogs', - 'page callback' => 'blog_page', - 'access arguments' => array('access content'), - 'type' => MENU_SUGGESTED_ITEM, - ); - $items['blog/feed'] = array( - 'title' => 'RSS feed', - 'page callback' => 'blog_feed', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); - - return $items; -} - -/** - * Alter the data being saved to the {menu_router} table after hook_menu is invoked. - * - * This hook is invoked by menu_router_build(). The menu definitions are passed - * in by reference. Each element of the $items array is one item returned - * by a module from hook_menu. Additional items may be added, or existing items - * altered. - * - * @param $items - * Associative array of menu router definitions returned from hook_menu(). - */ -function hook_menu_alter(&$items) { - // Example - disable the page at node/add - $items['node/add']['access callback'] = FALSE; -} - -/** - * Alter the data being saved to the {menu_links} table by menu_link_save(). - * - * @param $item - * Associative array defining a menu link as passed into menu_link_save(). - */ -function hook_menu_link_alter(&$item) { - // Example 1 - make all new admin links hidden (a.k.a disabled). - if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) { - $item['hidden'] = 1; - } - // Example 2 - flag a link to be altered by hook_translated_menu_link_alter() - if ($item['link_path'] == 'devel/cache/clear') { - $item['options']['alter'] = TRUE; - } -} - -/** - * Alter a menu link after it's translated, but before it's rendered. - * - * This hook may be used, for example, to add a page-specific query string. - * For performance reasons, only links that have $item['options']['alter'] == TRUE - * will be passed into this hook. The $item['options']['alter'] flag should - * generally be set using hook_menu_link_alter(). - * - * @param $item - * Associative array defining a menu link after _menu_link_translate() - * @param $map - * Associative array containing the menu $map (path parts and/or objects). - */ -function hook_translated_menu_link_alter(&$item, $map) { - if ($item['href'] == 'devel/cache/clear') { - $item['localized_options']['query'] = drupal_get_destination(); - } -} - -/** - * Inform modules that a menu link has been created. - * - * This hook is used to notify modules that menu items have been - * created. Contributed modules may use the information to perform - * actions based on the information entered into the menu system. - * - * @param $link - * Associative array defining a menu link as passed into menu_link_save(). - * - * @see hook_menu_link_update() - * @see hook_menu_link_delete() - */ -function hook_menu_link_insert($link) { - // In our sample case, we track menu items as editing sections - // of the site. These are stored in our table as 'disabled' items. - $record['mlid'] = $link['mlid']; - $record['menu_name'] = $link['menu_name']; - $record['status'] = 0; - drupal_write_record('menu_example', $record); -} - -/** - * Inform modules that a menu link has been updated. - * - * This hook is used to notify modules that menu items have been - * updated. Contributed modules may use the information to perform - * actions based on the information entered into the menu system. - * - * @param $link - * Associative array defining a menu link as passed into menu_link_save(). - * - * @see hook_menu_link_insert() - * @see hook_menu_link_delete() - */ -function hook_menu_link_update($link) { - // If the parent menu has changed, update our record. - $menu_name = db_result(db_query("SELECT mlid, menu_name, status FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid']))); - if ($menu_name != $link['menu_name']) { - db_update('menu_example') - ->fields(array('menu_name' => $link['menu_name'])) - ->condition('mlid', $link['mlid']) - ->execute(); - } -} - -/** - * Inform modules that a menu link has been deleted. - * - * This hook is used to notify modules that menu items have been - * deleted. Contributed modules may use the information to perform - * actions based on the information entered into the menu system. - * - * @param $link - * Associative array defining a menu link as passed into menu_link_save(). - * - * @see hook_menu_link_insert() - * @see hook_menu_link_update() - */ -function hook_menu_link_delete($link) { - // Delete the record from our table. - db_delete('menu_example') - ->condition('mlid', $link['mlid']) - ->execute(); -} - /** * Informs modules that a custom menu was created. * @@ -470,112 +83,6 @@ function hook_menu_delete($menu) { variable_set('my_module_menus', $my_menus); } -/** - * Alter tabs and actions displayed on the page before they are rendered. - * - * This hook is invoked by menu_local_tasks(). The system-determined tabs and - * actions are passed in by reference. Additional tabs or actions may be added, - * or existing items altered. - * - * Each tab or action is an associative array containing: - * - #theme: The theme function to use to render. - * - #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(). - * - #active: Whether the link should be marked as 'active'. - * - * @param $data - * An associative array containing: - * - actions: An associative array containing: - * - count: The amount of actions determined by the menu system, which can - * be ignored. - * - output: A list of of actions, each one being an associative array - * as described above. - * - tabs: An indexed array (list) of tab levels (up to 2 levels), each - * containing an associative array: - * - count: The amount of tabs determined by the menu system. This value - * does not need to be altered if there is more than one tab. - * - output: A list of of tabs, each one being an associative array as - * described above. - */ -function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) { - // Add an action linking to node/add to all pages. - $data['actions']['output'][] = array( - '#theme' => 'menu_local_task', - '#link' => array( - 'title' => t('Add new content'), - 'href' => 'node/add', - 'localized_options' => array( - 'attributes' => array( - 'title' => t('Add new content'), - ), - ), - ), - ); - - // Add a tab linking to node/add to all pages. - $data['tabs'][0]['output'][] = array( - '#theme' => 'menu_local_task', - '#link' => array( - 'title' => t('Example tab'), - 'href' => 'node/add', - 'localized_options' => array( - 'attributes' => array( - 'title' => t('Add new content'), - ), - ), - ), - // Define whether this link is active. This can be omitted for - // implementations that add links to pages outside of the current page - // context. - '#active' => ($router_item['path'] == $root_path), - ); -} - -/** - * Alter contextual links before they are rendered. - * - * This hook is invoked by menu_contextual_links(). The system-determined - * contextual links are passed in by reference. Additional links may be added - * or existing links can be altered. - * - * Each contextual link must at least contain: - * - title: The localized title of the link. - * - href: The system path to link to. - * - localized_options: An array of options to pass to url(). - * - * @param $links - * An associative array containing contextual links for the given $root_path, - * as described above. The array keys are used to build CSS class names for - * contextual links and must therefore be unique for each set of contextual - * links. - * @param $router_item - * The menu router item belonging to the $root_path being requested. - * @param $root_path - * The (parent) path that has been requested to build contextual links for. - * This is a normalized path, which means that an originally passed path of - * 'node/123' became 'node/%'. - * - * @see menu_contextual_links() - * @see hook_menu() - * @see system_preprocess() - */ -function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) { - // Add a link to all contextual links for nodes. - if ($root_path == 'node/%') { - $links['foo'] = array( - 'title' => t('Do fu'), - 'href' => 'foo/do', - 'localized_options' => array( - 'query' => array( - 'foo' => 'bar', - ), - ), - ); - } -} - /** * @} End of "addtogroup hooks". */ diff --git a/modules/menu/menu.info b/modules/menu/menu.info index a8d962634..46db58d34 100644 --- a/modules/menu/menu.info +++ b/modules/menu/menu.info @@ -10,8 +10,8 @@ files[] = menu.install files[] = menu.test configure = admin/structure/menu -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/menu/menu.test b/modules/menu/menu.test index 3c99e2667..36ba1ab08 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -1,5 +1,5 @@ <?php -// $Id: menu.test,v 1.34 2010/02/17 05:46:16 webchick Exp $ +// $Id: menu.test,v 1.36 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -100,7 +100,7 @@ class MenuTestCase extends DrupalWebTestCase { */ function addCustomMenuCRUD() { // Add a new custom menu. - $menu_name = substr(md5($this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); + $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); $title = $this->randomName(16); $menu = array( @@ -130,7 +130,7 @@ class MenuTestCase extends DrupalWebTestCase { // Try adding a menu using a menu_name that is too long. $this->drupalGet('admin/structure/menu/add'); - $menu_name = substr(md5($this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1); + $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1); $title = $this->randomName(16); $edit = array( 'menu_name' => $menu_name, @@ -143,7 +143,7 @@ class MenuTestCase extends DrupalWebTestCase { $this->assertText(format_plural(MENU_MAX_MENU_NAME_LENGTH_UI, "The menu name can't be longer than 1 character.", "The menu name can't be longer than @count characters."), t('Validation failed when menu name is too long.')); // Change the menu_name so it no longer exceeds the maximum length. - $menu_name = substr(md5($this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); + $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); $edit['menu_name'] = $menu_name; $this->drupalPost('admin/structure/menu/add', $edit, t('Save')); @@ -483,7 +483,7 @@ class MenuTestCase extends DrupalWebTestCase { // Load menu link. // Use api function so that link is translated for rendering. $item = menu_link_load($mlid); - $this->assertTrue((bool)$item, 'Standard menu link was loaded'); + $this->assertTrue((bool) $item, 'Standard menu link was loaded'); return $item; } diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 03d199dcf..72e058eb5 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -1,5 +1,5 @@ <?php -// $Id: content_types.inc,v 1.112 2010/04/24 14:49:14 dries Exp $ +// $Id: content_types.inc,v 1.114 2010/05/05 06:55:25 webchick Exp $ /** * @file @@ -94,7 +94,7 @@ function node_type_form($form, &$form_state, $type = NULL) { '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>Add new content</em> page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'), '#required' => TRUE, '#size' => 30, - '#field_suffix' => ' <small id="edit-name-suffix">' . ($type->locked ? t('Machine name: @name', array('@name' => $type->type)) : ' ') . '</small>', + '#field_suffix' => ' <small id="edit-name-suffix">' . ($type->locked ? t('Machine name: @name', array('@name' => $type->type)) : ' ') . '</small>', ); if (!$type->locked) { @@ -160,12 +160,6 @@ function node_type_form($form, &$form_state, $type = NULL) { $form['submission']['title_label']['#description'] = t('This content type does not have a title field.'); $form['submission']['title_label']['#required'] = FALSE; } - $form['submission']['body_label'] = array( - '#title' => t('Body field label'), - '#type' => 'textfield', - '#default_value' => isset($type->body_label) ? $type->body_label : '', - '#description' => t('To remove the body field, remove text and leave blank.'), - ); $form['submission']['node_preview'] = array( '#type' => 'radios', '#title' => t('Preview before submitting'), @@ -322,12 +316,9 @@ function node_type_form_submit($form, &$form_state) { $type->description = $form_state['values']['description']; $type->help = $form_state['values']['help']; $type->title_label = $form_state['values']['title_label']; - $type->body_label = $form_state['values']['body_label']; - // title_label is required in core; has_title will always be true, unless a // module alters the title field. $type->has_title = ($type->title_label != ''); - $type->has_body = ($type->body_label != ''); $type->base = !empty($form_state['values']['base']) ? $form_state['values']['base'] : 'node_content'; $type->custom = $form_state['values']['custom']; @@ -372,6 +363,7 @@ function node_type_form_submit($form, &$form_state) { node_types_rebuild(); menu_rebuild(); + node_add_body_field($type); $t_args = array('%name' => $type->name); if ($status == SAVED_UPDATED) { diff --git a/modules/node/content_types.js b/modules/node/content_types.js index 2fc099822..94fe1eb89 100644 --- a/modules/node/content_types.js +++ b/modules/node/content_types.js @@ -1,4 +1,4 @@ -// $Id: content_types.js,v 1.9 2010/04/09 12:24:53 dries Exp $ +// $Id: content_types.js,v 1.10 2010/05/05 06:55:25 webchick Exp $ (function ($) { Drupal.behaviors.contentTypes = { @@ -7,7 +7,6 @@ Drupal.behaviors.contentTypes = { $('fieldset#edit-submission', context).drupalSetSummary(function(context) { var vals = []; vals.push(Drupal.checkPlain($('#edit-title-label', context).val()) || Drupal.t('Requires a title')); - vals.push(Drupal.checkPlain($('#edit-body-label', context).val()) || Drupal.t('No body')); return vals.join(', '); }); $('fieldset#edit-workflow', context).drupalSetSummary(function(context) { diff --git a/modules/node/node.api.php b/modules/node/node.api.php index a2d79354d..81daab58b 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -1,5 +1,5 @@ <?php -// $Id: node.api.php,v 1.66 2010/03/26 17:14:45 dries Exp $ +// $Id: node.api.php,v 1.67 2010/05/05 06:55:25 webchick Exp $ /** * @file @@ -771,10 +771,6 @@ function hook_node_view_alter(&$build) { * field. Optional (defaults to TRUE). * - "title_label": the label for the title field of this content type. * Optional (defaults to 'Title'). - * - "has_body": boolean indicating whether or not this node type has a body - * field. Optional (defaults to TRUE). - * - "body_label": the label for the body field of this content type. Optional - * (defaults to 'Body'). * - "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). @@ -986,17 +982,6 @@ function hook_prepare($node) { function hook_form($node, $form_state) { $type = node_type_get_type($node); - $form['title'] = array( - '#type' => 'textfield', - '#title' => check_plain($type->title_label), - '#required' => TRUE, - ); - $form['body'] = array( - '#type' => 'textarea', - '#title' => check_plain($type->body_label), - '#rows' => 20, - '#required' => TRUE, - ); $form['field1'] = array( '#type' => 'textfield', '#title' => t('Custom field'), diff --git a/modules/node/node.css b/modules/node/node.css index 53fcb5f00..5a725f0a4 100644 --- a/modules/node/node.css +++ b/modules/node/node.css @@ -1,4 +1,4 @@ -/* $Id: node.css,v 1.16 2010/04/08 18:26:42 dries Exp $ */ +/* $Id: node.css,v 1.17 2010/04/28 20:08:39 dries Exp $ */ .node-unpublished { background-color: #fff4f4; @@ -7,7 +7,8 @@ background-color: #ffffea; } /* Override the default multiselect layout in system-behavior.css. */ -#node-admin-content dl.multiselect dd, dl.multiselect dd .form-item { +#node-admin-content dl.multiselect dd, +dl.multiselect dd .form-item { width: 20em; /* 6em label + 14em select */ } #node-admin-content dl.multiselect dd .form-item label { diff --git a/modules/node/node.info b/modules/node/node.info index 3496f1518..928da46b2 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -14,8 +14,8 @@ files[] = node.tokens.inc required = TRUE configure = admin/structure/types -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/node/node.install b/modules/node/node.install index e7e75df84..c6085f84c 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -1,5 +1,5 @@ <?php -// $Id: node.install,v 1.46 2010/03/28 11:16:29 dries Exp $ +// $Id: node.install,v 1.51 2010/05/14 04:37:53 webchick Exp $ /** * @file @@ -306,21 +306,6 @@ function node_schema() { 'default' => '', 'translatable' => TRUE, ), - 'has_body' => array( - 'description' => 'Boolean indicating whether this type has the body field attached.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'size' => 'tiny', - ), - 'body_label' => array( - 'description' => 'The label displayed for the body field on the edit form.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'translatable' => TRUE, - ), 'custom' => array( 'description' => 'A boolean indicating whether this type is defined by a module (FALSE) or by a user via Add content type (TRUE).', 'type' => 'int', @@ -496,30 +481,39 @@ function node_update_7005() { /** * Convert body and teaser from node properties to fields, and migrate status/comment/promote and sticky columns to the {node_revision} table. */ -function node_update_7006(&$context) { - $context['#finished'] = 0; +function node_update_7006(&$sandbox) { + $sandbox['#finished'] = 0; // Get node type info for every invocation. drupal_static_reset('_node_types_build'); $node_types = node_type_get_types(); - if (!isset($context['total'])) { + if (!isset($sandbox['total'])) { // Initial invocation. - // Re-save node types to create body field instances. - foreach ($node_types as $type => $info) { - if ($info->has_body) { - node_type_save($info); + // Get node type info, specifically the body field settings. + $result = db_select('node_type', 'node_type') + ->fields('node_type') + ->execute(); + + // Add body field instances for existing node types. + foreach ($result as $node_type) { + if ($node_type->has_body) { + node_add_body_field($node_type, $node_type->body_label); } + + $sandbox['node_types_info'][$node_type->type] = array( + 'has_body' => $node_type->has_body, + ); } // Initialize state for future calls. - $context['last'] = 0; - $context['count'] = 0; + $sandbox['last'] = 0; + $sandbox['count'] = 0; $query = db_select('node', 'n'); $query->join('node_revision', 'nr', 'n.vid = nr.vid'); - $context['total'] = $query->countQuery()->execute()->fetchField(); + $sandbox['total'] = $query->countQuery()->execute()->fetchField(); } else { // Subsequent invocations. @@ -529,15 +523,15 @@ function node_update_7006(&$context) { $body_field_id = $body_field['id']; $found = FALSE; - if ($context['total']) { + if ($sandbox['total']) { // Operate on every revision of every node (whee!), in batches. $batch_size = 200; $query = db_select('node_revision', 'nr'); $query->innerJoin('node', 'n', 'n.vid = nr.vid'); $query ->fields('nr', array('nid', 'vid', 'body', 'teaser', 'format')) - ->fields('n', array('type', 'status', 'comment', 'promote', 'sticky')) - ->condition('nr.vid', $context['last'], '>') + ->fields('n', array('type', 'status', 'comment', 'promote', 'sticky', 'language')) + ->condition('nr.vid', $sandbox['last'], '>') ->orderBy('nr.vid', 'ASC') ->range(0, $batch_size); $revisions = $query->execute(); @@ -552,26 +546,30 @@ function node_update_7006(&$context) { foreach ($revisions as $revision) { $found = TRUE; - if ($node_types[$revision->type]->has_body) { + if ($sandbox['node_types_info'][$revision->type]['has_body']) { $node = (object) array( 'nid' => $revision->nid, 'vid' => $revision->vid, 'type' => $revision->type, ); + // After node_update_7009() we will always have LANGUAGE_NONE as + // language neutral language code, but here we still have empty + // strings. + $langcode = empty($revision->language) ? LANGUAGE_NONE : $revision->language; if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) { - $node->body[LANGUAGE_NONE][0]['summary'] = $revision->teaser; + $node->body[$langcode][0]['summary'] = $revision->teaser; } // Do this after text_summary() above. $break = '<!--break-->'; if (substr($revision->body, 0, strlen($break)) == $break) { $revision->body = substr($revision->body, strlen($break)); } - $node->body[LANGUAGE_NONE][0]['value'] = $revision->body; + $node->body[$langcode][0]['value'] = $revision->body; // Explicitly store the current default text format if the revision // did not have its own text format. Similar conversions for other // core modules are performed in filter_update_7005(), but we do this // one here since we are already migrating the data. - $node->body[LANGUAGE_NONE][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1); + $node->body[$langcode][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1); // This is a core update and no contrib modules are enabled yet, so // we can assume default field storage for a faster update. field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array($body_field_id)); @@ -588,11 +586,11 @@ function node_update_7006(&$context) { ->condition('vid', $revision->vid) ->execute(); - $context['last'] = $revision->vid; - $context['count'] += 1; + $sandbox['last'] = $revision->vid; + $sandbox['count'] += 1; } - $context['#finished'] = min(0.99, $context['count'] / $context['total']); + $sandbox['#finished'] = min(0.99, $sandbox['count'] / $sandbox['total']); } if (!$found) { @@ -603,9 +601,12 @@ function node_update_7006(&$context) { db_drop_field('node_revision', 'teaser'); db_drop_field('node_revision', 'format'); + // Remove node_type properties related to the former 'body'. + db_drop_field('node_type', 'has_body'); + db_drop_field('node_type', 'body_label'); + // We're done. - $context['#finished'] = 1; - return t("!number node body and teaser properties migrated to the 'body' field.", array('!number' => $context['total'])); + $sandbox['#finished'] = 1; } } } diff --git a/modules/node/node.js b/modules/node/node.js index 76c982244..872729fae 100644 --- a/modules/node/node.js +++ b/modules/node/node.js @@ -1,4 +1,4 @@ -// $Id: node.js,v 1.5 2010/04/09 12:24:53 dries Exp $ +// $Id: node.js,v 1.6 2010/05/04 16:03:34 dries Exp $ (function ($) { @@ -11,7 +11,8 @@ Drupal.behaviors.nodeFieldsetSummaries = { }); $('fieldset#edit-author', context).drupalSetSummary(function (context) { - var name = $('#edit-name').val(), date = $('#edit-date').val(); + var name = $('#edit-name').val() || Drupal.settings.anonymous, + date = $('#edit-date').val(); return date ? Drupal.t('By @name on @date', { '@name': name, '@date': date }) : Drupal.t('By @name', { '@name': name }); diff --git a/modules/node/node.module b/modules/node/node.module index dd759bb6d..04a438110 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1,5 +1,5 @@ <?php -// $Id: node.module,v 1.1264 2010/04/22 09:12:35 webchick Exp $ +// $Id: node.module,v 1.1273 2010/05/17 07:43:36 dries Exp $ /** * @file @@ -445,7 +445,7 @@ function node_type_get_name($node) { * * All new module-defined node types are saved to the database via a call to * 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 + * node_type_delete(). See _node_types_build() for an explanation of the new * and obsolete types. */ function node_types_rebuild() { @@ -496,8 +496,6 @@ function node_type_save($info) { 'base' => (string) $type->base, 'has_title' => (int) $type->has_title, 'title_label' => (string) $type->title_label, - 'has_body' => (int) $type->has_body, - 'body_label' => (string) $type->body_label, 'description' => (string) $type->description, 'help' => (string) $type->help, 'custom' => (int) $type->custom, @@ -514,7 +512,6 @@ function node_type_save($info) { if (!empty($type->old_type) && $type->old_type != $type->type) { field_attach_rename_bundle('node', $type->old_type, $type->type); } - node_configure_fields($type); module_invoke_all('node_type_update', $type); $status = SAVED_UPDATED; } @@ -525,7 +522,7 @@ function node_type_save($info) { ->execute(); field_attach_create_bundle('node', $type->type); - node_configure_fields($type); + module_invoke_all('node_type_insert', $type); $status = SAVED_NEW; } @@ -537,60 +534,48 @@ function node_type_save($info) { } /** - * Manage the field(s) for a node type. + * Add default body field to a node type. * - * Currently, the node module manages a single Field API field, - * 'body'. If $type->has_body is true, this function ensures the - * 'body' field exists and creates an instance of it for the bundle - * $type->type (e.g. 'page', 'story', ...). If $type->has_body is - * false, this function removes the instance (if it exists) for the - * 'body' field on $type->type. + * @param $type + * A node type object. + * @param $label + * The label for the body instance. */ -function node_configure_fields($type) { +function node_add_body_field($type, $label = 'Body') { // Add or remove the body field, as needed. $field = field_info_field('body'); $instance = field_info_instance('node', 'body', $type->type); - if ($type->has_body) { - if (empty($field)) { - $field = array( - 'field_name' => 'body', - 'type' => 'text_with_summary', - 'entity_types' => array('node'), - 'translatable' => TRUE, - ); - $field = field_create_field($field); - } - if (empty($instance)) { - $instance = array( - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => $type->type, - 'label' => $type->body_label, - 'widget_type' => 'text_textarea_with_summary', - 'settings' => array('display_summary' => TRUE), - - // Define default formatters for the teaser and full view. - 'display' => array( - 'full' => array( - 'label' => 'hidden', - 'type' => 'text_default', - ), - 'teaser' => array( - 'label' => 'hidden', - 'type' => 'text_summary_or_trimmed', - ), + if (empty($field)) { + $field = array( + 'field_name' => 'body', + 'type' => 'text_with_summary', + 'entity_types' => array('node'), + 'translatable' => TRUE, + ); + $field = field_create_field($field); + } + if (empty($instance)) { + $instance = array( + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => $type->type, + 'label' => $label, + 'widget_type' => 'text_textarea_with_summary', + 'settings' => array('display_summary' => TRUE), + + // Define default formatters for the teaser and full view. + 'display' => array( + 'full' => array( + 'label' => 'hidden', + 'type' => 'text_default', ), - ); - field_create_instance($instance); - } - else { - $instance['label'] = $type->body_label; - $instance['settings']['display_summary'] = TRUE; - field_update_instance($instance); - } - } - elseif (!empty($instance)) { - field_delete_instance($instance); + 'teaser' => array( + 'label' => 'hidden', + 'type' => 'text_summary_or_trimmed', + ), + ), + ); + field_create_instance($instance); } } @@ -626,6 +611,7 @@ function node_type_delete($type) { db_delete('node_type') ->condition('type', $type) ->execute(); + field_attach_delete_bundle('node', $type); module_invoke_all('node_type_delete', $info); // Clear the node type cache. @@ -737,14 +723,13 @@ function node_type_set_defaults($info = array()) { $type->base = ''; $type->description = ''; $type->help = ''; - $type->has_title = 1; - $type->has_body = 1; - $type->title_label = t('Title'); - $type->body_label = t('Body'); $type->custom = 0; $type->modified = 0; $type->locked = 1; $type->is_new = 1; + + $type->has_title = 1; + $type->title_label = 'Title'; } $new_type = clone $type; @@ -752,13 +737,10 @@ function node_type_set_defaults($info = array()) { foreach ($info as $key => $data) { $new_type->$key = $data; } - // If the type has no title or body, set an empty label. + // If the type has no title, set an empty label. if (!$new_type->has_title) { $new_type->title_label = ''; } - if (!$new_type->has_body) { - $new_type->body_label = ''; - } $new_type->orig_type = isset($info['type']) ? $info['type'] : ''; return $new_type; @@ -1277,7 +1259,7 @@ function node_build_content($node, $view_mode = 'full') { // to know when a teaser view is different than a full view. $links = array(); if ($view_mode == 'teaser') { - $links['node_readmore'] = array( + $links['node-readmore'] = array( 'title' => t('Read more'), 'href' => 'node/' . $node->nid, 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title)) @@ -1360,7 +1342,7 @@ function template_preprocess_node(&$variables) { } // Flatten the node object's member fields. - $variables = array_merge((array)$node, $variables); + $variables = array_merge((array) $node, $variables); // Helpful $content variable for templates. foreach (element_children($variables['elements']) as $key) { @@ -1560,16 +1542,10 @@ function node_search_execute($keys = NULL) { // Add the ranking expressions. _node_rankings($query); - // Add a count query. - $inner_query = clone $query; - $count_query = db_select($inner_query->fields('i', array('sid')), NULL, array('target' => 'slave')); - $count_query->addExpression('COUNT(*)'); - $query->setCountQuery($count_query); + // Load results. $find = $query ->limit(10) ->execute(); - - // Load results. $results = array(); foreach ($find as $item) { // Render the node. @@ -3314,6 +3290,7 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { function node_content_form($node, $form_state) { // It is impossible to define a content type without implementing hook_form() // @todo: remove this requirement. + $form = array(); $type = node_type_get_type($node); if ($type->has_title) { @@ -3669,4 +3646,16 @@ class NodeController extends DrupalDefaultEntityController { $this->hookLoadArguments[] = array_keys($typed_nodes); parent::attachLoad($nodes, $revision_id); } + + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + // Ensure that uid is taken from the {node} table, + // alias timestamp to revision_timestamp and add revision_uid. + $query = parent::buildQuery($ids, $conditions, $revision_id); + $fields =& $query->getFields(); + unset($fields['timestamp']); + $query->addField('revision', 'timestamp', 'revision_timestamp'); + $fields['uid']['table'] = 'base'; + $query->addField('revision', 'uid', 'revision_uid'); + return $query; + } } diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index c70c0eaf0..c39278cd4 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: node.pages.inc,v 1.123 2010/04/24 14:49:14 dries Exp $ +// $Id: node.pages.inc,v 1.126 2010/05/10 06:34:39 dries Exp $ /** * @file @@ -66,7 +66,7 @@ function node_add($type) { // If a node type has been specified, validate its existence. if (isset($types[$type])) { // Initialize settings: - $node = (object)array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => LANGUAGE_NONE); + $node = (object) array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => LANGUAGE_NONE); drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH); $output = drupal_get_form($type . '_node_form', $node); @@ -76,7 +76,7 @@ function node_add($type) { } function node_form_validate($form, &$form_state) { - $node = (object)$form_state['values']; + $node = (object) $form_state['values']; node_validate($node, $form); // Field validation. Requires access to $form_state, so this cannot be @@ -95,7 +95,7 @@ function node_form($form, &$form_state, $node) { } if (isset($form_state['node'])) { - $node = (object)($form_state['node'] + (array)$node); + $node = (object) ($form_state['node'] + (array) $node); } if (isset($form_state['node_preview'])) { $form['#prefix'] = $form_state['node_preview']; @@ -196,7 +196,13 @@ function node_form($form, &$form_state, $node) { '#collapsed' => TRUE, '#group' => 'additional_settings', '#attached' => array( - 'js' => array(drupal_get_path('module', 'node') . '/node.js'), + 'js' => array( + drupal_get_path('module', 'node') . '/node.js', + array( + 'type' => 'setting', + 'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))), + ), + ), ), '#weight' => 90, ); @@ -271,7 +277,6 @@ function node_form($form, &$form_state, $node) { ); } $form['#validate'][] = 'node_form_validate'; - $form['#theme'] = array($node->type . '_node_form', 'node_form'); $form['#builder_function'] = 'node_form_submit_build_node'; field_attach_form('node', $node, $form, $form_state, $node->language); @@ -415,11 +420,11 @@ function node_form_submit_build_node($form, &$form_state) { // functions to process the form values into an updated node. unset($form_state['submit_handlers']); form_execute_handlers('submit', $form, $form_state); - $node = node_submit((object)$form_state['values']); + $node = node_submit((object) $form_state['values']); field_attach_submit('node', $node, $form, $form_state); - $form_state['node'] = (array)$node; + $form_state['node'] = (array) $node; $form_state['rebuild'] = TRUE; return $node; } diff --git a/modules/node/node.test b/modules/node/node.test index 73b57d5a3..ad2768dea 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -1,5 +1,5 @@ <?php -// $Id: node.test,v 1.81 2010/04/20 09:48:06 webchick Exp $ +// $Id: node.test,v 1.85 2010/05/07 15:00:56 dries Exp $ /** * Test the node_load_multiple() function. @@ -260,7 +260,7 @@ class PageEditTestCase extends DrupalWebTestCase { // Check that the title and body fields are displayed with the correct values. $active = '<span class="element-invisible">' . t('(active tab)') . '</span>'; - $link_text = t('!local-task-title !active', array('!local-task-title' => t('Edit'), '!active' => $active)); + $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active)); $this->assertText(strip_tags($link_text), 0, t('Edit tab found and marked active.')); $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); @@ -1087,39 +1087,33 @@ class NodeTypeTestCase extends DrupalWebTestCase { $this->assertRaw('Title', t('Title field was found.')); $this->assertRaw('Body', t('Body field was found.')); - // Rename the title field and remove the body field. + // Rename the title field. $edit = array( 'title_label' => 'Foo', - 'body_label' => '', ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + // Refresh the field information for the rest of the test. field_info_cache_clear(); - $this->assertFalse(field_info_instance('node', 'body', 'page'), t('Body field was removed.')); $this->drupalGet('node/add/page'); $this->assertRaw('Foo', t('New title label was displayed.')); $this->assertNoRaw('Title', t('Old title label was not displayed.')); - $this->assertNoRaw('Full text', t('Body field was not found.')); - // Add the body field again and change the name, machine name and description. + // Change the name, machine name and description. $edit = array( 'name' => 'Bar', 'type' => 'bar', 'description' => 'Lorem ipsum.', - 'body_label' => 'Baz', ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - field_info_cache_clear(); - $instance = field_info_instance('node', 'body', 'bar'); - $this->assertEqual($instance['label'], 'Baz', t('Body field was added.')); $this->drupalGet('node/add'); $this->assertRaw('Bar', t('New name was displayed.')); $this->assertRaw('Lorem ipsum', t('New description was displayed.')); $this->clickLink('Bar'); $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), t('New machine name was used in URL.')); $this->assertRaw('Foo', t('Title field was found.')); - $this->assertRaw('Baz', t('Body field was found.')); + $this->assertRaw('Body', t('Body field was found.')); } } @@ -1297,9 +1291,11 @@ class NodeTitleTestCase extends DrupalWebTestCase { * Create one node and test if the node title has the correct value. */ function testNodeTitle() { - // Create "Basic page" content with title + // Create "Basic page" content with title. + // Add the node to the frontpage so we can test if teaser links are clickable. $settings = array( 'title' => $this->randomName(8), + 'promote' => 1, ); $node = $this->drupalCreateNode($settings); @@ -1315,11 +1311,15 @@ class NodeTitleTestCase extends DrupalWebTestCase { // Test node title in comment preview. $this->assertEqual(current($this->xpath('//div[@id=:id]/h2/a', array(':id' => 'node-' . $node->nid))), $node->title, 'Node preview title is equal to node title.', 'Node'); + + // Test node title is clickable on teaser list (/node). + $this->drupalGet('node'); + $this->clickLink($node->title); } } /** - * Test the node_feed() functionality + * Test the node_feed() functionality. */ class NodeFeedTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1661,7 +1661,7 @@ class NodeQueryAlter extends DrupalWebTestCase { } catch (Exception $e) { $this->fail($e->getMessage()); - $this->fail((string)$query); + $this->fail((string) $query); $this->fail(t('Altered query is malformed')); } } @@ -1699,7 +1699,7 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase { ); $node = $this->drupalCreateNode($settings); - // Load node so that the body and summary fields are structured properly. + // Load node so that the body and summary fields are structured properly. $node = node_load($node->nid); $instance = field_info_instance('node', 'body', $node->type); diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index 98b5e2696..e5777a6c6 100644 --- a/modules/node/tests/node_access_test.info +++ b/modules/node/tests/node_access_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = node_access_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/node/tests/node_presave_test.info b/modules/node/tests/node_presave_test.info index b72e32581..57fcd8aea 100644 --- a/modules/node/tests/node_presave_test.info +++ b/modules/node/tests/node_presave_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = node_presave_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index 1d2bef874..ec3291f31 100644 --- a/modules/node/tests/node_test.info +++ b/modules/node/tests/node_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = node_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index a0fa4462f..da643046d 100644 --- a/modules/node/tests/node_test_exception.info +++ b/modules/node/tests/node_test_exception.info @@ -7,8 +7,8 @@ core = 7.x files[] = node_test_exception.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/openid/openid.api.php b/modules/openid/openid.api.php index d65ddb4cb..b797e807c 100644 --- a/modules/openid/openid.api.php +++ b/modules/openid/openid.api.php @@ -1,5 +1,5 @@ <?php -// $Id: openid.api.php,v 1.4 2010/03/02 08:59:54 dries Exp $ +// $Id: openid.api.php,v 1.5 2010/05/13 17:37:24 dries Exp $ /** * @file @@ -71,8 +71,8 @@ function hook_openid_discovery_method_info() { * Allow modules to alter discovery methods. */ function hook_openid_discovery_method_info_alter(&$methods) { - // Remove Google discovery scheme. - unset($methods['google']); + // Remove XRI discovery scheme. + unset($methods['xri']); } /** diff --git a/modules/openid/openid.css b/modules/openid/openid.css index 7a8b1bcb3..dc587683c 100644 --- a/modules/openid/openid.css +++ b/modules/openid/openid.css @@ -1,10 +1,10 @@ -/* $Id: openid.css,v 1.10 2010/01/30 07:59:25 dries Exp $ */ +/* $Id: openid.css,v 1.11 2010/05/05 16:28:06 dries Exp $ */ #edit-openid-identifier { background-image: url("login-bg.png"); - background-position: 0% 50%; + background-position: left 50%; /* LTR */ background-repeat: no-repeat; - padding-left: 20px; + padding-left: 20px; /* LTR */ } div.form-item-openid-identifier { display: block; @@ -13,11 +13,6 @@ html.js #user-login-form div.form-item-openid-identifier, html.js #user-login div.form-item-openid-identifier { display: none; } -html.js #user-login-form li.openid-link, -html.js #user-login li.openid-link { - display : block; - list-style: none; -} #user-login-form ul { margin-top: 0; } @@ -27,14 +22,26 @@ html.js #user-login li.openid-link { #user-login ul li { margin: 0; } -#user-login-form li.openid-link, -#user-login-form li.user-link, -#user-login li.openid-link, -#user-login li.user-link { +#user-login-form .openid-links { + padding-bottom: 0; +} +#user-login .openid-links { + padding-left: 0; /* LTR */ +} +#user-login-form .openid-links li, +#user-login .openid-links li { display: none; + list-style: none; +} +html.js #user-login-form li.openid-link, +html.js #user-login li.openid-link { + display: block; + margin-left: 0; /* LTR */ } #user-login-form li.openid-link a, #user-login li.openid-link a { - background: transparent url("login-bg.png") no-repeat 0 2px; - padding: 0 20px; + background-image: url("login-bg.png"); + background-position: left top; /* LTR */ + background-repeat: no-repeat; + padding: 0 0 0 1.5em; /* LTR */ } diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc index 0a0318568..aba6ed515 100644 --- a/modules/openid/openid.inc +++ b/modules/openid/openid.inc @@ -1,5 +1,5 @@ <?php -// $Id: openid.inc,v 1.31 2010/04/24 14:49:14 dries Exp $ +// $Id: openid.inc,v 1.33 2010/05/13 17:37:24 dries Exp $ /** * @file @@ -65,11 +65,6 @@ define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0'); */ define('OPENID_NS_XRD', 'xri://$xrd*($v*2.0)'); -/** - * OpenID IDP for Google hosted domains. - */ -define('OPENID_NS_GOOGLE', 'http://namespace.google.com/openid/xmlns'); - /** * Performs an HTTP 302 redirect (for the 1.x protocol). */ @@ -89,11 +84,20 @@ function openid_redirect_http($url, $message) { * Creates a js auto-submit redirect for (for the 2.x protocol) */ function openid_redirect($url, $message) { - $output = '<html><head><title>' . t('OpenID redirect') . "</title></head>\n<body>"; + global $language; + + $output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n"; + $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $language->language . '" lang="' . $language->language . '">' . "\n"; + $output .= "<head>\n"; + $output .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; + $output .= "<title>" . t('OpenID redirect') . "</title>\n"; + $output .= "</head>\n"; + $output .= "<body>\n"; $elements = drupal_get_form('openid_redirect_form', $url, $message); $output .= drupal_render($elements); - $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>'; - $output .= "</body></html>\n"; + $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>' . "\n"; + $output .= "</body>\n"; + $output .= "</html>\n"; print $output; drupal_exit(); @@ -112,8 +116,8 @@ function openid_redirect_form($form, &$form_state, $url, $message) { $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', - '#prefix' => '<noscript>', - '#suffix' => '</noscript>', + '#prefix' => '<noscript><div>', + '#suffix' => '</div></noscript>', '#value' => t('Send'), ); @@ -291,34 +295,6 @@ function _openid_url_normalize($url) { return $normalized_url; } -/** - * OpenID normalization method: Normalize Google identifiers. - * - * This transforms a Google identifier (user@domain) into an XRDS URL. - * - * @see http://sites.google.com/site/oauthgoog/fedlogininterp/openiddiscovery#TOC-IdP-Discovery - */ -function _openid_google_idp_normalize($identifier) { - if (!valid_email_address($identifier)) { - return; - } - - // If the identifier is a valid email address, try to discover the domain - // with Google Federated Login. We only use the generic URL, because the - // domain-specific URL (http://example.com/.well-known/host-meta) cannot - // be trusted. - list($name, $domain) = explode('@', $identifier, 2); - $response = drupal_http_request('https://www.google.com/accounts/o8/.well-known/host-meta?hd=' . rawurlencode($domain)); - if (isset($response->error) || $response->code != 200) { - return; - } - - if (preg_match('/Link: <(.*)>/', $response->data, $matches)) { - $xrds_url = $matches[1]; - return $xrds_url; - } -} - /** * Create a serialized message packet as per spec: $key:$value\n . */ diff --git a/modules/openid/openid.info b/modules/openid/openid.info index d682c5654..46749768f 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -11,8 +11,8 @@ files[] = xrds.inc files[] = openid.install files[] = openid.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/openid/openid.module b/modules/openid/openid.module index c1d6e5882..3964e59f4 100644 --- a/modules/openid/openid.module +++ b/modules/openid/openid.module @@ -1,5 +1,5 @@ <?php -// $Id: openid.module,v 1.84 2010/04/07 16:35:03 dries Exp $ +// $Id: openid.module,v 1.89 2010/05/20 08:51:24 dries Exp $ /** * @file @@ -136,7 +136,9 @@ function _openid_user_login_form_alter(&$form, &$form_state) { ); $form['openid_links'] = array( - '#markup' => theme('item_list', array('items' => $items)), + '#theme' => 'item_list', + '#items' => $items, + '#attributes' => array('class' => array('openid-links')), '#weight' => 1, ); @@ -385,12 +387,10 @@ function openid_openid_discovery_method_info() { // The discovery process will stop as soon as one discovery method succeed. // We first attempt to discover XRI-based identifiers, then standard XRDS // identifiers via Yadis and HTML-based discovery, conforming to the OpenID 2.0 - // specification. If those fail, we attempt to discover services based on - // the Google user discovery scheme. + // specification. return array( 'xri' => '_openid_xri_discovery', 'xrds' => '_openid_xrds_discovery', - 'google' => '_openid_google_user_discovery', ); } @@ -437,14 +437,14 @@ function _openid_xrds_discovery($claimed_id) { $result = drupal_http_request($xrds_url, array('headers' => $headers)); if (!isset($result->error)) { - if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) { + if (isset($result->headers['content-type']) && preg_match("/application\/xrds\+xml/", $result->headers['content-type'])) { // Parse XML document to find URL $services = _openid_xrds_parse($result->data); } else { $xrds_url = NULL; - if (isset($result->headers['X-XRDS-Location'])) { - $xrds_url = $result->headers['X-XRDS-Location']; + if (isset($result->headers['x-xrds-location'])) { + $xrds_url = $result->headers['x-xrds-location']; } else { // Look for meta http-equiv link in HTML head @@ -485,52 +485,16 @@ function _openid_xrds_discovery($claimed_id) { return $services; } -/** - * OpenID discovery method: Perform an user discovery using Google Discovery protocol. - * - * This transforms a OpenID identifier into an OpenID endpoint. - * - * @see http://sites.google.com/site/oauthgoog/fedlogininterp/openiddiscovery#TOC-User-Discovery - * @see hook_openid_discovery_method_info() - */ -function _openid_google_user_discovery($claimed_id) { - $xrds_url = $claimed_id; - $url = @parse_url($xrds_url); - if (empty($url['scheme']) || ($url['scheme'] != 'http' && $scheme['scheme'] != 'https') || empty($url['host'])) { - return; - } - - $response = drupal_http_request('https://www.google.com/accounts/o8/.well-known/host-meta?hd=' . rawurlencode($url['host'])); - if (isset($response->error) || $response->code != 200) { - return; - } - - if (preg_match('/Link: <(.*)>/', $response->data, $matches)) { - $xrds_url = $matches[1]; - $services = _openid_xrds_discovery($xrds_url); - - foreach ($services as $i => $service) { - if (in_array('http://www.iana.org/assignments/relation/describedby', $service['types']) && $service['service']->children(OPENID_NS_GOOGLE)->URITemplate) { - $template = (string)$service['service']->children(OPENID_NS_GOOGLE)->URITemplate; - $xrds_url = str_replace('{%uri}', rawurlencode($claimed_id), $template); - return _openid_xrds_discovery($xrds_url); - } - } - } -} - /** * Implementation of hook_openid_normalization_method_info(). * * Define standard normalization methods. */ function openid_openid_normalization_method_info() { - // We first try to normalize Google Identifiers (user@domain) into their - // corresponding XRDS URL. If this fail, we proceed with standard OpenID - // normalization by normalizing XRI idenfiers. Finally, normalize the identifier - // into a canonical URL. + // OpenID Authentication 2.0, section 7.2: + // If the User-supplied Identifier looks like an XRI, treat it as such; + // otherwise treat it as an HTTP URL. return array( - 'google_idp' => '_openid_google_idp_normalize', 'xri' => '_openid_xri_normalize', 'url' => '_openid_url_normalize', ); @@ -548,7 +512,7 @@ function openid_association($op_endpoint) { // Remove Old Associations: db_delete('openid_association') - ->condition('created + expires_in', REQUEST_TIME, '<') + ->where('created + expires_in < :request_time', array(':request_time' => REQUEST_TIME)) ->execute(); // Check to see if we have an association for this IdP already @@ -687,6 +651,8 @@ function openid_association_request($public) { } function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $service) { + global $base_url; + module_load_include('inc', 'openid'); $request = array( @@ -699,10 +665,10 @@ function openid_authentication_request($claimed_id, $identity, $return_to = '', if ($service['version'] == 2) { $request['openid.ns'] = OPENID_NS_2_0; $request['openid.claimed_id'] = $claimed_id; - $request['openid.realm'] = url('', array('absolute' => TRUE)); + $request['openid.realm'] = $base_url .'/'; } else { - $request['openid.trust_root'] = url('', array('absolute' => TRUE)); + $request['openid.trust_root'] = $base_url .'/'; } // Always request Simple Registration. The specification doesn't mandate diff --git a/modules/openid/openid.test b/modules/openid/openid.test index 3a2ebec53..b16760092 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -1,5 +1,5 @@ <?php -// $Id: openid.test,v 1.22 2010/04/07 14:22:34 dries Exp $ +// $Id: openid.test,v 1.23 2010/05/13 17:37:24 dries Exp $ /** * Base class for OpenID tests. @@ -509,16 +509,4 @@ class OpenIDUnitTest extends DrupalWebTestCase { $this->assertEqual(openid_normalize('http://example.com/path#fragment'), 'http://example.com/path', t('openid_normalize() correctly normalized a URL with a fragment.')); } - - /** - * Test _openid_google_idp_normalize(). - */ - function testGoogleIdpNormalize() { - // We consider that Gmail will always be Gmail. - $this->assertTrue(valid_url(_openid_google_idp_normalize('testuser@gmail.com'), TRUE), t('_openid_google_idp_normalize() correctly normalized a Google Gmail identifier.')); - // This is a test domain documented on http://sites.google.com/site/oauthgoog/fedlogininterp/saml-idp. - $this->assertTrue(valid_url(_openid_google_idp_normalize('test@lso-test-domain.com'), TRUE), t('_openid_google_idp_normalize() correctly normalized a Google Apps for Domain identifier.')); - // We consider that microsoft.com will never be hosted by Google. - $this->assertFalse(valid_url(_openid_google_idp_normalize('test@microsoft.com'), TRUE), t("_openid_google_idp_normalize() didn't normalized an identifier for a domain that is not Google-enabled.")); - } } diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index bcf5457fc..724ada543 100644 --- a/modules/openid/tests/openid_test.info +++ b/modules/openid/tests/openid_test.info @@ -9,8 +9,8 @@ files[] = openid_test.module dependencies[] = openid hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/overlay/overlay-parent.css b/modules/overlay/overlay-parent.css index 6e184e538..a2005da3d 100644 --- a/modules/overlay/overlay-parent.css +++ b/modules/overlay/overlay-parent.css @@ -1,4 +1,4 @@ -/* $Id: overlay-parent.css,v 1.13 2010/03/20 14:45:05 dries Exp $ */ +/* $Id: overlay-parent.css,v 1.14 2010/04/28 20:08:39 dries Exp $ */ /** * ui-dialog overlay. @@ -38,7 +38,8 @@ body.overlay-autofit { min-height: 100px; } -.overlay.ui-widget-content, .overlay .ui-widget-header { +.overlay.ui-widget-content, +.overlay .ui-widget-header { background: none; border: none; } diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 232c4c2f8..5d16c407a 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -1,4 +1,4 @@ -// $Id: overlay-parent.js,v 1.38 2010/04/24 07:14:29 dries Exp $ +// $Id: overlay-parent.js,v 1.41 2010/05/14 07:45:54 dries Exp $ (function ($) { @@ -31,6 +31,17 @@ Drupal.behaviors.overlayParent = { /** * Overlay object for parent windows. + * + * Events + * Overlay triggers a number of events that can be used by other scripts. + * - drupalOverlayOpen: This event is triggered when the overlay is opened. + * - drupalOverlayBeforeClose: This event is triggered when the overlay attempts + * to close. If an event handler returns false, the close will be prevented. + * - drupalOverlayClose: This event is triggered when the overlay is closed. + * - drupalOverlayBeforeLoad: This event is triggered right before a new URL + * is loaded into the overlay. + * - drupalOverlayLoad: This event is triggered when the overlay is finished + * loading. */ Drupal.overlay = Drupal.overlay || { options: {}, @@ -66,10 +77,6 @@ Drupal.overlay = Drupal.overlay || { * - height: height of the overlay in pixels. * - autoFit: boolean indicating whether the overlay should be resized to * fit the contents of the document loaded. - * - onOverlayOpen: callback to invoke when the overlay is opened. - * - onOverlayCanClose: callback to allow external scripts to decide if the - * overlay can be closed. - * - onOverlayClose: callback to invoke when the overlay is closed. * - customDialogOptions: an object with custom jQuery UI Dialog options. * * @return @@ -89,9 +96,6 @@ Drupal.overlay.open = function (options) { width: options.width, height: options.height, autoFit: (options.autoFit == undefined || options.autoFit), - onOverlayOpen: options.onOverlayOpen, - onOverlayCanClose: options.onOverlayCanClose, - onOverlayClose: options.onOverlayClose, customDialogOptions: options.customDialogOptions || {} }; @@ -103,6 +107,9 @@ Drupal.overlay.open = function (options) { // Open the dialog. self.$container.dialog('open'); + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayOpen'); + return true; }; @@ -182,10 +189,12 @@ Drupal.overlay.create = function () { return false; } - // Allow external scripts to decide if the overlay can be closed. - // The external script should call Drupal.overlay.close() again when it is - // ready for closing. - if ($.isFunction(self.options.onOverlayCanClose) && self.options.onOverlayCanClose(self) === false) { + // Allow other scripts to decide if the overlay can be closed. If an event- + // handler returns false the overlay won't be closed. The external script + // should call Drupal.overlay.close() again when it is ready for closing. + var event = $.Event('drupalOverlayBeforeClose'); + $(document).trigger(event); + if (event.isDefaultPrevented()) { return false; } @@ -216,9 +225,8 @@ Drupal.overlay.create = function () { self.lastHeight = 0; - if ($.isFunction(self.options.onOverlayClose)) { - self.options.onOverlayClose(); - } + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayClose'); }; // Default jQuery UI Dialog options. @@ -319,18 +327,24 @@ Drupal.overlay.load = function (url) { // No need to resize while loading. clearTimeout(self.resizeTimeoutID); + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayBeforeLoad'); + // While the overlay is loading, we remove the loaded class from the dialog. // After the loading is finished, the loaded class is added back. The loaded // class is being used to hide the iframe while loading. // See overlay-parent.css .overlay-loaded #overlay-element. self.$dialog.removeClass('overlay-loaded'); - self.$iframe + self.$iframe.once('overlay-event-load') .bind('load.overlay-event', function () { self.isLoading = false; // Only continue when overlay is still open and not closing. if (self.isOpen && !self.isClosing) { self.$dialog.addClass('overlay-loaded'); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayLoad'); } else { self.destroy(); @@ -636,15 +650,9 @@ Drupal.overlay.outerResize = function () { return; } - // Consider any region that should be visible above the overlay (such as - // an admin toolbar). - var $displaceTop = $('.overlay-displace-top'); - var displaceTopHeight = 0; - $displaceTop.each(function () { - displaceTopHeight += $(this).height(); - }); + var displaceTop = Drupal.displace ? Drupal.displace.getDisplacement('top') : 0; - self.$wrapper.css('top', displaceTopHeight); + self.$wrapper.css('top', displaceTop); // When the overlay has no height yet, make it fit exactly in the window, // or the configured height when autoFit is disabled. @@ -652,7 +660,7 @@ Drupal.overlay.outerResize = function () { var titleBarHeight = self.$dialogTitlebar.outerHeight(true); if (self.options.autoFit || self.options.height == undefined ||!isNan(self.options.height)) { - self.lastHeight = parseInt($(window).height() - displaceTopHeight - titleBarHeight - 45); + self.lastHeight = parseInt($(window).height() - displaceTop - titleBarHeight - 45); } else { self.lastHeight = self.options.height; @@ -689,11 +697,11 @@ Drupal.overlay.clickHandler = function (event) { var $target = $(event.target); - if (self.isOpen && $target.closest('.overlay-displace-top, .overlay-displace-bottom').length) { + if (self.isOpen && $target.closest('.displace-top, .displace-bottom').length) { // Click events in displaced regions could potentionally change the size of // that region (e.g. the toggle button of the toolbar module). Trigger the // resize event to force a recalculation of overlay's size/position. - $(window).triggerHandler('resize.overlay-event'); + $(window).triggerHandler('resize'); } // Only continue by left-click or right-click. @@ -810,14 +818,13 @@ Drupal.overlay.hashchangeHandler = function (event) { else { // There is not an overlay opened yet; we should open a new one. var overlayOptions = { - url: linkURL, - onOverlayClose: function () { - // Clear the overlay URL fragment. - $.bbq.pushState(); - - self.resetActiveClass(self.getPath(window.location)); - } + url: linkURL }; + $(document).one('drupalOverlayClose', function () { + // Clear the overlay URL fragment. + $.bbq.pushState(); + self.resetActiveClass(self.getPath(window.location)); + }); self.open(overlayOptions); } } @@ -916,7 +923,7 @@ Drupal.overlay.resetActiveClass = function(activePath) { var self = this; var windowDomain = window.location.protocol + window.location.hostname; - $('.overlay-displace-top, .overlay-displace-bottom') + $('.displace-top, .displace-bottom') .find('a[href]') // Remove active class from all links in displaced regions. .removeClass('active') diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index 23fcde282..47e7ff7e7 100644 --- a/modules/overlay/overlay.info +++ b/modules/overlay/overlay.info @@ -7,8 +7,8 @@ core = 7.x files[] = overlay.module files[] = overlay.install -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index e64400c57..917f5bd0f 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -1,5 +1,5 @@ <?php -// $Id: overlay.module,v 1.17 2010/04/24 07:14:29 dries Exp $ +// $Id: overlay.module,v 1.18 2010/05/14 07:45:54 dries Exp $ /** * @file @@ -299,16 +299,6 @@ function overlay_preprocess_page(&$variables) { } } -/** - * Preprocess template variables for toolbar.tpl.php. - * - * Adding the 'overlay-displace-top' class to the toolbar pushes the overlay - * down, so it appears below the toolbar. - */ -function overlay_preprocess_toolbar(&$variables) { - $variables['classes_array'][] = "overlay-displace-top"; -} - /** * Form after_build callback. * diff --git a/modules/path/path.admin.inc b/modules/path/path.admin.inc index e19048326..d6fd4c0b4 100644 --- a/modules/path/path.admin.inc +++ b/modules/path/path.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: path.admin.inc,v 1.43 2010/04/24 14:49:14 dries Exp $ +// $Id: path.admin.inc,v 1.44 2010/05/14 04:57:59 webchick Exp $ /** * @file @@ -19,14 +19,13 @@ function path_admin_overview($keys = NULL) { $alias_exists = (bool) db_query_range('SELECT 1 FROM {url_alias} WHERE language <> :language', 0, 1, array(':language' => LANGUAGE_NONE))->fetchField(); $multilanguage = (module_exists('locale') || $alias_exists); - $header = array( - array('data' => t('Alias'), 'field' => 'alias', 'sort' => 'asc'), - array('data' => t('System'), 'field' => 'source'), - array('data' => t('Operations'), 'colspan' => '2') - ); + $header = array(); + $header[] = array('data' => t('Alias'), 'field' => 'alias', 'sort' => 'asc'); + $header[] = array('data' => t('System'), 'field' => 'source'); if ($multilanguage) { - array_splice($header, 2, 0, array(array('data' => t('Language'), 'field' => 'language'))); + $header[] = array('data' => t('Language'), 'field' => 'language'); } + $header[] = array('data' => t('Operations')); $query = db_select('url_alias')->extend('PagerDefault')->extend('TableSort'); if ($keys) { @@ -42,22 +41,38 @@ function path_admin_overview($keys = NULL) { $rows = array(); $destination = drupal_get_destination(); foreach ($result as $data) { - $row = array( + $row = array(); + $row['data']['alias'] = l($data->alias, $data->source); + $row['data']['source'] = l($data->source, $data->source, array('alias' => TRUE)); + if ($multilanguage) { + $row['data']['language'] = module_invoke('locale', 'language_name', $data->language); + } + + $operations = array(); + $operations['edit'] = array( + 'title' => t('edit'), + 'href' => "admin/config/search/path/edit/$data->pid", + 'query' => $destination, + ); + $operations['delete'] = array( + 'title' => t('delete'), + 'href' => "admin/config/search/path/delete/$data->pid", + 'query' => $destination, + ); + $row['data']['operations'] = array( 'data' => array( - l($data->alias, $data->source), - l($data->source, $data->source, array('alias' => TRUE)), - l(t('edit'), "admin/config/search/path/edit/$data->pid", array('query' => $destination)), - l(t('delete'), "admin/config/search/path/delete/$data->pid", array('query' => $destination)), + '#theme' => 'links', + '#links' => $operations, + '#attributes' => array('class' => array('links', 'inline', 'nowrap')), ), ); + // If the system path maps to a different URL alias, highlight this table // row to let the user know of old aliases. if ($data->alias != drupal_get_path_alias($data->source, $data->language)) { $row['class'] = array('warning'); } - if ($multilanguage) { - array_splice($row['data'], 2, 0, module_invoke('locale', 'language_name', $data->language)); - } + $rows[] = $row; } @@ -65,7 +80,7 @@ function path_admin_overview($keys = NULL) { '#theme' => 'table', '#header' => $header, '#rows' => $rows, - '#empty' => t('No URL aliases available. <a href="@link">Add alias</a>.', array('@link' => url('admin/config/search/path/add'))), + '#empty' => t('No URL aliases available. <a href="@link">Add URL alias</a>.', array('@link' => url('admin/config/search/path/add'))), ); $build['path_pager'] = array('#theme' => 'pager'); diff --git a/modules/path/path.info b/modules/path/path.info index f16c083ea..6aa6b5390 100644 --- a/modules/path/path.info +++ b/modules/path/path.info @@ -9,8 +9,8 @@ files[] = path.admin.inc files[] = path.test configure = admin/config/search/path -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/php/php.info b/modules/php/php.info index 16b501d42..2af11cd9a 100644 --- a/modules/php/php.info +++ b/modules/php/php.info @@ -8,8 +8,8 @@ files[] = php.module files[] = php.install files[] = php.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/poll/poll.info b/modules/poll/poll.info index 50f1c19fe..c3e122779 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -10,8 +10,8 @@ files[] = poll.install files[] = poll.test files[] = poll.tokens.inc -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/poll/poll.module b/modules/poll/poll.module index 876f8b844..340fd7c11 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -1,5 +1,5 @@ <?php -// $Id: poll.module,v 1.345 2010/04/24 14:49:14 dries Exp $ +// $Id: poll.module,v 1.348 2010/05/09 13:37:32 dries Exp $ /** * @file @@ -297,7 +297,6 @@ function poll_form($node, &$form_state) { '#ajax' => array( 'callback' => 'poll_choice_js', 'wrapper' => 'poll-choices', - 'method' => 'replace', 'effect' => 'fade', ), ); @@ -365,8 +364,6 @@ function poll_more_choices_submit($form, &$form_state) { } function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) { - $admin = user_access('administer nodes'); - $form = array( '#tree' => TRUE, ); @@ -422,7 +419,7 @@ function poll_choice_js($form, $form_state) { function poll_node_form_submit(&$form, &$form_state) { // Renumber fields $form_state['values']['choice'] = array_values($form_state['values']['choice']); - $form_state['values']['teaser'] = poll_teaser((object)$form_state['values']); + $form_state['values']['teaser'] = poll_teaser((object) $form_state['values']); } /** diff --git a/modules/poll/poll.test b/modules/poll/poll.test index 717a3d82b..51e3aff13 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -1,5 +1,5 @@ <?php -// $Id: poll.test,v 1.33 2010/04/22 23:59:04 dries Exp $ +// $Id: poll.test,v 1.34 2010/05/12 08:26:15 dries Exp $ /** * @file @@ -375,7 +375,7 @@ class PollVoteCheckHostname extends PollTestCase { // Enable page cache to verify that the result page is not saved in the // cache when anonymous voting is allowed. - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); // Create poll. $title = $this->randomName(); diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 04be1fa37..6d7fc5022 100644 --- a/modules/profile/profile.info +++ b/modules/profile/profile.info @@ -11,8 +11,8 @@ files[] = profile.install files[] = profile.test configure = admin/config/people/profile -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index 867eda390..4c6c1b5b5 100644 --- a/modules/rdf/rdf.info +++ b/modules/rdf/rdf.info @@ -8,8 +8,8 @@ files[] = rdf.install files[] = rdf.module files[] = rdf.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/rdf/rdf.module b/modules/rdf/rdf.module index 15061512a..37d47ffc8 100644 --- a/modules/rdf/rdf.module +++ b/modules/rdf/rdf.module @@ -1,5 +1,5 @@ <?php -// $Id: rdf.module,v 1.39 2010/04/22 21:41:09 webchick Exp $ +// $Id: rdf.module,v 1.40 2010/05/05 15:49:04 dries Exp $ /** * @file @@ -502,7 +502,7 @@ function rdf_preprocess_node(&$variables) { // Adds RDFa markup annotating the number of comments a node has. if (isset($variables['node']->comment_count) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) { // Annotates the 'x comments' link in teaser view. - if (isset($variables['content']['links']['comment']['#links']['comment_comments'])) { + if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) { $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates']; $comment_count_attributes['content'] = $variables['node']->comment_count; $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype']; @@ -512,7 +512,7 @@ function rdf_preprocess_node(&$variables) { // we set an empty rel attribute which triggers rule number 5. See // http://www.w3.org/TR/rdfa-syntax/#sec_5.5. $comment_count_attributes['rel'] = ''; - $variables['content']['links']['comment']['#links']['comment_comments']['attributes'] += $comment_count_attributes; + $variables['content']['links']['comment']['#links']['comment-comments']['attributes'] += $comment_count_attributes; } // In full node view, the number of comments is not displayed by // node.tpl.php so it is expressed in RDFa in the <head> tag. diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index 9f59ec3b6..416586d84 100644 --- a/modules/rdf/tests/rdf_test.info +++ b/modules/rdf/tests/rdf_test.info @@ -8,8 +8,8 @@ files[] = rdf_test.install files[] = rdf_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/search/search.api.php b/modules/search/search.api.php index 62418a582..f366e6436 100644 --- a/modules/search/search.api.php +++ b/modules/search/search.api.php @@ -1,5 +1,5 @@ <?php -// $Id: search.api.php,v 1.25 2010/02/27 10:54:12 dries Exp $ +// $Id: search.api.php,v 1.26 2010/05/12 15:53:43 dries Exp $ /** * @file @@ -177,16 +177,10 @@ function hook_search_execute($keys = NULL) { // Add the ranking expressions. _node_rankings($query); - // Add a count query. - $inner_query = clone $query; - $count_query = db_select($inner_query->fields('i', array('sid'))); - $count_query->addExpression('COUNT(*)'); - $query->setCountQuery($count_query); + // Load results. $find = $query ->limit(10) ->execute(); - - // Load results. $results = array(); foreach ($find as $item) { // Build the node body. diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc index 5c994aa1d..ebab18211 100644 --- a/modules/search/search.extender.inc +++ b/modules/search/search.extender.inc @@ -1,5 +1,5 @@ <?php -// $Id: search.extender.inc,v 1.4 2010/04/16 13:53:43 dries Exp $ +// $Id: search.extender.inc,v 1.5 2010/05/12 15:53:43 dries Exp $ /** * @file @@ -407,6 +407,7 @@ class SearchQuery extends SelectQueryExtender { return new DatabaseStatementEmpty(); } + // Add conditions to query. $this->join('search_dataset', 'd', 'i.sid = d.sid AND i.type = d.type'); $this->condition($this->conditions); @@ -443,4 +444,35 @@ class SearchQuery extends SelectQueryExtender { return $this->query->execute(); } + + /** + * Build the default count query for SearchQuery. + * + * Since SearchQuery always uses GROUP BY, we can default to a subquery. Also + * adding the same conditions as execute() because countQuery() is called + * first. + */ + public function countQuery() { + // Clone the inner query. + $inner = clone $this->query; + + // Add conditions to query. + $inner->join('search_dataset', 'd', 'i.sid = d.sid AND i.type = d.type'); + $inner->condition($this->conditions); + + // Remove existing fields and expressions, they are not needed for a count + // query. + $fields =& $inner->getFields(); + $fields = array(); + $expressions =& $inner->getExpressions(); + $expressions = array(); + + // Add the sid as the only field and count them as a subquery. + $count = db_select($inner->fields('i', array('sid')), NULL, array('target' => 'slave')); + + // Add the COUNT() expression. + $count->addExpression('COUNT(*)'); + + return $count; + } } diff --git a/modules/search/search.info b/modules/search/search.info index 097adfc11..ab1e5798c 100644 --- a/modules/search/search.info +++ b/modules/search/search.info @@ -12,8 +12,8 @@ files[] = search.test files[] = search.extender.inc configure = admin/config/search/settings -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/search/search.module b/modules/search/search.module index 3a511976a..910b2d20b 100644 --- a/modules/search/search.module +++ b/modules/search/search.module @@ -1,5 +1,5 @@ <?php -// $Id: search.module,v 1.345 2010/04/24 14:49:14 dries Exp $ +// $Id: search.module,v 1.348 2010/05/09 19:46:11 dries Exp $ /** * @file @@ -103,12 +103,25 @@ define('PREG_CLASS_PUNCTUATION', '\x{ff65}'); /** - * Matches all CJK characters that are candidates for auto-splitting - * (Chinese, Japanese, Korean). - * Contains kana and BMP ideographs. + * Matches CJK (Chinese, Japanese, Korean) letter-like characters. + * + * This list is derived from the "East Asian Scripts" section of + * http://www.unicode.org/charts/index.html, as well as a comment on + * http://unicode.org/reports/tr11/tr11-11.html listing some character + * ranges that are reserved for additional CJK ideographs. + * + * The character ranges do not include numbers, punctuation, or symbols, since + * these are handled separately in search. Note that radicals and strokes are + * considered symbols. (See + * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt) + * + * @see search_expand_cjk() */ -define('PREG_CLASS_CJK', '\x{3041}-\x{30ff}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}' . -'\x{4e00}-\x{9fbb}\x{f900}-\x{fad9}'); +define('PREG_CLASS_CJK', '\x{1100}-\x{11FF}\x{3040}-\x{309F}\x{30A1}-\x{318E}' . + '\x{31A0}-\x{31B7}\x{31F0}-\x{31FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FCF}' . + '\x{A000}-\x{A48F}\x{A4D0}-\x{A4FD}\x{A960}-\x{A97F}\x{AC00}-\x{D7FF}' . + '\x{F900}-\x{FAFF}\x{FF21}-\x{FF3A}\x{FF41}-\x{FF5A}\x{FF66}-\x{FFDC}' . + '\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}'); /** * Implements hook_help(). @@ -124,7 +137,9 @@ function search_help($path, $arg) { $output .= '<dt>' . t('Searching content and users') . '</dt>'; $output .= '<dd>' . t('Users with <em>Use search</em> permission can use the search block and <a href="@search">Search page</a>. Users with the <em>View published content</em> permission can search for content containing exact keywords. Users with the <em>View user profiles</em> permission can search for users containing the keyword anywhere in the user name, and users with the <em>Administer users</em> permission can search for users by email address. Additionally, users with <em>Use advanced search</em> permission can find content using more complex search methods and filtering by choosing the <em>Advanced search</em> option on the <a href="@search">Search page</a>.', array('@search' => url('search'))) . '</dd>'; $output .= '<dt>' . t('Indexing content with cron') . '</dt>'; - $output .= '<dd>' . t('To provide keyword searching, the search engine maintains an index of words found in the content. To build and maintain this index, a correctly configured <a href="@cron">cron maintenance task</a> is required. Users with <em>Administer search</em> permission can further configure the cron settings on the <a href="@searchsettings">Search settings page</a>.', array('@cron' => 'http://drupal.org/cron', '@searchsettings' => url('admin/config/search/settings'))) . '</dd>'; + $output .= '<dd>' . t('To provide keyword searching, the search engine maintains an index of words found in the content and its fields, along with text added to your content by other modules (such as comments from the core Comment module, and taxonomy terms from the core Taxonomy module). To build and maintain this index, a correctly configured <a href="@cron">cron maintenance task</a> is required. Users with <em>Administer search</em> permission can further configure the cron settings on the <a href="@searchsettings">Search settings page</a>.', array('@cron' => 'http://drupal.org/cron', '@searchsettings' => url('admin/config/search/settings'))) . '</dd>'; + $output .= '<dt>' . t('Content reindexing') . '</dt>'; + $output .= '<dd>' . t('Content-related actions on your site (creating, editing, or deleting content and comments) automatically cause affected content items to be marked for indexing or reindexing at the next cron run. When content is marked for reindexing, the previous content remains in the index until cron runs, at which time it is replaced by the new content. Unlike content-related actions, actions related to the structure of your site do not cause affected content to be marked for reindexing. Examples of structure-related actions that affect content include deleting or editing taxonomy terms, enabling or disabling modules that add text to content (such as Taxonomy, Comment, and field-providing modules), and modifying the fields or display parameters of your content types. If you take one of these actions and you want to ensure that the search index is updated to reflect your changed site structure, you can mark all content for reindexing by clicking the "Re-index site" button on the <a href="@searchsettings">Search settings page</a>. If you have a lot of content on your site, it may take several cron runs for the content to be reindexed.', array('@searchsettings' => url('admin/config/search/settings'))) . '</dd>'; $output .= '<dt>' . t('Configuring search settings') . '</dt>'; $output .= '<dd>' . t('Indexing behavior can be adjusted using the <a href="@searchsettings">Search settings page</a>. Users with <em>Administer search</em> permission can control settings such as the <em>Number of items to index per cron run</em>, <em>Indexing settings</em> (word length), <em>Active search modules</em>, and <em>Content ranking</em>, which lets you adjust the priority in which indexed content is returned in results.', array('@searchsettings' => url('admin/config/search/settings'))) . '</dd>'; $output .= '<dt>' . t('Search block') . '</dt>'; @@ -244,18 +259,43 @@ function search_menu() { 'file path' => drupal_get_path('module', 'dblog'), 'file' => 'dblog.admin.inc', ); + + // Add paths for searching. We add each module search path twice: once without + // and once with %menu_tail appended. The reason for this is that we want to + // preserve keywords when switching tabs, and also to have search tabs + // highlighted properly. The only way to do that within the Drupal menu + // system appears to be having two sets of tabs. See discussion on issue + // http://drupal.org/node/245103 for details. + drupal_static_reset('search_get_info'); - $search_hooks = search_get_info(); - foreach(variable_get('search_active_modules', array('node', 'user')) as $module) { - if (isset($search_hooks[$module])) { - $items['search/' . $search_hooks[$module]['path'] . '/%menu_tail'] = array( - 'title' => $search_hooks[$module]['title'], + if ($active = variable_get('search_active_modules', array('node', 'user'))) { + foreach (array_intersect_key(search_get_info(), array_flip($active)) as $module => $search_info) { + $path = 'search/' . $search_info['path']; + $items[$path] = array( + 'title' => $search_info['title'], + 'page callback' => 'search_view', + 'page arguments' => array($module), + 'access callback' => '_search_menu_access', + 'access arguments' => array($module), + 'type' => $module == 'node' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'file' => 'search.pages.inc', + 'weight' => $module == 'node' ? -10 : 0, + ); + $items["$path/%menu_tail"] = array( + 'title' => $search_info['title'], 'page callback' => 'search_view', 'page arguments' => array($module), 'access callback' => '_search_menu_access', 'access arguments' => array($module), + // The default local task points to its parent, but this item points to + // where it should so it should not be changed. 'type' => MENU_LOCAL_TASK, 'file' => 'search.pages.inc', + 'weight' => 0, + // These tabs are not subtabs. + 'tab_root' => 'search/node/%', + // These tabs need to display at the same level. + 'tab_parent' => 'search', ); } } @@ -420,28 +460,45 @@ function search_simplify($text) { } /** - * Basic CJK tokenizer. Simply splits a string into consecutive, overlapping - * sequences of characters ('minimum_word_size' long). + * Splits CJK (Chinese, Japanese, Korean) text into tokens. + * + * The Search module matches exact words, where a word is defined to be a + * sequence of characters delimited by spaces or punctuation. CJK languages are + * written in long strings of characters, though, not split up into words. So + * in order to allow search matching, we split up CJK text into tokens + * consisting of consecutive, overlapping sequences of characters whose length + * is equal to the 'minimum_word_size' variable. This tokenizing is only done if + * the 'overlap_cjk' variable is TRUE. + * + * @param $matches + * This function is a callback for preg_replace_callback(), which is called + * from search_simplify(). So, $matches is an array of regular expression + * matches, which means that $matches[0] contains the matched text -- a string + * of CJK characters to tokenize. + * + * @return + * Tokenized text, starting and ending with a space character. */ function search_expand_cjk($matches) { $min = variable_get('minimum_word_size', 3); $str = $matches[0]; - $l = drupal_strlen($str); - // Passthrough short words - if ($l <= $min) { + $length = drupal_strlen($str); + // If the text is shorter than the minimum word size, don't tokenize it. + if ($length <= $min) { return ' ' . $str . ' '; } $tokens = ' '; - // FIFO queue of characters + // Build a FIFO queue of characters. $chars = array(); - // Begin loop - for ($i = 0; $i < $l; ++$i) { - // Grab next character + for ($i = 0; $i < $length; $i++) { + // Add the next character off the beginning of the string to the queue. $current = drupal_substr($str, 0, 1); $str = substr($str, strlen($current)); $chars[] = $current; if ($i >= $min - 1) { + // Make a token of $min characters, and add it to the token string. $tokens .= implode('', $chars) . ' '; + // Shift out the first character in the queue. array_shift($chars); } } diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc index 6e5672363..f6058b60b 100644 --- a/modules/search/search.pages.inc +++ b/modules/search/search.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: search.pages.inc,v 1.18 2010/04/13 15:23:03 dries Exp $ +// $Id: search.pages.inc,v 1.19 2010/05/01 01:04:24 webchick Exp $ /** * @file @@ -14,14 +14,14 @@ function search_view($type = 'node') { // the search query URL clean as a whistle: // search/type/keyword+keyword if (!isset($_POST['form_id'])) { - if ($type == '') { - // Note: search/node can not be a default tab because it would take on the - // path of its parent (search). It would prevent remembering keywords when - // switching tabs. This is why we drupal_goto to it from the parent instead. - drupal_goto('search/node'); + $keys = search_get_keys(); + if ($_GET['q'] != 'search' && $type == 'node' && !$keys) { + // Due to how search_menu() sets up the tabs, path search/node would + // display two sets of tabs. So instead, if there are no keywords and + // we're on the node tab, just redirect to the bare 'search' path. + drupal_goto('search'); } - $keys = search_get_keys(); // Only perform search if there is non-whitespace search term: $results = ''; if (trim($keys)) { diff --git a/modules/search/search.test b/modules/search/search.test index c2402360e..f129e03d5 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -1,5 +1,5 @@ <?php -// $Id: search.test,v 1.60 2010/04/26 14:26:46 dries Exp $ +// $Id: search.test,v 1.63 2010/05/12 15:53:43 dries Exp $ // The search index can contain different types of content. Typically the type is 'node'. // Here we test with _test_ and _test2_ as the type. @@ -413,6 +413,88 @@ class SearchRankingTestCase extends DrupalWebTestCase { } } + /** + * Test rankings of HTML tags. + */ + function testHTMLRankings() { + // Login with sufficient privileges. + $this->drupalLogin($this->drupalCreateUser(array('create page content'))); + + // Test HTML tags with different weights. + $sorted_tags = array('h1', 'h2', 'h3', 'h4', 'a', 'h5', 'h6', 'notag'); + $shuffled_tags = $sorted_tags; + + // Shuffle tags to ensure HTML tags are ranked properly. + shuffle($shuffled_tags); + $settings = array( + 'type' => 'page', + 'title' => array(LANGUAGE_NONE => array(array('value' => 'Simple node'))), + ); + foreach ($shuffled_tags as $tag) { + switch ($tag) { + case 'a': + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => l('Drupal Rocks', 'node'), 'format' => 3))); + break; + case 'notag': + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'Drupal Rocks'))); + break; + default: + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3))); + break; + } + $nodes[$tag] = $this->drupalCreateNode($settings); + } + + // Update the search index. + module_invoke_all('update_index'); + search_update_totals(); + + // Refresh variables after the treatment. + $this->refreshVariables(); + + // Disable all other rankings. + $node_ranks = array('sticky', 'promote', 'recent', 'comments', 'views'); + foreach ($node_ranks as $node_rank) { + variable_set('node_rank_' . $node_rank, 0); + } + $set = node_search_execute('rocks'); + + // Test the ranking of each tag. + foreach ($sorted_tags as $tag_rank => $tag) { + // Assert the results. + if ($tag == 'notag') { + $this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for plain text order.'); + } else { + $this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for "<' . $sorted_tags[$tag_rank] . '>" order.'); + } + } + + // Test tags with the same weight against the sorted tags. + $unsorted_tags = array('u', 'b', 'i', 'strong', 'em'); + foreach ($unsorted_tags as $tag) { + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3))); + $node = $this->drupalCreateNode($settings); + + // Update the search index. + module_invoke_all('update_index'); + search_update_totals(); + + // Refresh variables after the treatment. + $this->refreshVariables(); + + $set = node_search_execute('rocks'); + + // Ranking should always be second to last. + $set = array_slice($set, -2, 1); + + // Assert the results. + $this->assertEqual($set[0]['node']->nid, $node->nid, 'Search tag ranking for "<' . $tag . '>" order.'); + + // Delete node so it doesn't show up in subsequent search results. + node_delete($node->nid); + } + } + /** * Verifies that if we combine two rankings, search still works. * @@ -511,6 +593,66 @@ class SearchBlockTestCase extends DrupalWebTestCase { } } +/** + * Tests that searching for a phrase gets the correct page count. + */ +class SearchExactTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Search engine phrase queries', + 'description' => 'Tests that searching for a phrase gets the correct page count.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp('search'); + } + + /** + * Tests that the correct number of pager links are found for both keywords and phrases. + */ + function testExactQuery() { + // Login with sufficient privileges. + $this->drupalLogin($this->drupalCreateUser(array('create page content', 'search content'))); + + $settings = array( + 'type' => 'page', + 'title' => 'Simple Node', + ); + // Create nodes with exact phrase. + for ($i = 0; $i <= 17; $i++) { + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love pizza'))); + $this->drupalCreateNode($settings); + } + // Create nodes containing keywords. + for ($i = 0; $i <= 17; $i++) { + $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love cheesy pizza'))); + $this->drupalCreateNode($settings); + } + + // Update the search index. + module_invoke_all('update_index'); + search_update_totals(); + + // Refresh variables after the treatment. + $this->refreshVariables(); + + // Test that the correct number of pager links are found for keyword search. + $edit = array('keys' => 'love pizza'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertLinkByHref('page=1', 0, '2nd page link is found for keyword search.'); + $this->assertLinkByHref('page=2', 0, '3rd page link is found for keyword search.'); + $this->assertLinkByHref('page=3', 0, '4th page link is found for keyword search.'); + $this->assertNoLinkByHref('page=4', '5th page link is not found for keyword search.'); + + // Test that the correct number of pager links are found for exact phrase search. + $edit = array('keys' => '"love pizza"'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertLinkByHref('page=1', 0, '2nd page link is found for exact phrase search.'); + $this->assertNoLinkByHref('page=2', '3rd page link is not found for exact phrase search.'); + } +} /** * Test integration searching comments. @@ -790,3 +932,153 @@ class SearchConfigSettingsForm extends DrupalWebTestCase { } } +/** + * Test the CJK tokenizer. + */ +class SearchTokenizerTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'CJK tokenizer', + 'description' => 'Check that CJK tokenizer works as intended.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp('search'); + } + + /** + * Verifies that strings of CJK characters are tokenized. + * + * The search_simplify() function does special things with numbers, symbols, + * and punctuation. So we only test that CJK characters that are not in these + * character classes are tokenized properly. See PREG_CLASS_CKJ for more + * information. + */ + function testTokenizer() { + // Set the minimum word size to 1 (to split all CJK characters) and make + // sure CJK tokenizing is turned on. + variable_set('minimum_word_size', 1); + variable_set('overlap_cjk', TRUE); + $this->refreshVariables(); + + // Create a string of CJK characters from various character ranges in + // the Unicode tables. + + // Beginnings of the character ranges. + $starts = array( + 'CJK unified' => 0x4e00, + 'CJK Ext A' => 0x3400, + 'CJK Compat' => 0xf900, + 'Hangul Jamo' => 0x1100, + 'Hangul Ext A' => 0xa960, + 'Hangul Ext B' => 0xd7b0, + 'Hangul Compat' => 0x3131, + 'Half non-punct 1' => 0xff21, + 'Half non-punct 2' => 0xff41, + 'Half non-punct 3' => 0xff66, + 'Hangul Syllables' => 0xac00, + 'Hiragana' => 0x3040, + 'Katakana' => 0x30a1, + 'Katakana Ext' => 0x31f0, + 'CJK Reserve 1' => 0x20000, + 'CJK Reserve 2' => 0x30000, + 'Bomofo' => 0x3100, + 'Bomofo Ext' => 0x31a0, + 'Lisu' => 0xa4d0, + 'Yi' => 0xa000, + ); + + // Ends of the character ranges. + $ends = array( + 'CJK unified' => 0x9fcf, + 'CJK Ext A' => 0x4dbf, + 'CJK Compat' => 0xfaff, + 'Hangul Jamo' => 0x11ff, + 'Hangul Ext A' => 0xa97f, + 'Hangul Ext B' => 0xd7ff, + 'Hangul Compat' => 0x318e, + 'Half non-punct 1' => 0xff3a, + 'Half non-punct 2' => 0xff5a, + 'Half non-punct 3' => 0xffdc, + 'Hangul Syllables' => 0xd7af, + 'Hiragana' => 0x309f, + 'Katakana' => 0x30ff, + 'Katakana Ext' => 0x31ff, + 'CJK Reserve 1' => 0x2fffd, + 'CJK Reserve 2' => 0x3fffd, + 'Bomofo' => 0x312f, + 'Bomofo Ext' => 0x31b7, + 'Lisu' => 0xa4fd, + 'Yi' => 0xa48f, + ); + + // Generate characters consisting of starts, midpoints, and ends. + $chars = array(); + $charcodes = array(); + foreach ($starts as $key => $value) { + $charcodes[] = $starts[$key]; + $chars[] = $this->code2utf($starts[$key]); + $mid = round(0.5 * ($starts[$key] + $ends[$key])); + $charcodes[] = $mid; + $chars[] = $this->code2utf($mid); + $charcodes[] = $ends[$key]; + $chars[] = $this->code2utf($ends[$key]); + } + + // Merge into a string and tokenize. + $string = implode('', $chars); + $out = trim(search_simplify($string)); + $expected = drupal_strtolower(implode(' ', $chars)); + + // Verify that the output matches what we expect. + $this->assertEqual($out, $expected, 'CJK tokenizer worked on all supplied CJK characters'); + } + + /** + * Verifies that strings of non-CJK characters are not tokenized. + * + * This is just a sanity check - it verifies that strings of letters are + * not tokenized. + */ + function testNoTokenizer() { + // Set the minimum word size to 1 (to split all CJK characters) and make + // sure CJK tokenizing is turned on. + variable_set('minimum_word_size', 1); + variable_set('overlap_cjk', TRUE); + $this->refreshVariables(); + + $letters = 'abcdefghijklmnopqrstuvwxyz'; + $out = trim(search_simplify($letters)); + + $this->assertEqual($letters, $out, 'Letters are not CJK tokenized'); + } + + /** + * Like PHP chr() function, but for unicode characters. + * + * chr() only works for ASCII characters up to character 255. This function + * converts a number to the corresponding unicode character. Adapted from + * functions supplied in comments on several functions on php.net. + */ + function code2utf($num) { + if ($num < 128) { + return chr($num); + } + + if ($num < 2048) { + return chr(($num >> 6) + 192) . chr(($num & 63) + 128); + } + + if ($num < 65536) { + return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + } + + if ($num < 2097152) { + return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + } + + return ''; + } +} diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index ffe1d04e9..d93bd4182 100644 --- a/modules/shortcut/shortcut.info +++ b/modules/shortcut/shortcut.info @@ -10,8 +10,8 @@ files[] = shortcut.install files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 702eb1913..a781958e2 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1,5 +1,5 @@ <?php -// $Id: drupal_web_test_case.php,v 1.211 2010/04/23 05:46:01 webchick Exp $ +// $Id: drupal_web_test_case.php,v 1.217 2010/05/10 06:38:23 dries Exp $ /** * Base class for Drupal tests. @@ -837,11 +837,12 @@ class DrupalWebTestCase extends DrupalTestCase { 'locked' => 0, ); $type = $forced + $settings + $defaults; - $type = (object)$type; + $type = (object) $type; $saved_type = node_type_save($type); node_types_rebuild(); menu_rebuild(); + node_add_body_field($type); $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type))); @@ -1075,7 +1076,7 @@ class DrupalWebTestCase extends DrupalTestCase { */ protected function drupalGetToken($value = '') { $private_key = drupal_get_private_key(); - return md5($this->session_id . $value . $private_key); + return drupal_hmac_base64($value, $this->session_id . $private_key); } /* @@ -1158,6 +1159,11 @@ class DrupalWebTestCase extends DrupalTestCase { $this->preloadRegistry(); + // Set path variables. + variable_set('file_public_path', $public_files_directory); + variable_set('file_private_path', $private_files_directory); + variable_set('file_temporary_path', $temp_files_directory); + // Include the default profile. variable_set('install_profile', 'standard'); $profile_details = install_profile_info('standard', 'en'); @@ -1212,11 +1218,6 @@ class DrupalWebTestCase extends DrupalTestCase { unset($GLOBALS['conf']['language_default']); $language = language_default(); - // Set path variables - variable_set('file_public_path', $public_files_directory); - variable_set('file_private_path', $private_files_directory); - variable_set('file_temporary_path', $temp_files_directory); - // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'TestingMailSystem')); @@ -1359,15 +1360,20 @@ class DrupalWebTestCase extends DrupalTestCase { } /** - * Performs a cURL exec with the specified options after calling curlConnect(). + * Initializes and executes a cURL request. * * @param $curl_options - * Custom cURL options. + * An associative array of cURL options to set, where the keys are constants + * defined by the cURL library. For a list of valid options, see + * http://www.php.net/manual/function.curl-setopt.php * @param $redirect - * FALSE if this is an initial request, TRUE if this request is the result of - * a redirect. + * FALSE if this is an initial request, TRUE if this request is the result + * of a redirect. + * * @return - * Content returned from the exec. + * The content returned from the call to curl_exec(). + * + * @see curlInitialize() */ protected function curlExec($curl_options, $redirect = FALSE) { $this->curlInitialize(); @@ -1860,8 +1866,8 @@ class DrupalWebTestCase extends DrupalTestCase { $name = (string) $element['name']; // This can either be the type of <input> or the name of the tag itself // for <select> or <textarea>. - $type = isset($element['type']) ? (string)$element['type'] : $element->getName(); - $value = isset($element['value']) ? (string)$element['value'] : ''; + $type = isset($element['type']) ? (string) $element['type'] : $element->getName(); + $value = isset($element['value']) ? (string) $element['value'] : ''; $done = FALSE; if (isset($edit[$name])) { switch ($type) { @@ -1900,7 +1906,7 @@ class DrupalWebTestCase extends DrupalTestCase { $index = 0; $key = preg_replace('/\[\]$/', '', $name); foreach ($options as $option) { - $option_value = (string)$option['value']; + $option_value = (string) $option['value']; if (in_array($option_value, $new_value)) { $post[$key . '[' . $index++ . ']'] = $option_value; $done = TRUE; @@ -1936,7 +1942,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (!isset($post[$name]) && !$done) { switch ($type) { case 'textarea': - $post[$name] = (string)$element; + $post[$name] = (string) $element; break; case 'select': $single = empty($element['multiple']); @@ -1950,10 +1956,10 @@ class DrupalWebTestCase extends DrupalTestCase { if ($option['selected'] || ($first && $single)) { $first = FALSE; if ($single) { - $post[$name] = (string)$option['value']; + $post[$name] = (string) $option['value']; } else { - $post[$key . '[' . $index++ . ']'] = (string)$option['value']; + $post[$key . '[' . $index++ . ']'] = (string) $option['value']; } } } @@ -3003,7 +3009,7 @@ function simpletest_verbose($message, $original_file_directory = NULL, $test_cla if ($original_file_directory) { $file_directory = $original_file_directory; $class = $test_class; - $verbose = variable_get('simpletest_verbose', FALSE); + $verbose = variable_get('simpletest_verbose', TRUE); $directory = $file_directory . '/simpletest/verbose'; $writable = file_prepare_directory($directory, FILE_CREATE_DIRECTORY); if ($writable && !file_exists($directory . '/.htaccess')) { diff --git a/modules/simpletest/files/README.txt b/modules/simpletest/files/README.txt index 63ae5c310..ddfce559c 100644 --- a/modules/simpletest/files/README.txt +++ b/modules/simpletest/files/README.txt @@ -1,5 +1,5 @@ -$Id: README.txt,v 1.1 2008/04/20 18:23:30 dries Exp $ +$Id: README.txt,v 1.2 2010/04/28 20:25:21 dries Exp $ These files are use in some tests that upload files or other operations were a file is useful. These files are copied to the files directory as specified -in the site settings. Other tests files are generated in order to save space. \ No newline at end of file +in the site settings. Other tests files are generated in order to save space. diff --git a/modules/simpletest/files/javascript-1.txt b/modules/simpletest/files/javascript-1.txt index e0206ba83..efd44fd93 100644 --- a/modules/simpletest/files/javascript-1.txt +++ b/modules/simpletest/files/javascript-1.txt @@ -1,3 +1,3 @@ <script> alert('SimpleTest PHP was executed!'); -</script> \ No newline at end of file +</script> diff --git a/modules/simpletest/files/php-1.txt b/modules/simpletest/files/php-1.txt index dc8e64213..52788b6fe 100644 --- a/modules/simpletest/files/php-1.txt +++ b/modules/simpletest/files/php-1.txt @@ -1,3 +1,3 @@ <?php print 'SimpleTest PHP was executed!'; -?> \ No newline at end of file +?> diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index c2d617be2..c44669271 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -38,8 +38,8 @@ files[] = tests/unicode.test files[] = tests/update.test files[] = tests/xmlrpc.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/simpletest.pages.inc b/modules/simpletest/simpletest.pages.inc index 8e46e964f..2a0844a9e 100644 --- a/modules/simpletest/simpletest.pages.inc +++ b/modules/simpletest/simpletest.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: simpletest.pages.inc,v 1.28 2010/04/13 15:23:03 dries Exp $ +// $Id: simpletest.pages.inc,v 1.30 2010/05/19 19:22:24 dries Exp $ /** * @file @@ -71,10 +71,11 @@ function theme_simpletest_test_table($variables) { drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css'); drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js'); + drupal_add_js('misc/tableselect.js'); // Create header for test selection table. $header = array( - theme('table_select_header_cell'), + array('class' => array('select-all')), array('data' => t('Test'), 'class' => array('simpletest_test')), array('data' => t('Description'), 'class' => array('simpletest_description')), ); @@ -276,7 +277,7 @@ function simpletest_result_form($form, &$form_state, $test_id) { // Set summary information. $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0; - $form['result']['results'][$group]['#collapsed'] = $group_summary['#ok'] && !$group_summary['#debug']; + $form['result']['results'][$group]['#collapsed'] = $group_summary['#ok']; // Store test group (class) as for use in filter. $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group; @@ -444,7 +445,7 @@ function simpletest_settings_form($form, &$form_state) { '#type' => 'checkbox', '#title' => t('Provide verbose information when running tests'), '#description' => t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'), - '#default_value' => variable_get('simpletest_verbose', FALSE), + '#default_value' => variable_get('simpletest_verbose', TRUE), ); $form['httpauth'] = array( diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index e528595e9..6525ae391 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -1,5 +1,5 @@ <?php -// $Id: simpletest.test,v 1.40 2010/03/31 20:05:06 dries Exp $ +// $Id: simpletest.test,v 1.41 2010/05/10 06:38:23 dries Exp $ class SimpleTestFunctionalTest extends DrupalWebTestCase { /** @@ -415,6 +415,26 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { } } +/** + * Test Folder creation + */ +class SimpleTestFolderTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Testing SimpleTest setUp', + 'description' => "This test will check SimpleTest's treatment of hook_install during setUp. Image module is used for test.", + 'group' => 'SimpleTest', + ); + } + + function testFolderSetup() { + if (module_exists('image')) { + $path = file_directory_path() . '/styles'; + $this->assertTrue(file_prepare_directory($path, FALSE), "Directory created."); + } + } +} + /** * Test required modules for tests. */ diff --git a/modules/simpletest/tests/actions.test b/modules/simpletest/tests/actions.test index 3a098a894..1dee630e4 100644 --- a/modules/simpletest/tests/actions.test +++ b/modules/simpletest/tests/actions.test @@ -1,5 +1,5 @@ <?php -// $Id: actions.test,v 1.14 2010/03/26 17:14:45 dries Exp $ +// $Id: actions.test,v 1.15 2010/05/01 08:12:23 dries Exp $ class ActionsConfigurationTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -21,7 +21,7 @@ class ActionsConfigurationTestCase extends DrupalWebTestCase { // Make a POST request to admin/config/system/actions/manage. $edit = array(); - $edit['action'] = md5('system_goto_action'); + $edit['action'] = drupal_hash_base64('system_goto_action'); $this->drupalPost('admin/config/system/actions/manage', $edit, t('Create')); // Make a POST request to the individual action configuration page. @@ -29,7 +29,7 @@ class ActionsConfigurationTestCase extends DrupalWebTestCase { $action_label = $this->randomName(); $edit['actions_label'] = $action_label; $edit['url'] = 'admin'; - $this->drupalPost('admin/config/system/actions/configure/' . md5('system_goto_action'), $edit, t('Save')); + $this->drupalPost('admin/config/system/actions/configure/' . drupal_hash_base64('system_goto_action'), $edit, t('Save')); // Make sure that the new complex action was saved properly. $this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully saved the complex action.")); @@ -87,7 +87,7 @@ class ActionLoopTestCase extends DrupalWebTestCase { $user = $this->drupalCreateUser(array('administer actions')); $this->drupalLogin($user); - $hash = md5('actions_loop_test_log'); + $hash = drupal_hash_base64('actions_loop_test_log'); $edit = array('aid' => $hash); $this->drupalPost('admin/structure/trigger/actions_loop_test', $edit, t('Assign')); diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index a24f5132d..dbf7b16a9 100644 --- a/modules/simpletest/tests/actions_loop_test.info +++ b/modules/simpletest/tests/actions_loop_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = actions_loop_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test index 3fc93e387..2e10b80f1 100644 --- a/modules/simpletest/tests/ajax.test +++ b/modules/simpletest/tests/ajax.test @@ -1,5 +1,5 @@ <?php -// $Id: ajax.test,v 1.9 2010/04/11 18:33:44 dries Exp $ +// $Id: ajax.test,v 1.11 2010/04/30 08:07:55 dries Exp $ class AJAXTestCase extends DrupalWebTestCase { function setUp() { @@ -146,6 +146,11 @@ class AJAXCommandsTestCase extends AJAXTestCase { $command = $commands[0]; $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data"); + // Tests the 'insert' command. + $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method'].")))); + $command = $commands[0]; + $this->assertTrue($command['command'] == 'insert' && $command['method'] == NULL && $command['data'] == 'insert replacement text', "'insert' AJAX command issued with correct data"); + // Tests the 'prepend' command. $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something")))); $command = $commands[0]; @@ -268,9 +273,7 @@ class AJAXMultiFormTestCase extends AJAXTestCase { $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == 1, t('Found the correct number of field items on the initial page.')); $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button on the initial page.')); } - // @todo Legacy bug of duplicate ids for filter-guidelines-FORMAT. See - // http://drupal.org/node/755566. - $this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other', array('filter-guidelines-1', 'filter-guidelines-2', 'filter-guidelines-3')); + $this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other'); // Submit the "add more" button of each form twice. After each corresponding // page update, ensure the same as above. To successfully implement @@ -286,9 +289,7 @@ class AJAXMultiFormTestCase extends AJAXTestCase { $settings = array_merge_recursive($settings, $commands[0]['settings']); $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, t('Found the correct number of field items after an AJAX submission.')); $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button after an AJAX submission.')); - // @todo Legacy bug of duplicate ids for filter-guidelines-FORMAT. See - // http://drupal.org/node/755566. - $this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other', array('filter-guidelines-1', 'filter-guidelines-2', 'filter-guidelines-3')); + $this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other'); } } } diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index 0b357f5a2..9d20ddc0c 100644 --- a/modules/simpletest/tests/ajax_forms_test.info +++ b/modules/simpletest/tests/ajax_forms_test.info @@ -7,8 +7,8 @@ files[] = ajax_forms_test.module version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module index acd9226c2..3f7c765b7 100644 --- a/modules/simpletest/tests/ajax_forms_test.module +++ b/modules/simpletest/tests/ajax_forms_test.module @@ -1,5 +1,5 @@ <?php -// $Id: ajax_forms_test.module,v 1.5 2010/03/26 18:58:12 dries Exp $ +// $Id: ajax_forms_test.module,v 1.7 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -81,8 +81,8 @@ function ajax_forms_test_simple_form_select_callback($form, $form_state) { */ function ajax_forms_test_simple_form_checkbox_callback($form, $form_state) { $commands = array(); - $commands[] = ajax_command_html('#ajax_checkbox_value', (int)$form_state['values']['checkbox']); - $commands[] = ajax_command_data('#ajax_checkbox_value', 'form_state_value_select', (int)$form_state['values']['checkbox']); + $commands[] = ajax_command_html('#ajax_checkbox_value', (int) $form_state['values']['checkbox']); + $commands[] = ajax_command_data('#ajax_checkbox_value', 'form_state_value_select', (int) $form_state['values']['checkbox']); return array('#type' => 'ajax', '#commands' => $commands); } @@ -186,6 +186,17 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) { '#suffix' => '<div id="html_div">Original contents</div>', ); + // Shows the AJAX 'insert' command. + $form['insert_command_example'] = array( + '#value' => t("AJAX insert: Let client insert based on #ajax['method']."), + '#type' => 'submit', + '#ajax' => array( + 'callback' => 'ajax_forms_test_advanced_commands_insert_callback', + 'method' => 'prepend', + ), + '#suffix' => '<div id="insert_div">Original contents</div>', + ); + // Shows the AJAX 'prepend' command. $form['prepend_command_example'] = array( '#value' => t("AJAX 'prepend': Click to prepend something"), @@ -320,6 +331,15 @@ function ajax_forms_test_advanced_commands_html_callback($form, $form_state) { return array('#type' => 'ajax', '#commands' => $commands); } +/** + * AJAX callback for 'insert'. + */ +function ajax_forms_test_advanced_commands_insert_callback($form, $form_state) { + $commands = array(); + $commands[] = ajax_command_insert('#insert_div', 'insert replacement text'); + return array('#type' => 'ajax', '#commands' => $commands); +} + /** * AJAX callback for 'prepend'. */ diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index 98b94a798..042a198a8 100644 --- a/modules/simpletest/tests/ajax_test.info +++ b/modules/simpletest/tests/ajax_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = ajax_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/batch.test b/modules/simpletest/tests/batch.test index f9aa936c2..5c11f7269 100644 --- a/modules/simpletest/tests/batch.test +++ b/modules/simpletest/tests/batch.test @@ -1,5 +1,5 @@ <?php -// $Id: batch.test,v 1.8 2010/01/08 06:36:34 webchick Exp $ +// $Id: batch.test,v 1.9 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -352,7 +352,7 @@ class BatchPercentagesUnitTestCase extends DrupalUnitTestCase { foreach ($this->testCases as $expected_result => $arguments) { // PHP sometimes casts numeric strings that are array keys to integers, // cast them back here. - $expected_result = (string)$expected_result; + $expected_result = (string) $expected_result; $total = $arguments['total']; $current = $arguments['current']; $actual_result = _batch_api_percentage($total, $current); diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index 6e0a4adbd..aad88e7b3 100644 --- a/modules/simpletest/tests/batch_test.info +++ b/modules/simpletest/tests/batch_test.info @@ -8,8 +8,8 @@ files[] = batch_test.module files[] = batch_test.callbacks.inc hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test index 10c434c0d..9bbe1378b 100644 --- a/modules/simpletest/tests/bootstrap.test +++ b/modules/simpletest/tests/bootstrap.test @@ -1,5 +1,5 @@ <?php -// $Id: bootstrap.test,v 1.29 2010/03/17 13:58:45 dries Exp $ +// $Id: bootstrap.test,v 1.30 2010/05/12 08:26:15 dries Exp $ class BootstrapIPAddressTestCase extends DrupalWebTestCase { @@ -105,7 +105,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { * Test support for requests containing If-Modified-Since and If-None-Match headers. */ function testConditionalRequests() { - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); // Fill the cache. $this->drupalGet(''); @@ -143,7 +143,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { * Test cache headers. */ function testPageCache() { - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); // Fill the cache. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); @@ -187,7 +187,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { * mod_deflate Apache module. */ function testPageCompression() { - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); // Fill the cache and verify that output is compressed. $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate')); @@ -280,14 +280,14 @@ class HookBootExitTestCase extends DrupalWebTestCase { */ function testHookBootExit() { // Test with cache disabled. Boot and exit should always fire. - variable_set('cache', CACHE_DISABLED); + variable_set('cache', 0); $this->drupalGet(''); $calls = 1; $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with disabled cache.')); $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with disabled cache.')); // Test with normal cache. Boot and exit should be called. - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); $this->drupalGet(''); $calls++; $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with normal cache.')); diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 8c2775673..3e8d9d695 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -1,5 +1,5 @@ <?php -// $Id: common.test,v 1.110 2010/04/22 21:41:09 webchick Exp $ +// $Id: common.test,v 1.112 2010/05/18 06:59:46 dries Exp $ /** * @file @@ -86,6 +86,28 @@ class CommonURLUnitTest extends DrupalWebTestCase { $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path))); } + /* + * Tests for active class in l() function. + */ + function testLActiveClass() { + $link = l($this->randomName(), $_GET['q']); + $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); + } + + /** + * Tests for custom class in l() function. + */ + function testLCustomClass() { + $class = $this->randomName(); + $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); + $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class))); + $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); + } + + private function hasClass($link, $class) { + return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link); + } + /** * Test drupal_get_query_parameters(). */ @@ -907,19 +929,6 @@ class DrupalHTTPRequestTestCase extends DrupalWebTestCase { $redirect_307 = drupal_http_request(url('system-test/redirect/307', array('absolute' => TRUE)), array('max_redirects' => 0)); $this->assertFalse(isset($redirect_307->redirect_code), t('drupal_http_request does not follow 307 redirect if max_redirects = 0.')); } - - function testDrupalGetDestination() { - $query = $this->randomName(10); - - // Verify that a 'destination' query string is used as destination. - $this->drupalGet('system-test/destination', array('query' => array('destination' => $query))); - $this->assertText('The destination: ' . $query, t('The given query string destination is determined as destination.')); - - // Verify that the current path is used as destination. - $this->drupalGet('system-test/destination', array('query' => array($query => NULL))); - $url = 'system-test/destination?' . $query; - $this->assertText('The destination: ' . $url, t('The current path is determined as destination.')); - } } /** @@ -985,17 +994,33 @@ class DrupalGotoTest extends DrupalWebTestCase { } /** - * Test setting and retrieving content for theme regions. + * Test drupal_goto(). */ function testDrupalGoto() { $this->drupalGet('common-test/drupal_goto/redirect'); + $headers = $this->drupalGetHeaders(TRUE); + list(, $status) = explode(' ', $headers[0][':status'], 3); + $this->assertEqual($status, 302, t('Expected response code was sent.')); + $this->assertText('drupal_goto', t('Drupal goto redirect succeeded.')); + $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('absolute' => TRUE)), t('Drupal goto redirected to expected URL.')); - $this->assertNoText(t("Drupal goto failed to stop program"), t("Drupal goto stopped program.")); - $this->assertText('drupal_goto', t("Drupal goto redirect failed.")); + $this->drupalGet('common-test/drupal_goto/redirect_advanced'); + $headers = $this->drupalGetHeaders(TRUE); + list(, $status) = explode(' ', $headers[0][':status'], 3); + $this->assertEqual($status, 301, t('Expected response code was sent.')); + $this->assertText('drupal_goto', t('Drupal goto redirect succeeded.')); + $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), t('Drupal goto redirected to expected URL.')); + + // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL + // to test that the path is encoded and decoded properly. + $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; + $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination))); + $this->assertText('drupal_goto', t('Drupal goto redirect with destination succeeded.')); + $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), t('Drupal goto redirected to given query string destination. ')); } /** - * Test setting and retrieving content for theme regions. + * Test hook_drupal_goto_alter(). */ function testDrupalGotoAlter() { $this->drupalGet('common-test/drupal_goto/redirect_fail'); @@ -1003,6 +1028,22 @@ class DrupalGotoTest extends DrupalWebTestCase { $this->assertNoText(t("Drupal goto failed to stop program"), t("Drupal goto stopped program.")); $this->assertNoText('drupal_goto_fail', t("Drupal goto redirect failed.")); } + + /** + * Test drupal_get_destination(). + */ + function testDrupalGetDestination() { + $query = $this->randomName(10); + + // Verify that a 'destination' query string is used as destination. + $this->drupalGet('common-test/destination', array('query' => array('destination' => $query))); + $this->assertText('The destination: ' . $query, t('The given query string destination is determined as destination.')); + + // Verify that the current path is used as destination. + $this->drupalGet('common-test/destination', array('query' => array($query => NULL))); + $url = 'common-test/destination?' . $query; + $this->assertText('The destination: ' . $url, t('The current path is determined as destination.')); + } } /** diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index 987cd5af7..0249090e8 100644 --- a/modules/simpletest/tests/common_test.info +++ b/modules/simpletest/tests/common_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = common_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index de2e32b61..4dc29b1c8 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -1,5 +1,5 @@ <?php -// $Id: common_test.module,v 1.10 2010/03/02 08:47:41 dries Exp $ +// $Id: common_test.module,v 1.11 2010/05/18 06:59:46 dries Exp $ /** * @file @@ -28,6 +28,12 @@ function common_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['common-test/drupal_goto/redirect_advanced'] = array( + 'title' => 'Drupal Goto', + 'page callback' => 'common_test_drupal_goto_redirect_advanced', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['common-test/drupal_goto/redirect_fail'] = array( 'title' => 'Drupal Goto Failure', 'page callback' => 'drupal_goto', @@ -35,6 +41,12 @@ function common_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['common-test/destination'] = array( + 'title' => 'Drupal Get Destination', + 'page callback' => 'common_test_destination', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['common-test/query-string'] = array( 'title' => 'Test querystring', 'page callback' => 'common_test_js_and_css_querystring', @@ -45,11 +57,17 @@ function common_test_menu() { } /** - * Check that drupal_goto() exits once called. + * Redirect using drupal_goto(). */ function common_test_drupal_goto_redirect() { drupal_goto('common-test/drupal_goto'); - print t("Drupal goto failed to stop program"); +} + +/** + * Redirect using drupal_goto(). + */ +function common_test_drupal_goto_redirect_advanced() { + drupal_goto('common-test/drupal_goto', array('query' => array('foo' => '123')), 301); } /** @@ -75,6 +93,14 @@ function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code) } } +/** + * Print destination query parameter. + */ +function common_test_destination() { + $destination = drupal_get_destination(); + print "The destination: " . check_plain($destination['destination']); +} + /** * Implements hook_TYPE_alter(). */ diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index ca0f2275d..ef98079f3 100644 --- a/modules/simpletest/tests/database_test.info +++ b/modules/simpletest/tests/database_test.info @@ -8,8 +8,8 @@ files[] = database_test.install version = VERSION hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 018e7fec8..492f3b729 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -1,5 +1,5 @@ <?php -// $Id: database_test.test,v 1.87 2010/04/11 18:33:44 dries Exp $ +// $Id: database_test.test,v 1.92 2010/05/15 07:04:21 dries Exp $ /** * Dummy class for fetching into a class. @@ -456,7 +456,7 @@ 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, t('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.')); } @@ -1015,7 +1015,6 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { $this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.')); } - /** * Confirm that we can truncate a whole table successfully. */ @@ -1274,6 +1273,27 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); } + /** + * Test rudimentary SELECT statement with a COMMENT. + */ + function testSimpleComment() { + $query = db_select('test')->comment('Testing query comments'); + $name_field = $query->addField('test', 'name'); + $age_field = $query->addField('test', 'age', 'age'); + $result = $query->execute(); + + $num_records = 0; + foreach ($result as $record) { + $num_records++; + } + + $query = (string)$query; + $expected = "/* Testing query comments */ SELECT 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.')); + } + /** * Test basic conditionals on SELECT statements. */ @@ -1835,7 +1855,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $task_field = $query->addField('t', 'task'); $query->orderBy($count_field); $query->groupBy($task_field); - $query->havingCondition('COUNT(task)', 2, '>='); + $query->having('COUNT(task) >= 2'); $result = $query->execute(); $num_records = 0; @@ -1972,6 +1992,20 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $job = $query->execute()->fetchField(); $this->assertEqual($job, 'Songwriter', t('Correct data retrieved.')); } + + /** + * Confirm we can join on a single table twice with a dynamic alias. + */ + function testJoinTwice() { + $query = db_select('test')->fields('test'); + $alias = $query->join('test', 'test', 'test.job = %alias.job'); + $query->addField($alias, 'name', 'othername'); + $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.')); + } } class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { @@ -2326,22 +2360,6 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $this->assertEqual($records[0]->$pid_field, 1, t('Correct data retrieved.')); $this->assertEqual($records[0]->$task_field, 'sleep', t('Correct data retrieved.')); } -} - -/** - * Select alter tests, part 2. - * - * @see database_test_query_alter() - */ -class DatabaseAlter2TestCase extends DatabaseTestCase { - - public static function getInfo() { - return array( - 'name' => 'Query altering tests, part 2', - 'description' => 'Test the hook_query_alter capabilities of the Select builder.', - 'group' => 'Database', - ); - } /** * Test that we can alter the fields of a query. @@ -2390,6 +2408,31 @@ class DatabaseAlter2TestCase extends DatabaseTestCase { $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); } + + /** + * Test that we can do basic alters on subqueries. + */ + function testSimpleAlterSubquery() { + // Create a sub-query with an alter tag. + $subquery = db_select('test', 'p'); + $subquery->addField('p', 'name'); + $subquery->addField('p', 'id'); + // Pick out George. + $subquery->condition('age', 27); + $subquery->addExpression("age*2", 'double_age'); + // This query alter should change it to age * 3. + $subquery->addTag('database_test_alter_change_expressions'); + + // Create a main query and join to sub-query. + $query = db_select('test_task', 'tt'); + $query->join($subquery, 'pq', 'pq.id = tt.pid'); + $age_field = $query->addField('pq', 'double_age'); + $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.')); + } } /** @@ -3227,5 +3270,4 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { $this->assertEqual($result->fetchAll(), array(), t('Empty array returned from empty result set.')); } - } diff --git a/modules/simpletest/tests/entity_cache_test.info b/modules/simpletest/tests/entity_cache_test.info index c0b8b31c5..d0f2be50c 100644 --- a/modules/simpletest/tests/entity_cache_test.info +++ b/modules/simpletest/tests/entity_cache_test.info @@ -8,8 +8,8 @@ files[] = entity_cache_test.module dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/entity_cache_test_dependency.info b/modules/simpletest/tests/entity_cache_test_dependency.info index 0888277d5..3300857f6 100644 --- a/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/modules/simpletest/tests/entity_cache_test_dependency.info @@ -7,8 +7,8 @@ core = 7.x files[] = entity_cache_test_dependency.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/error_test.info b/modules/simpletest/tests/error_test.info index 324a296e1..438f3b655 100644 --- a/modules/simpletest/tests/error_test.info +++ b/modules/simpletest/tests/error_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = error_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 971aa2522..dd4270b9b 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -1,5 +1,5 @@ <?php -// $Id: file.test,v 1.53 2010/04/11 18:33:44 dries Exp $ +// $Id: file.test,v 1.54 2010/05/11 10:56:04 dries Exp $ /** * @file @@ -2022,7 +2022,6 @@ class FileURLRewritingTest extends FileTestCase { function setUp() { parent::setUp('file_test'); - variable_set('file_test_hook_file_url_alter', TRUE); } /** @@ -2031,12 +2030,33 @@ class FileURLRewritingTest extends FileTestCase { function testShippedFileURL() { // Test generating an URL to a shipped file (i.e. a file that is part of // Drupal core, a module or a theme, for example a JavaScript file). + + // Test alteration of file URLs to use a CDN. + variable_set('file_test_hook_file_url_alter', 'cdn'); + $filepath = 'misc/jquery.js'; + $url = file_create_url($filepath); + $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.')); + $filepath = 'misc/favicon.ico'; + $url = file_create_url($filepath); + $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.')); + + // Test alteration of file URLs to use root-relative URLs. + variable_set('file_test_hook_file_url_alter', 'root-relative'); + $filepath = 'misc/jquery.js'; + $url = file_create_url($filepath); + $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.')); + $filepath = 'misc/favicon.ico'; + $url = file_create_url($filepath); + $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.')); + + // Test alteration of file URLs to use protocol-relative URLs. + variable_set('file_test_hook_file_url_alter', 'protocol-relative'); $filepath = 'misc/jquery.js'; $url = file_create_url($filepath); - $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.')); + $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.')); $filepath = 'misc/favicon.ico'; $url = file_create_url($filepath); - $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.')); + $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.')); } /** @@ -2044,9 +2064,24 @@ class FileURLRewritingTest extends FileTestCase { */ function testPublicCreatedFileURL() { // Test generating an URL to a created file. + + // Test alteration of file URLs to use a CDN. + variable_set('file_test_hook_file_url_alter', 'cdn'); + $file = $this->createFile(); + $url = file_create_url($file->uri); + $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a CDN URL for a created file.')); + + // Test alteration of file URLs to use root-relative URLs. + variable_set('file_test_hook_file_url_alter', 'root-relative'); + $file = $this->createFile(); + $url = file_create_url($file->uri); + $this->assertEqual(base_path() . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a root-relative URL for a created file.')); + + // Test alteration of file URLs to use a protocol-relative URLs. + variable_set('file_test_hook_file_url_alter', 'protocol-relative'); $file = $this->createFile(); $url = file_create_url($file->uri); - $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.')); + $this->assertEqual('/' . base_path() . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a protocol-relative URL for a created file.')); } } diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info index 53fa3ce89..1280ef9e2 100644 --- a/modules/simpletest/tests/file_test.info +++ b/modules/simpletest/tests/file_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/file_test.module b/modules/simpletest/tests/file_test.module index 8c531f2d0..d7624e55e 100644 --- a/modules/simpletest/tests/file_test.module +++ b/modules/simpletest/tests/file_test.module @@ -1,5 +1,5 @@ <?php -// $Id: file_test.module,v 1.21 2010/03/26 17:14:45 dries Exp $ +// $Id: file_test.module,v 1.22 2010/05/11 10:56:04 dries Exp $ /** * @file @@ -286,41 +286,90 @@ function file_test_file_delete($file) { function file_test_file_url_alter(&$uri) { // Only run this hook when this variable is set. Otherwise, we'd have to add // another hidden test module just for this hook. - if (!variable_get('file_test_hook_file_url_alter', FALSE)) { + $alter_mode = variable_get('file_test_hook_file_url_alter', FALSE); + if (!$alter_mode) { return; } - - $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png'); - - // Most CDNs don't support private file transfers without a lot of hassle, - // so don't support this in the common case. - $schemes = array('public'); - - $scheme = file_uri_scheme($uri); - - // Only serve shipped files and public created files from the CDN. - if (!$scheme || in_array($scheme, $schemes)) { - // Shipped files. - if (!$scheme) { - $path = $uri; - } - // Public created files. - else { - $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); - $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); + // Test alteration of file URLs to use a CDN. + elseif ($alter_mode == 'cdn') { + $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png'); + + // Most CDNs don't support private file transfers without a lot of hassle, + // so don't support this in the common case. + $schemes = array('public'); + + $scheme = file_uri_scheme($uri); + + // Only serve shipped files and public created files from the CDN. + if (!$scheme || in_array($scheme, $schemes)) { + // Shipped files. + if (!$scheme) { + $path = $uri; + } + // Public created files. + else { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); + } + + // Clean up Windows paths. + $path = str_replace('\\', '/', $path); + + // Serve files with one of the CDN extensions from CDN 1, all others from + // CDN 2. + $pathinfo = pathinfo($path); + if (array_key_exists('extension', $pathinfo) && in_array($pathinfo['extension'], $cdn_extensions)) { + $uri = FILE_URL_TEST_CDN_1 . '/' . $path; + } + else { + $uri = FILE_URL_TEST_CDN_2 . '/' . $path; + } } - - // Clean up Windows paths. - $path = str_replace('\\', '/', $path); - - // Serve files with one of the CDN extensions from CDN 1, all others from - // CDN 2. - $pathinfo = pathinfo($path); - if (array_key_exists('extension', $pathinfo) && in_array($pathinfo['extension'], $cdn_extensions)) { - $uri = FILE_URL_TEST_CDN_1 . '/' . $path; + } + // Test alteration of file URLs to use root-relative URLs. + elseif ($alter_mode == 'root-relative') { + // Only serve shipped files and public created files with root-relative + // URLs. + $scheme = file_uri_scheme($uri); + if (!$scheme || $scheme == 'public') { + // Shipped files. + if (!$scheme) { + $path = $uri; + } + // Public created files. + else { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); + } + + // Clean up Windows paths. + $path = str_replace('\\', '/', $path); + + // Generate a root-relative URL. + $uri = base_path() . '/' . $path; } - else { - $uri = FILE_URL_TEST_CDN_2 . '/' . $path; + } + // Test alteration of file URLs to use protocol-relative URLs. + elseif ($alter_mode == 'protocol-relative') { + // Only serve shipped files and public created files with protocol-relative + // URLs. + $scheme = file_uri_scheme($uri); + if (!$scheme || $scheme == 'public') { + // Shipped files. + if (!$scheme) { + $path = $uri; + } + // Public created files. + else { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); + } + + // Clean up Windows paths. + $path = str_replace('\\', '/', $path); + + // Generate a protocol-relative URL. + $uri = '/' . base_path() . '/' . $path; } } } diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info index 965c1eb81..921282e86 100644 --- a/modules/simpletest/tests/filter_test.info +++ b/modules/simpletest/tests/filter_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = filter_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index cf4765fdb..b413f19c9 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -1,5 +1,5 @@ <?php -// $Id: form.test,v 1.48 2010/04/11 19:00:27 dries Exp $ +// $Id: form.test,v 1.50 2010/04/28 16:11:22 dries Exp $ /** * @file @@ -209,6 +209,40 @@ class FormsTestCase extends DrupalWebTestCase { } } +/** + * Test form alter hooks. + */ +class FormAlterTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Form alter hooks', + 'description' => 'Tests hook_form_alter() and hook_form_FORM_ID_alter().', + 'group' => 'Form API', + ); + } + + function setUp() { + parent::setUp('form_test'); + } + + /** + * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter(). + */ + function testExecutionOrder() { + $this->drupalGet('form-test/alter'); + // Ensure that the order is first by module, then for a given module, the + // id-specific one after the generic one. + $expected = array( + 'block_form_form_test_alter_form_alter() executed.', + 'form_test_form_alter() executed.', + 'form_test_form_form_test_alter_form_alter() executed.', + 'system_form_form_test_alter_form_alter() executed.', + ); + $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array())); + $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, t('Form alter hooks executed in the expected order.')); + } +} + /** * Test form validation handlers. */ @@ -341,6 +375,20 @@ class FormsElementsLabelsTestCase extends DrupalWebTestCase { $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-no-show"]'); $this->assertFalse(isset($elements[0]), t("No label tag when title set not to display.")); + + // Check #field_prefix and #field_suffix placement. + $elements = $this->xpath('//span[@class="field-prefix"]/following-sibling::div[@id="edit-form-radios-test"]'); + $this->assertTrue(isset($elements[0]), t("Properly placed the #field_prefix element after the label and before the field.")); + + $elements = $this->xpath('//span[@class="field-suffix"]/preceding-sibling::div[@id="edit-form-radios-test"]'); + $this->assertTrue(isset($elements[0]), t("Properly places the #field_suffix element immediately after the form field.")); + + // Check #prefix and #suffix placement. + $elements = $this->xpath('//div[@id="form-test-textfield-title-prefix"]/following-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]'); + $this->assertTrue(isset($elements[0]), t("Properly places the #prefix element before the form item.")); + + $elements = $this->xpath('//div[@id="form-test-textfield-title-suffix"]/preceding-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]'); + $this->assertTrue(isset($elements[0]), t("Properly places the #suffix element before the form item.")); } } diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index 3c81f8f8d..4a88f7657 100644 --- a/modules/simpletest/tests/form_test.info +++ b/modules/simpletest/tests/form_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = form_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 9f8094c4a..354d436c0 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -1,5 +1,5 @@ <?php -// $Id: form_test.module,v 1.37 2010/04/11 19:00:27 dries Exp $ +// $Id: form_test.module,v 1.39 2010/04/28 16:11:22 dries Exp $ /** * @file @@ -10,6 +10,13 @@ * Implements hook_menu(). */ function form_test_menu() { + $items['form-test/alter'] = array( + 'title' => 'Form altering test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_alter_form'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['form-test/validate'] = array( 'title' => 'Form validation handlers test', 'page callback' => 'drupal_get_form', @@ -141,6 +148,46 @@ function form_test_menu() { return $items; } +/** + * Form builder for testing hook_form_alter() and hook_form_FORM_ID_alter(). + */ +function form_test_alter_form($form, &$form_state) { + // Elements can be added as needed for future testing needs, but for now, + // we're only testing alter hooks that do not require any elements added by + // this function. + return $form; +} + +/** + * Implements hook_form_FORM_ID_alter() on behalf of block.module. + */ +function block_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message('block_form_form_test_alter_form_alter() executed.'); +} + +/** + * Implements hook_form_alter(). + */ +function form_test_form_alter(&$form, &$form_state, $form_id) { + if ($form_id == 'form_test_alter_form') { + drupal_set_message('form_test_form_alter() executed.'); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function form_test_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message('form_test_form_form_test_alter_form_alter() executed.'); +} + +/** + * Implements hook_form_FORM_ID_alter() on behalf of system.module. + */ +function system_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message('system_form_form_test_alter_form_alter() executed.'); +} + /** * Form builder for testing drupal_validate_form(). * @@ -529,6 +576,9 @@ function form_label_test_form() { 'second-radio' => t('Second radio'), 'third-radio' => t('Third radio'), ), + // Test #field_prefix and #field_suffix placement. + '#field_prefix' => '<span id="form-test-radios-field-prefix">' . t('Radios #field_prefix element') . '</span>', + '#field_suffix' => '<span id="form-test-radios-field-suffix">' . t('Radios #field_suffix element') . '</span>', ); $form['form_checkbox_test'] = array( '#type' => 'checkbox', @@ -550,6 +600,9 @@ function form_label_test_form() { '#type' => 'textfield', '#title' => t('Textfield test for title only'), // Not required. + // Test #prefix and #suffix placement. + '#prefix' => '<div id="form-test-textfield-title-prefix">' . t('Textfield #prefix element') . '</div>', + '#suffix' => '<div id="form-test-textfield-title-suffix">' . t('Textfield #suffix element') . '</div>', ); $form['form_textfield_test_title_after'] = array( '#type' => 'textfield', diff --git a/modules/simpletest/tests/graph.test b/modules/simpletest/tests/graph.test index ce92ca985..1751fe2ca 100644 --- a/modules/simpletest/tests/graph.test +++ b/modules/simpletest/tests/graph.test @@ -1,5 +1,5 @@ <?php -// $Id: graph.test,v 1.6 2009/07/13 21:51:41 webchick Exp $ +// $Id: graph.test,v 1.7 2010/05/18 18:11:13 dries Exp $ /** * @file @@ -23,7 +23,7 @@ class GraphUnitTest extends DrupalUnitTestCase { */ function testDepthFirstSearch() { // Provoke the inclusion of graph.inc. - require_once 'includes/graph.inc'; + require_once DRUPAL_ROOT . '/includes/graph.inc'; // The sample graph used is: // 1 --> 2 --> 3 5 ---> 6 diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index 99aeaff51..4fe62cb55 100644 --- a/modules/simpletest/tests/image_test.info +++ b/modules/simpletest/tests/image_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = image_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index 9a39315f8..967193987 100644 --- a/modules/simpletest/tests/menu_test.info +++ b/modules/simpletest/tests/menu_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = menu_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/module_test.info b/modules/simpletest/tests/module_test.info index 482d10f07..1572971fc 100644 --- a/modules/simpletest/tests/module_test.info +++ b/modules/simpletest/tests/module_test.info @@ -8,8 +8,8 @@ files[] = module_test.module files[] = module_test.file.inc hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/registry.test b/modules/simpletest/tests/registry.test index f77db6d3d..e82276897 100644 --- a/modules/simpletest/tests/registry.test +++ b/modules/simpletest/tests/registry.test @@ -1,5 +1,5 @@ <?php -// $Id: registry.test,v 1.16 2009/08/24 00:14:21 webchick Exp $ +// $Id: registry.test,v 1.17 2010/05/01 08:12:23 dries Exp $ class RegistryParseFileTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -11,9 +11,10 @@ class RegistryParseFileTestCase extends DrupalWebTestCase { } function setUp() { - $this->fileName = 'registry_test_' . md5(rand()); - $this->className = 'registry_test_class' . md5(rand()); - $this->interfaceName = 'registry_test_interface' . md5(rand()); + $chrs = hash('sha256', microtime() . mt_rand()); + $this->fileName = 'registry_test_' . substr($chrs, 0, 16); + $this->className = 'registry_test_class' . substr($chrs, 16, 16); + $this->interfaceName = 'registry_test_interface' . substr($chrs, 32, 16); parent::setUp(); } @@ -61,18 +62,19 @@ class RegistryParseFilesTestCase extends DrupalWebTestCase { // Create files with some php to parse - one 'new', one 'existing' so // we test all the important code paths in _registry_parse_files. foreach ($this->fileTypes as $fileType) { + $chrs = hash('sha256', microtime() . mt_rand()); $this->$fileType = new stdClass(); - $this->$fileType->fileName = file_directory_path() . '/registry_test_' . md5(rand()); - $this->$fileType->className = 'registry_test_class' . md5(rand()); - $this->$fileType->interfaceName = 'registry_test_interface' . md5(rand()); + $this->$fileType->fileName = file_directory_path() . '/registry_test_' . substr($chrs, 0, 16); + $this->$fileType->className = 'registry_test_class' . substr($chrs, 16, 16); + $this->$fileType->interfaceName = 'registry_test_interface' . substr($chrs, 32, 16); $this->$fileType->contents = $this->getFileContents($fileType); file_save_data($this->$fileType->contents, $this->$fileType->fileName); if ($fileType == 'existing_changed') { db_insert('registry_file') ->fields(array( - 'filectime' => rand(1, 1000000), - 'filemtime' => rand(1, 1000000), + 'filectime' => mt_rand(1, 1000000), + 'filemtime' => mt_rand(1, 1000000), 'filename' => $this->$fileType->fileName, )) ->execute(); @@ -81,7 +83,7 @@ class RegistryParseFilesTestCase extends DrupalWebTestCase { foreach (array('class', 'interface') as $type) { db_insert('registry') ->fields(array( - 'name' => $type . md5(rand()), + 'name' => $type . hash('sha256', microtime() . mt_rand()), 'type' => $type, 'filename' => $this->$fileType->fileName, )) @@ -117,8 +119,8 @@ class RegistryParseFilesTestCase extends DrupalWebTestCase { foreach ($this->fileTypes as $fileType) { $files[$this->$fileType->fileName] = array('module' => '', 'weight' => 0); if ($fileType == 'existing_changed') { - $files[$this->$fileType->fileName]['filectime'] = rand(1, 1000000); - $files[$this->$fileType->fileName]['filemtime'] = rand(1, 1000000); + $files[$this->$fileType->fileName]['filectime'] = mt_rand(1, 1000000); + $files[$this->$fileType->fileName]['filemtime'] = mt_rand(1, 1000000); } } return $files; diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index 74ffab944..b1aa396fb 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -1,5 +1,5 @@ <?php -// $Id: session.test,v 1.27 2010/02/17 08:56:48 dries Exp $ +// $Id: session.test,v 1.28 2010/05/12 08:26:15 dries Exp $ /** * @file @@ -140,7 +140,7 @@ class SessionTestCase extends DrupalWebTestCase { $this->assertSessionEmpty(TRUE); // The same behavior is expected when caching is enabled. - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); $this->drupalGet(''); $this->assertSessionCookie(FALSE); $this->assertSessionEmpty(TRUE); diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info index d095ad616..5ddbc7597 100644 --- a/modules/simpletest/tests/session_test.info +++ b/modules/simpletest/tests/session_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = session_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info index abbb29d6b..0fa290436 100644 --- a/modules/simpletest/tests/system_dependencies_test.info +++ b/modules/simpletest/tests/system_dependencies_test.info @@ -8,8 +8,8 @@ files[] = system_dependencies_test.module hidden = TRUE dependencies[] = _missing_dependency -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/system_test.info b/modules/simpletest/tests/system_test.info index a48c86a32..32f3e6ae2 100644 --- a/modules/simpletest/tests/system_test.info +++ b/modules/simpletest/tests/system_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module index df17b73dc..613b57a54 100644 --- a/modules/simpletest/tests/system_test.module +++ b/modules/simpletest/tests/system_test.module @@ -1,5 +1,5 @@ <?php -// $Id: system_test.module,v 1.25 2010/02/17 22:44:52 webchick Exp $ +// $Id: system_test.module,v 1.28 2010/05/20 08:51:24 dries Exp $ /** * Implements hook_menu(). @@ -43,13 +43,6 @@ function system_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['system-test/destination'] = array( - 'title' => 'Redirect', - 'page callback' => 'system_test_destination', - 'page arguments' => array(2), - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); $items['system-test/variable-get'] = array( 'title' => 'Variable Get', @@ -115,9 +108,10 @@ function system_test_basic_auth_page() { } function system_test_redirect($code) { - $code = (int)$code; + $code = (int) $code; if ($code != 200) { - header("Location: " . url('system-test/redirect/200', array('absolute' => TRUE)), TRUE, $code); + // Header names are case-insensitive. + header("locaTION: " . url('system-test/redirect/200', array('absolute' => TRUE)), TRUE, $code); exit; } return ''; @@ -143,11 +137,6 @@ function system_test_redirect_invalid_scheme() { exit; } -function system_test_destination() { - $destination = drupal_get_destination(); - return 'The destination: ' . $destination['destination']; -} - /** * Implements hook_modules_installed(). */ diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index 4a252be84..2ecdb6b80 100644 --- a/modules/simpletest/tests/taxonomy_test.info +++ b/modules/simpletest/tests/taxonomy_test.info @@ -8,8 +8,8 @@ files[] = taxonomy_test.module hidden = TRUE dependencies[] = taxonomy -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index f69c2af41..2d27c260b 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -1,5 +1,5 @@ <?php -// $Id: theme.test,v 1.15 2010/04/22 22:33:29 dries Exp $ +// $Id: theme.test,v 1.16 2010/04/29 05:22:06 webchick Exp $ /** * @file @@ -44,7 +44,7 @@ class ThemeUnitTest extends DrupalWebTestCase { $suggestions = theme_get_suggestions($args, 'page'); $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), t('Removed invalid \\0 from suggestions')); } - + /** * Preprocess functions for the base hook should run even for suggestion implementations. */ @@ -155,3 +155,30 @@ class ThemeItemListUnitTest extends DrupalWebTestCase { $this->assertIdentical($expected, $output, 'Nested list is rendered correctly.'); } } + +/** + * Functional test for initialization of the theme system in hook_init(). + */ +class ThemeHookInitUnitTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Theme initialization in hook_init()', + 'description' => 'Tests that the theme system can be correctly initialized in hook_init().', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + variable_set('theme_default', 'garland'); + } + + /** + * Test that the theme system can generate output when called by hook_init(). + */ + function testThemeInitializationHookInit() { + $this->drupalGet('theme-test/hook-init'); + $this->assertRaw('Themed output generated in hook_init()', t('Themed output generated in hook_init() correctly appears on the page.')); + $this->assertRaw('garland/style.css', t("The default theme's CSS appears on the page when the theme system is initialized in hook_init().")); + } +} diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index b08461b67..da7a96bca 100644 --- a/modules/simpletest/tests/theme_test.info +++ b/modules/simpletest/tests/theme_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = theme_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module index 6265cbbd4..6815a1cfd 100644 --- a/modules/simpletest/tests/theme_test.module +++ b/modules/simpletest/tests/theme_test.module @@ -1,5 +1,5 @@ <?php -// $Id: theme_test.module,v 1.1 2010/03/21 04:05:24 webchick Exp $ +// $Id: theme_test.module,v 1.2 2010/04/29 05:22:06 webchick Exp $ /** * Implements hook_menu(). @@ -12,10 +12,36 @@ function theme_test_menu() { 'theme callback' => '_theme_custom_theme', 'type' => MENU_CALLBACK, ); - + $items['theme-test/hook-init'] = array( + 'page callback' => 'theme_test_hook_init_page_callback', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); return $items; } +/** + * Implements hook_init(). + */ +function theme_test_init() { + // First, force the theme registry to be rebuilt on this page request. This + // allows us to test a full initialization of the theme system in the code + // below. + drupal_theme_rebuild(); + // Next, initialize the theme system by storing themed text in a global + // variable. We will use this later in theme_test_hook_init_page_callback() + // to test that even when the theme system is initialized this early, it is + // still capable of returning output and theming the page as a whole. + $GLOBALS['theme_test_output'] = theme('more_link', array('url' => url('user'), 'title' => 'Themed output generated in hook_init()')); +} + +/** + * Menu callback for testing themed output generated in hook_init(). + */ +function theme_test_hook_init_page_callback() { + return $GLOBALS['theme_test_output']; +} + /** * Custom theme callback. */ diff --git a/modules/simpletest/tests/update.test b/modules/simpletest/tests/update.test index ea86bdf1f..944dba7f6 100644 --- a/modules/simpletest/tests/update.test +++ b/modules/simpletest/tests/update.test @@ -1,5 +1,5 @@ <?php -// $Id: update.test,v 1.1 2010/02/03 18:16:23 webchick Exp $ +// $Id: update.test,v 1.2 2010/04/28 05:28:22 webchick Exp $ /** * @file @@ -86,3 +86,31 @@ class UpdateDependencyMissingTestCase extends DrupalWebTestCase { } } +/** + * Tests for the invocation of hook_update_dependencies(). + */ +class UpdateDependencyHookInvocationTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Update dependency hook invocation', + 'description' => 'Test that the hook invocation for determining update dependencies works correctly.', + 'group' => 'Update API', + ); + } + + function setUp() { + parent::setUp('update_test_1', 'update_test_2'); + require_once DRUPAL_ROOT . '/includes/update.inc'; + } + + /** + * Test the structure of the array returned by hook_update_dependencies(). + */ + function testHookUpdateDependencies() { + $update_dependencies = update_retrieve_dependencies(); + $this->assertTrue($update_dependencies['system'][7000]['update_test_1'] == 7000, t('An update function that has a dependency on two separate modules has the first dependency recorded correctly.')); + $this->assertTrue($update_dependencies['system'][7000]['update_test_2'] == 7001, t('An update function that has a dependency on two separate modules has the second dependency recorded correctly.')); + $this->assertTrue($update_dependencies['system'][7001]['update_test_1'] == 7002, t('An update function that depends on more than one update from the same module only has the dependency on the higher-numbered update function recorded.')); + } +} + diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index 3a8d9c325..696344a91 100644 --- a/modules/simpletest/tests/update_test_1.info +++ b/modules/simpletest/tests/update_test_1.info @@ -8,8 +8,8 @@ files[] = update_test_1.module files[] = update_test_1.install hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/update_test_1.install b/modules/simpletest/tests/update_test_1.install index 3bb71bde7..814a74a8d 100644 --- a/modules/simpletest/tests/update_test_1.install +++ b/modules/simpletest/tests/update_test_1.install @@ -1,11 +1,40 @@ <?php -// $Id: update_test_1.install,v 1.1 2010/02/03 18:16:23 webchick Exp $ +// $Id: update_test_1.install,v 1.2 2010/04/28 05:28:22 webchick Exp $ /** * @file * Install, update and uninstall functions for the update_test_1 module. */ +/** + * Implements hook_update_dependencies(). + * + * @see update_test_2_update_dependencies() + */ +function update_test_1_update_dependencies() { + // These dependencies are used in combination with those declared in + // update_test_2_update_dependencies() for the sole purpose of testing that + // the results of hook_update_dependencies() are collected correctly and have + // the correct array structure. Therefore, we use updates from System module + // (which have already run), so that they will not get in the way of other + // tests. + $dependencies['system'][7000] = array( + // Compare to update_test_2_update_dependencies(), where the same System + // module update function is forced to depend on an update function from a + // different module. This allows us to test that both dependencies are + // correctly recorded. + 'update_test_1' => 7000, + ); + $dependencies['system'][7001] = array( + // Compare to update_test_2_update_dependencies(), where the same System + // module update function is forced to depend on a different update + // function within the same module. This allows us to test that only the + // dependency on the higher-numbered update function is recorded. + 'update_test_1' => 7002, + ); + return $dependencies; +} + /** * Dummy update_test_1 update 7000. */ diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index c1c6c4d4c..6cd8bc12f 100644 --- a/modules/simpletest/tests/update_test_2.info +++ b/modules/simpletest/tests/update_test_2.info @@ -8,8 +8,8 @@ files[] = update_test_2.module files[] = update_test_2.install hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/update_test_2.install b/modules/simpletest/tests/update_test_2.install index 585c1d6d0..6e094ac48 100644 --- a/modules/simpletest/tests/update_test_2.install +++ b/modules/simpletest/tests/update_test_2.install @@ -1,5 +1,5 @@ <?php -// $Id: update_test_2.install,v 1.1 2010/02/03 18:16:23 webchick Exp $ +// $Id: update_test_2.install,v 1.2 2010/04/28 05:28:22 webchick Exp $ /** * @file @@ -8,11 +8,30 @@ /** * Implements hook_update_dependencies(). + * + * @see update_test_1_update_dependencies() + * @see update_test_3_update_dependencies() */ function update_test_2_update_dependencies() { + // Combined with update_test_3_update_dependencies(), we are declaring here + // that these two modules run updates in the following order: + // 1. update_test_2_update_7000() + // 2. update_test_3_update_7000() + // 3. update_test_2_update_7001() + // 4. update_test_2_update_7002() $dependencies['update_test_2'][7001] = array( 'update_test_3' => 7000, ); + + // These are coordinated with the corresponding dependencies declared in + // update_test_1_update_dependencies(). + $dependencies['system'][7000] = array( + 'update_test_2' => 7001, + ); + $dependencies['system'][7001] = array( + 'update_test_1' => 7001, + ); + return $dependencies; } diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index a95f31dad..d10794a64 100644 --- a/modules/simpletest/tests/update_test_3.info +++ b/modules/simpletest/tests/update_test_3.info @@ -8,8 +8,8 @@ files[] = update_test_3.module files[] = update_test_3.install hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/update_test_3.install b/modules/simpletest/tests/update_test_3.install index 3030296fa..4a86e79b3 100644 --- a/modules/simpletest/tests/update_test_3.install +++ b/modules/simpletest/tests/update_test_3.install @@ -1,5 +1,5 @@ <?php -// $Id: update_test_3.install,v 1.1 2010/02/03 18:16:23 webchick Exp $ +// $Id: update_test_3.install,v 1.2 2010/04/28 05:28:22 webchick Exp $ /** * @file @@ -8,6 +8,8 @@ /** * Implements hook_update_dependencies(). + * + * @see update_test_2_update_dependencies() */ function update_test_3_update_dependencies() { $dependencies['update_test_3'][7000] = array( diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index f24af5268..a171d908b 100644 --- a/modules/simpletest/tests/url_alter_test.info +++ b/modules/simpletest/tests/url_alter_test.info @@ -8,8 +8,8 @@ files[] = url_alter_test.module files[] = url_alter_test.install hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/simpletest/tests/xmlrpc.test b/modules/simpletest/tests/xmlrpc.test index 42288250c..7625a8ffc 100644 --- a/modules/simpletest/tests/xmlrpc.test +++ b/modules/simpletest/tests/xmlrpc.test @@ -1,5 +1,5 @@ <?php -// $Id: xmlrpc.test,v 1.16 2010/03/31 15:56:53 dries Exp $ +// $Id: xmlrpc.test,v 1.17 2010/05/06 05:59:31 webchick Exp $ /** * Perform basic XML-RPC tests that do not require addition callbacks. @@ -155,7 +155,7 @@ class XMLRPCValidator1IncTestCase extends DrupalWebTestCase { for ($y = 2000; $y < 2002; $y++) { for ($m = 3; $m < 5; $m++) { for ($d = 1; $d < 6; $d++) { - $ys = (string)$y; + $ys = (string) $y; $ms = sprintf('%02d', $m); $ds = sprintf('%02d', $d); $struct_7[$ys][$ms][$ds]['moe'] = mt_rand(-100, 100); diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index 4537cb493..244b58f6f 100644 --- a/modules/simpletest/tests/xmlrpc_test.info +++ b/modules/simpletest/tests/xmlrpc_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = xmlrpc_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 8329f376c..5922dd379 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -12,8 +12,8 @@ files[] = statistics.test files[] = statistics.tokens.inc configure = admin/config/system/statistics -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/statistics/statistics.install b/modules/statistics/statistics.install index 1c18ef467..02765039d 100644 --- a/modules/statistics/statistics.install +++ b/modules/statistics/statistics.install @@ -1,5 +1,5 @@ <?php -// $Id: statistics.install,v 1.25 2009/12/04 16:49:47 dries Exp $ +// $Id: statistics.install,v 1.26 2010/04/28 05:29:55 webchick Exp $ /** * @file @@ -133,24 +133,6 @@ function statistics_schema() { return $schema; } -/** - * @defgroup updates-6.x-extra Extra statistics updates for 6.x - * @{ - */ - -/** - * Allow longer referrers. - */ -function statistics_update_6000() { - db_change_field('accesslog', 'url', 'url', array('type' => 'text', 'not null' => FALSE)); -} - -/** - * @} End of "defgroup updates-6.x-extra" - * The next series of updates should start at 7000. - */ - - /** * @defgroup updates-6.x-to-7.x statistics updates from 6.x to 7.x * @{ diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module index 16b14fdd4..08d8612cd 100644 --- a/modules/statistics/statistics.module +++ b/modules/statistics/statistics.module @@ -1,5 +1,5 @@ <?php -// $Id: statistics.module,v 1.333 2010/03/30 07:17:19 webchick Exp $ +// $Id: statistics.module,v 1.335 2010/05/18 18:26:30 dries Exp $ /** * @file @@ -51,7 +51,12 @@ function statistics_help($path, $arg) { function statistics_exit() { global $user; - drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + // When serving cached pages with the 'page_cache_without_database' + // configuration, system variables need to be loaded. This is a major + // performance decrease for non-database page caches, but with Statistics + // module, it is likely to also have 'statistics_enable_access_log' enabled, + // in which case we need to bootstrap to the session phase anyway. + drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); if (variable_get('statistics_count_content_views', 0)) { // We are counting content views. @@ -70,6 +75,7 @@ function statistics_exit() { } } if (variable_get('statistics_enable_access_log', 0)) { + drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); // Log this page access. db_insert('accesslog') ->fields(array( @@ -304,12 +310,14 @@ function statistics_get($nid) { * Implements hook_block_info(). */ function statistics_block_info() { + $blocks = array(); + if (variable_get('statistics_count_content_views', 0)) { $blocks['popular']['info'] = t('Popular content'); // Too dynamic to cache. $blocks['popular']['cache'] = DRUPAL_NO_CACHE; - return $blocks; } + return $blocks; } /** diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test index 1b25a33ac..e35c6ada1 100644 --- a/modules/statistics/statistics.test +++ b/modules/statistics/statistics.test @@ -1,5 +1,5 @@ <?php -// $Id: statistics.test,v 1.17 2010/04/20 09:48:06 webchick Exp $ +// $Id: statistics.test,v 1.18 2010/05/18 18:26:30 dries Exp $ /** * Sets up a base class for the Statistics module. @@ -33,6 +33,69 @@ class StatisticsTestCase extends DrupalWebTestCase { } } +/** + * Tests that logging via statistics_exit() works for cached and uncached pages. + * + * Subclass DrupalWebTestCase rather than StatisticsTestCase, because we want + * to test requests from an anonymous user. + */ +class StatisticsLoggingTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Statistics logging tests', + 'description' => 'Tests request logging for cached and uncached pages.', + 'group' => 'Statistics' + ); + } + + function setUp() { + parent::setUp('statistics'); + + // Ensure we have a node page to access. + $this->node = $this->drupalCreateNode(); + + // Enable page caching. + variable_set('cache', TRUE); + + // Enable access logging. + variable_set('statistics_enable_access_log', 1); + variable_set('statistics_count_content_views', 1); + + // Clear the logs. + db_truncate('accesslog'); + db_truncate('node_counter'); + } + + /** + * Verifies request logging for cached and uncached pages. + */ + function testLogging() { + $path = 'node/' . $this->node->nid; + $expected = array( + 'title' => $this->node->title, + 'path' => $path, + ); + + // Verify logging of an uncached page. + $this->drupalGet($path); + $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Testing an uncached page.')); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 1, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[0], $expected), $expected); + $node_counter = statistics_get($this->node->nid); + $this->assertIdentical($node_counter['totalcount'], '1'); + + // Verify logging of a cached page. + $this->drupalGet($path); + $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Testing a cached page.')); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 2, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[1], $expected), $expected); + $node_counter = statistics_get($this->node->nid); + $this->assertIdentical($node_counter['totalcount'], '2'); + } +} + /** * Tests that report pages render properly, and that access logging works. */ diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index f5891b8f7..40efff403 100644 --- a/modules/syslog/syslog.info +++ b/modules/syslog/syslog.info @@ -7,8 +7,8 @@ core = 7.x files[] = syslog.module files[] = syslog.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/system/admin-rtl.css b/modules/system/admin-rtl.css index e5a8801b6..cbf6195c8 100644 --- a/modules/system/admin-rtl.css +++ b/modules/system/admin-rtl.css @@ -1,4 +1,4 @@ -/* $Id: admin-rtl.css,v 1.6 2009/08/03 03:04:33 webchick Exp $ */ +/* $Id: admin-rtl.css,v 1.7 2010/04/28 20:08:39 dries Exp $ */ div.admin-panel .body { padding: 0 8px 2px 4px; @@ -23,7 +23,8 @@ div.admin .expert-link { padding-left: 4px; } -table.system-status-report th, table.system-status-report tr.merge-up td { +table.system-status-report th, +table.system-status-report tr.merge-up td { padding-right: 30px; } @@ -38,7 +39,8 @@ table.screenshot { .date-container { clear: right; } -.date-container .select-container, .date-container .custom-container { +.date-container .select-container, +.date-container .custom-container { float: right; } .date-container .custom-container { diff --git a/modules/system/admin.css b/modules/system/admin.css index e4f6847cc..54df5af5e 100644 --- a/modules/system/admin.css +++ b/modules/system/admin.css @@ -1,4 +1,4 @@ -/* $Id: admin.css,v 1.21 2009/08/03 03:04:33 webchick Exp $ */ +/* $Id: admin.css,v 1.22 2010/04/28 20:08:39 dries Exp $ */ /* ** Formatting for administration page @@ -47,7 +47,8 @@ table.package .description { table.package .version { direction: ltr; } -div.admin-requirements, div.admin-required { +div.admin-requirements, +div.admin-required { font-size: 0.9em; color: #444; } @@ -67,7 +68,8 @@ span.admin-missing { table.system-status-report th { border-bottom: 1px solid #ccc; } -table.system-status-report th, table.system-status-report tr.merge-up td { +table.system-status-report th, +table.system-status-report tr.merge-up td { padding-left: 30px; /* LTR */ } table.system-status-report th { @@ -125,7 +127,8 @@ table.screenshot { .date-container .form-item { margin-top: 0; } -.date-container .select-container, .date-container .custom-container { +.date-container .select-container, +.date-container .custom-container { float: left; /* LTR */ } .date-container .custom-container { diff --git a/modules/system/system-behavior-rtl.css b/modules/system/system-behavior-rtl.css index ee9cb9d39..06c03fb1e 100644 --- a/modules/system/system-behavior-rtl.css +++ b/modules/system/system-behavior-rtl.css @@ -1,4 +1,4 @@ -/* $Id: system-behavior-rtl.css,v 1.2 2010/03/03 19:46:26 dries Exp $ */ +/* $Id: system-behavior-rtl.css,v 1.3 2010/04/28 20:08:39 dries Exp $ */ /** * Autocomplete @@ -71,14 +71,16 @@ div.indentation { padding: 0.42em 0.6em 0.42em 0; float: right; } -div.tree-child, div.tree-child-last { +div.tree-child, +div.tree-child-last { background-position: -65px center; } /** * Multiselect form */ -dl.multiselect dt, dl.multiselect dd { +dl.multiselect dt, +dl.multiselect dd { float: right; margin: 0 0 0 1em; } diff --git a/modules/system/system-behavior.css b/modules/system/system-behavior.css index 66e71a89b..14ed433f0 100644 --- a/modules/system/system-behavior.css +++ b/modules/system/system-behavior.css @@ -1,4 +1,4 @@ -/* $Id: system-behavior.css,v 1.8 2010/04/11 18:45:47 dries Exp $ */ +/* $Id: system-behavior.css,v 1.9 2010/04/28 20:08:39 dries Exp $ */ /** * Autocomplete @@ -176,12 +176,15 @@ tr .ajax-progress .throbber { /** * Multiselect form */ -dl.multiselect dd, dl.multiselect dd .form-item, dl.multiselect dd select { +dl.multiselect dd, +dl.multiselect dd .form-item, +dl.multiselect dd select { font-family: inherit; font-size: inherit; width: 14em; } -dl.multiselect dt, dl.multiselect dd { +dl.multiselect dt, +dl.multiselect dd { float: left; /* LTR */ line-height: 1.75em; padding: 0; @@ -217,7 +220,8 @@ dl.multiselect .form-item { width: 0%; background-color: #47C965; } -input.password-confirm, input.password-field { +input.password-confirm, +input.password-field { width: 16em; margin-bottom: 0.4em; } diff --git a/modules/system/system-menus-rtl.css b/modules/system/system-menus-rtl.css index 36dedabec..f1449afc3 100644 --- a/modules/system/system-menus-rtl.css +++ b/modules/system/system-menus-rtl.css @@ -1,4 +1,4 @@ -/* $Id: system-menus-rtl.css,v 1.2 2010/03/07 08:13:43 webchick Exp $ */ +/* $Id: system-menus-rtl.css,v 1.3 2010/04/28 20:08:39 dries Exp $ */ ul.menu { text-align:right; @@ -9,6 +9,8 @@ ul.menu li { ul li.collapsed { list-style-image: url(../../misc/menu-collapsed-rtl.png); } -li.expanded, li.collapsed, li.leaf { +li.expanded, +li.collapsed, +li.leaf { padding: 0.2em 0 0 0.5em; } diff --git a/modules/system/system-menus.css b/modules/system/system-menus.css index 87abccf59..dd7a243df 100644 --- a/modules/system/system-menus.css +++ b/modules/system/system-menus.css @@ -1,4 +1,4 @@ -/* $Id: system-menus.css,v 1.2 2010/03/07 08:13:43 webchick Exp $ */ +/* $Id: system-menus.css,v 1.4 2010/05/18 11:56:59 dries Exp $ */ ul.menu { list-style: none; @@ -10,7 +10,7 @@ ul.menu li { } ul li.expanded { list-style-type: circle; - list-style-image: url(../../misc/menu-expanded.png); + list-style-image: url(../../misc/menu-expanded.png); } ul li.collapsed { list-style-type: disc; @@ -20,7 +20,9 @@ ul li.leaf { list-style-type: square; list-style-image: url(../../misc/menu-leaf.png); } -li.expanded, li.collapsed, li.leaf { +li.expanded, +li.collapsed, +li.leaf { padding: 0.2em 0.5em 0 0; /* LTR */ margin: 0; } diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index cf6fd312d..263b06301 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: system.admin.inc,v 1.270 2010/04/24 14:49:14 dries Exp $ +// $Id: system.admin.inc,v 1.284 2010/05/21 11:37:55 dries Exp $ /** * @file @@ -10,11 +10,12 @@ * Menu callback; Provide the administration overview page. */ function system_main_admin_page($arg = NULL) { - // If we received an argument, they probably meant some other page. - // Let's 404 them since the menu system cannot be told we do not - // accept arguments. + // Only continue if provided arguments are expected. This function serves + // as the callback for the top-level admin/ page, so any unexpected arguments + // are likely the result of someone typing in the URL of an administrative + // page that doesn't actually exist; for example, admin/some/random/page. if (isset($arg) && substr($arg, 0, 3) != 'by-') { - return drupal_not_found(); + return MENU_NOT_FOUND; } // Check for status report errors. @@ -96,12 +97,15 @@ function system_admin_config_page() { } $block = $item; $block['content'] = ''; - $block['show'] = TRUE; if ($item['block_callback'] && function_exists($item['block_callback'])) { $function = $item['block_callback']; $block['content'] .= $function(); } $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item))); + if (!empty($block['content'])) { + $block['show'] = TRUE; + } + // Prepare for sorting as in function _menu_tree_check_access(). // The weight is offset so it is always positive, with a uniform 5-digits. $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block; @@ -141,8 +145,12 @@ function system_admin_menu_block_page() { * Menu callback; prints a listing of admin tasks for each installed module. */ function system_admin_by_module() { - $module_info = system_get_info('module'); + foreach ($module_info as $module => $info) { + $module_info[$module] = new StdClass(); + $module_info[$module]->info = $info; + } + uasort($module_info, 'system_sort_modules_by_info_name'); $menu_items = array(); $help_arg = module_exists('help') ? drupal_help_arg() : FALSE; @@ -164,7 +172,7 @@ function system_admin_by_module() { // Sort. ksort($admin_tasks); - $menu_items[$info['name']] = array($info['description'], $admin_tasks); + $menu_items[$info->info['name']] = array($info->info['description'], $admin_tasks); } } return theme('system_admin_by_module', array('menu_items' => $menu_items)); @@ -187,35 +195,21 @@ function system_settings_overview() { return $output; } -/** - * Retrieve the list of themes that are not hidden. - */ -function _system_theme_list() { - // Get current list of themes. - $themes = system_rebuild_theme_data(); - - // Remove hidden themes from the display list. - foreach ($themes as $theme_key => $theme) { - if (!empty($theme->info['hidden'])) { - unset($themes[$theme_key]); - } - } - - uasort($themes, 'system_sort_modules_by_info_name'); - return $themes; -} - /** * Menu callback; displays a listing of all themes. */ function system_themes_page() { // Get current list of themes. - $themes = _system_theme_list(); + $themes = list_themes(); + uasort($themes, 'system_sort_modules_by_info_name'); $theme_default = variable_get('theme_default', 'garland'); $theme_groups = array(); foreach ($themes as &$theme) { + if (!empty($theme->info['hidden'])) { + continue; + } $admin_theme_options[$theme->name] = $theme->info['name']; $theme->is_default = ($theme->name == $theme_default); @@ -369,7 +363,7 @@ function system_theme_enable() { if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) { $theme = $_REQUEST['theme']; // Get current list of themes. - $themes = _system_theme_list(); + $themes = list_themes(); // Check if the specified theme is one recognized by the system. if (!empty($themes[$theme])) { @@ -391,7 +385,7 @@ function system_theme_disable() { if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) { $theme = $_REQUEST['theme']; // Get current list of themes. - $themes = _system_theme_list(); + $themes = list_themes(); // Check if the specified theme is one recognized by the system. if (!empty($themes[$theme])) { @@ -419,7 +413,7 @@ function system_theme_default() { if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) { $theme = $_REQUEST['theme']; // Get current list of themes. - $themes = _system_theme_list(); + $themes = list_themes(); // Check if the specified theme is one recognized by the system. if (!empty($themes[$theme])) { @@ -832,20 +826,21 @@ function system_modules($form, $form_state = array()) { $files = system_rebuild_module_data(); // Remove hidden modules from display list. - foreach ($files as $filename => $file) { + $visible_files = $files; + foreach ($visible_files as $filename => $file) { if (!empty($file->info['hidden']) || !empty($file->info['required'])) { - unset($files[$filename]); + unset($visible_files[$filename]); } } - uasort($files, 'system_sort_modules_by_info_name'); + uasort($visible_files, 'system_sort_modules_by_info_name'); // If the modules form was submitted, then system_modules_submit() runs first // and if there are unfilled required modules, then $form_state['storage'] is // filled, triggering a rebuild. In this case we need to display a // confirmation form. if (!empty($form_state['storage'])) { - return system_modules_confirm_form($files, $form_state['storage']); + return system_modules_confirm_form($visible_files, $form_state['storage']); } $modules = array(); @@ -855,7 +850,7 @@ function system_modules($form, $form_state = array()) { $help_arg = module_exists('help') ? drupal_help_arg() : FALSE; // Iterate through each of the modules. - foreach ($files as $filename => $module) { + foreach ($visible_files as $filename => $module) { $extra = array(); $extra['enabled'] = (bool) $module->status; // If this module requires other modules, add them to the array. @@ -864,7 +859,8 @@ function system_modules($form, $form_state = array()) { $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires))); $extra['disabled'] = TRUE; } - else { + // Only display visible modules. + elseif (isset($visible_files[$requires])) { $requires_name = $files[$requires]->info['name']; if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) { $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array( @@ -921,8 +917,8 @@ function system_modules($form, $form_state = array()) { // impossible to disable this one. foreach ($module->required_by as $required_by => $v) { // Hidden modules are unset already. - if (isset($files[$required_by])) { - if ($files[$required_by]->status == 1) { + if (isset($visible_files[$required_by])) { + if ($files[$required_by]->status == 1 && $module->status == 1) { $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name'])); $extra['disabled'] = TRUE; } @@ -1240,7 +1236,6 @@ function system_modules_submit($form, &$form_state) { registry_rebuild(); system_rebuild_theme_data(); drupal_theme_rebuild(); - cache_clear_all('system_list', 'cache_bootstrap'); node_types_rebuild(); menu_rebuild(); cache_clear_all('schema', 'cache'); @@ -1338,7 +1333,7 @@ function system_modules_uninstall_confirm_form($storage) { // Construct the hidden form elements and list items. foreach (array_filter($storage['uninstall']) as $module => $value) { - $info = drupal_parse_info_file(dirname(drupal_get_filename('module', $module)) . '/' . $module . '.info'); + $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info'); $uninstall[] = $info['name']; $form['uninstall'][$module] = array('#type' => 'hidden', '#value' => 1, @@ -1631,12 +1626,12 @@ function system_performance_settings() { '#title' => t('Caching'), ); - $cache = variable_get('cache', CACHE_DISABLED); + $cache = variable_get('cache', 0); $form['caching']['cache'] = array( - '#type' => 'radios', - '#title' => t('Page cache for anonymous users'), + '#type' => 'checkbox', + '#title' => t('Cache pages for anonymous users'), '#default_value' => $cache, - '#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Normal (recommended)')), + '#weight' => -2, ); $period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval'); $period[0] = '<' . t('none') . '>'; @@ -1645,7 +1640,14 @@ function system_performance_settings() { '#title' => t('Minimum cache lifetime'), '#default_value' => variable_get('cache_lifetime', 0), '#options' => $period, - '#description' => t('The minimum amount of time that will elapse before the caches are recreated.') + '#description' => t('Cached pages will not be re-created until at least this much time has elapsed.') + ); + $form['caching']['page_cache_maximum_age'] = array( + '#type' => 'select', + '#title' => t('Expiration of cached pages'), + '#default_value' => variable_get('page_cache_maximum_age', 0), + '#options' => $period, + '#description' => t('The maximum time an external cache can use an old version of a page.') ); $directory = 'public://'; @@ -1662,7 +1664,7 @@ function system_performance_settings() { '#description' => t('External resources can be optimized automatically, which can reduce both the size and number of requests made to your website.') . $disabled_message, ); - $js_hide = $cache == CACHE_DISABLED ? ' class="js-hide"' : ''; + $js_hide = $cache ? '' : ' class="js-hide"'; $form['bandwidth_optimization']['page_compression'] = array( '#type' => 'checkbox', '#title' => t('Compress cached pages.'), @@ -1736,15 +1738,18 @@ function system_file_system_settings() { // Any visible, writeable wrapper can potentially be used for the files // directory, including a remote file system that integrates with a CDN. foreach(file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) { - $options[$scheme] = $info['description']; + $options[$scheme] = check_plain($info['description']); + } + + if (!empty($options)) { + $form['file_default_scheme'] = array( + '#type' => 'radios', + '#title' => t('Default download method'), + '#default_value' => isset($options['public']) ? 'public' : key($options), + '#options' => $options, + '#description' => t('This setting is used as the preferred download method. The use of public files is more efficient, but does not provide any access control.'), + ); } - $form['file_default_scheme'] = array( - '#type' => 'radios', - '#title' => t('Default download method'), - '#default_value' => 'public', - '#options' => $options, - '#description' => t('This setting is used as the preferred download method. The use of public files is more efficient, but does not provide any access control.'), - ); return system_settings_form($form, TRUE); } @@ -2028,7 +2033,7 @@ function system_add_date_format_type_form($form, &$form_state) { '#title' => t('Date type'), '#type' => 'textfield', '#required' => TRUE, - '#field_suffix' => ' <small id="edit-date-type-suffix"> </small>', + '#field_suffix' => ' <small id="edit-date-type-suffix"> </small>', ); $js_settings = array( 'type' => 'setting', @@ -2474,15 +2479,15 @@ function theme_status_report($variables) { REQUIREMENT_WARNING => 'warning', REQUIREMENT_ERROR => 'error', ); - $class = $classes[isset($requirement['severity']) ? (int)$requirement['severity'] : 0] . ' ' . $class; + $class = $classes[isset($requirement['severity']) ? (int) $requirement['severity'] : 0] . ' ' . $class; // Output table row(s) if (!empty($requirement['description'])) { - $output .= '<tr class="' . $class . ' merge-down"><th>' . $requirement['title'] . '</th><td>' . $requirement['value'] . '</td></tr>'; + $output .= '<tr class="' . $class . ' merge-down"><td>' . $requirement['title'] . '</td><td>' . $requirement['value'] . '</td></tr>'; $output .= '<tr class="' . $class . ' merge-up"><td colspan="2">' . $requirement['description'] . '</td></tr>'; } else { - $output .= '<tr class="' . $class . '"><th>' . $requirement['title'] . '</th><td>' . $requirement['value'] . '</td></tr>'; + $output .= '<tr class="' . $class . '"><td>' . $requirement['title'] . '</td><td>' . $requirement['value'] . '</td></tr>'; } } } @@ -2936,7 +2941,7 @@ function system_actions_manage_form_submit($form, &$form_state) { * on our elements. * * @param $action - * md5 hash of an action ID or an integer. If it is an md5 hash, we are + * Hash of an action ID or an integer. If it is a hash, we are * creating a new instance. If it is an integer, we are editing an existing * instance. * @return @@ -2961,7 +2966,7 @@ function system_actions_configure($form, &$form_state, $action = NULL) { $edit['actions_label'] = $data->label; $edit['actions_type'] = $data->type; $function = $data->callback; - $action = md5($data->callback); + $action = drupal_hash_base64($data->callback); $params = unserialize($data->parameters); if ($params) { foreach ($params as $name => $val) { diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 3bb8824ea..9ab122284 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -1,5 +1,5 @@ <?php -// $Id: system.api.php,v 1.157 2010/04/26 14:33:54 dries Exp $ +// $Id: system.api.php,v 1.167 2010/05/07 12:59:07 dries Exp $ /** * @file @@ -32,6 +32,8 @@ * exists, and automatically load it when required. * * See system_hook_info() for all hook groups defined by Drupal core. + * + * @see hook_hook_info_alter(). */ function hook_hook_info() { $hooks['token_info'] = array( @@ -43,6 +45,21 @@ function hook_hook_info() { return $hooks; } +/** + * Alter information from hook_hook_info(). + * + * @param $hooks + * Information gathered by module_hook_info() from other modules' + * implementations of hook_hook_info(). Alter this array directly. + * See hook_hook_info() for information on what this may contain. + */ +function hook_hook_info_alter(&$hooks) { + // Our module wants to completely override the core tokens, so make + // sure the core token hooks are not found. + $hooks['token_info']['group'] = 'mytokens'; + $hooks['tokens']['group'] = 'mytokens'; +} + /** * Inform the base system and the Field API about one or more entity types. * @@ -405,8 +422,8 @@ function hook_cron_queue_info() { * @param array $queues * An array of cron queue information. * - * @see hook_cron_queue_info() - * @see drupal_cron_run() + * @see hook_cron_queue_info() + * @see drupal_cron_run() */ function hook_cron_queue_info_alter(&$queues) { // This site has many feeds so let's spend 90 seconds on each cron run @@ -537,9 +554,9 @@ function hook_js_alter(&$javascript) { * element of the value. * - 'css': Like 'js', an array of CSS elements passed to drupal_add_css(). * - 'dependencies': An array of libraries that are required for a library. Each - * element is an array containing the module and name of the registered - * library. Note that all dependencies for each dependent library will be - * added when this library is added. + * element is an array listing the module and name of another library. Note + * that all dependencies for each dependent library will also be added when + * this library is added. * * Registered information for a library should contain re-usable data only. * Module- or implementation-specific data and integration logic should be added @@ -582,7 +599,7 @@ function hook_library() { ), 'dependencies' => array( // Require jQuery UI core by System module. - array('system' => 'ui'), + array('system', 'ui'), // Require our other library. array('my_module', 'library-1'), // Require another library. @@ -682,6 +699,499 @@ function hook_page_build(&$page) { } } +/** + * Define menu items and page callbacks. + * + * This hook enables modules to register paths in order to define how URL + * requests are handled. Paths may be registered for URL handling only, or they + * can register a link to be placed in a menu (usually the Navigation menu). A + * path and its associated information is commonly called a "menu router item". + * This hook is rarely called (for example, when modules are enabled), and + * its results are cached in the database. + * + * hook_menu() implementations return an associative array whose keys define + * paths and whose values are an associative array of properties for each + * path. (The complete list of properties is in the return value section below.) + * + * The definition for each path may include a page callback function, which is + * invoked when the registered path is requested. If there is no other + * registered path that fits the requested path better, any further path + * components are passed to the callback function. For example, your module + * could register path 'abc/def': + * @code + * function mymodule_menu() { + * $items['abc/def'] = array( + * 'page callback' => 'mymodule_abc_view', + * ); + * } + * + * function mymodule_abc_view($ghi = 0, $jkl = '') { + * // ... + * } + * @endcode + * When path 'abc/def' is requested, no further path components are in the + * request, and no additional arguments are passed to the callback function (so + * $ghi and $jkl would take the default values as defined in the function + * signature). When 'abc/def/123/foo' is requested, $ghi will be '123' and + * $jkl will be 'foo'. Note that this automatic passing of optional path + * arguments applies only to page and theme callback functions. + * + * In addition to optional path arguments, the page callback and other callback + * functions may specify argument lists as arrays. These argument lists may + * contain both fixed/hard-coded argument values and integers that correspond + * to path components. When integers are used and the callback function is + * called, the corresponding path components will be substituted for the + * integers. That is, the integer 0 in an argument list will be replaced with + * the first path component, integer 1 with the second, and so on (path + * components are numbered starting from zero). This substitution feature allows + * you to re-use a callback function for several different paths. For example: + * @code + * function mymodule_menu() { + * $items['abc/def'] = array( + * 'page callback' => 'mymodule_abc_view', + * 'page arguments' => array(1, 'foo'), + * ); + * } + * @endcode + * When path 'abc/def' is requested, the page callback function will get 'def' + * as the first argument and (always) 'foo' as the second argument. + * + * Note that if a page or theme callback function has an argument list array, + * these arguments will be passed first to the function, followed by any + * any arguments generated by optional path arguments as described above. + * + * Special care should be taken for the page callback drupal_get_form(), because + * your specific form callback function will always receive $form and + * &$form_state as the first function arguments: + * @code + * function mymodule_abc_form($form, &$form_state) { + * // ... + * return $form; + * } + * @endcode + * See @link form_api Form API documentation @endlink for details. + * + * Wildcards within paths also work with integer substitution. For example, + * your module could register path 'my-module/%/edit': + * @code + * $items['my-module/%/edit'] = array( + * 'page callback' => 'mymodule_abc_edit', + * 'page arguments' => array(1), + * ); + * @endcode + * When path 'my-module/foo/edit' is requested, integer 1 will be replaced + * with 'foo' and passed to the callback function. + * + * Registered paths may also contain special "auto-loader" wildcard components + * in the form of '%mymodule_abc', where the '%' part means that this path + * component is a wildcard, and the 'mymodule_abc' part defines the prefix for a + * load function, which here would be named mymodule_abc_load(). When a matching + * path is requested, your load function will receive as its first argument the + * path component in the position of the wildcard; load functions may also be + * passed additional arguments (see "load arguments" in the return value + * section below). For example, your module could register path + * 'my-module/%mymodule_abc/edit': + * @code + * $items['my-module/%mymodule_abc/edit'] = array( + * 'page callback' => 'mymodule_abc_edit', + * 'page arguments' => array(1), + * ); + * @endcode + * When path 'my-module/123/edit' is requested, your load function + * mymodule_abc_load() will be invoked with the argument '123', and should + * load and return an "abc" object with internal id 123: + * @code + * function mymodule_abc_load($abc_id) { + * return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject(); + * } + * @endcode + * This 'abc' object will then be passed into the page callback function + * mymodule_abc_edit() to replace the integer 1 in the page arguments. + * + * You can also make groups of menu items to be rendered (by default) as tabs + * on a page. To do that, first create one menu item of type MENU_NORMAL_ITEM, + * with your chosen path, such as 'foo'. Then duplicate that menu item, using a + * subdirectory path, such as 'foo/tab1', and changing the type to + * MENU_DEFAULT_LOCAL_TASK to make it the default tab for the group. Then add + * the additional tab items, with paths such as "foo/tab2" etc., with type + * MENU_LOCAL_TASK. Example: + * @code + * // Make "Foo settings" appear on the admin Config page + * $items['admin/config/foo'] = array( + * 'title' => 'Foo settings', + * 'type' => MENU_NORMAL_ITEM, + * // page callback, etc. need to be added here + * ); + * // Make "Global settings" the main tab on the "Foo settings" page + * $items['admin/config/foo/global'] = array( + * 'title' => 'Global settings', + * 'type' => MENU_DEFAULT_LOCAL_TASK, + * // access callback, page callback, and theme callback will be inherited + * // from 'admin/config/foo', if not specified here to override + * ); + * // Make an additional tab called "Node settings" on "Foo settings" + * $items['admin/config/foo/node'] = array( + * 'title' => 'Node settings', + * 'type' => MENU_LOCAL_TASK, + * // access callback, page callback, and theme callback will be inherited + * // from 'admin/config/foo', if not specified here to override + * ); + * @endcode + * + * @return + * An array of menu items. Each menu item has a key corresponding to the + * Drupal path being registered. The corresponding array value is an + * associative array that may contain the following key-value pairs: + * - "title": Required. The untranslated title of the menu item. + * - "title callback": Function to generate the title; defaults to t(). + * If you require only the raw string to be output, set this to FALSE. + * - "title arguments": Arguments to send to t() or your custom callback, + * with path component substitution as described above. + * - "description": The untranslated description of the menu item. + * - "page callback": The function to call to display a web page when the user + * visits the path. If omitted, the parent menu item's callback will be used + * instead. + * - "page arguments": An array of arguments to pass to the page callback + * function, with path component substitution as described above. + * - "delivery callback": The function to call to package the result of the + * page callback function and send it to the browser. Defaults to + * drupal_deliver_html_page() unless a value is inherited from a parent menu + * item. + * - "access callback": A function returning a boolean value that determines + * whether the user has access rights to this menu item. Defaults to + * user_access() unless a value is inherited from a parent menu item. + * - "access arguments": An array of arguments to pass to the access callback + * function, with path component substitution as described above. + * - "theme callback": Optional. A function returning the machine-readable + * name of the default theme that will be used to render the page. If this + * function is provided, it is expected to return a currently-active theme + * on the site (otherwise, the main site theme will be used instead). If no + * function is provided, the main site theme will also be used, unless a + * value is inherited from a parent menu item. In all cases, the results of + * this function can be dynamically overridden for a particular page + * request by modules which implement hook_custom_theme(). + * - "theme arguments": An array of arguments to pass to the theme callback + * function, with path component substitution as described above. + * - "file": A file that will be included before the page callback is called; + * this allows page callback functions to be in separate files. The file + * should be relative to the implementing module's directory unless + * otherwise specified by the "file path" option. Does not apply to other + * callbacks (only page callback). + * - "file path": The path to the directory containing the file specified in + * "file". This defaults to the path to the module implementing the hook. + * - "load arguments": An array of arguments to be passed to each of the + * wildcard object loaders in the path, after the path argument itself. + * For example, if a module registers path node/%node/revisions/%/view + * with load arguments set to array(3), the '%node' in the path indicates + * that the loader function node_load() will be called with the second + * path component as the first argument. The 3 in the load arguments + * indicates that the fourth path component will also be passed to + * node_load() (numbering of path components starts at zero). So, if path + * node/12/revisions/29/view is requested, node_load(12, 29) will be called. + * There are also two "magic" values that can be used in load arguments. + * "%index" indicates the index of the wildcard path component. "%map" + * indicates the path components as an array. For example, if a module + * registers for several paths of the form 'user/%user_category/edit/*', all + * of them can use the same load function user_category_load(), by setting + * the load arguments to array('%map', '%index'). For instance, if the user + * is editing category 'foo' by requesting path 'user/32/edit/foo', the load + * function user_category_load() will be called with 32 as its first + * argument, the array ('user', 32, 'edit', 'foo') as the map argument, + * and 1 as the index argument (because %user_category is the second path + * component and numbering starts at zero). user_category_load() can then + * use these values to extract the information that 'foo' is the category + * being requested. + * - "weight": An integer that determines the relative position of items in + * the menu; higher-weighted items sink. Defaults to 0. Menu items with the + * 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. + * - "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 + * in page region containers outside of the parent menu item's primary page + * context should be registered using one of the following contexts: + * - MENU_CONTEXT_PAGE: (default) The tab is displayed as local task for the + * page context only. + * - MENU_CONTEXT_INLINE: The tab is displayed as contextual link outside of + * the primary page context only. + * Contexts can be combined. For example, to display a tab both on a page + * and inline, a menu router item may specify: + * @code + * 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + * @endcode + * - "tab_parent": For local task menu items, the path of the task's parent + * item; defaults to the same path without the last component (e.g., the + * default parent for 'admin/people/create' is 'admin/people'). + * - "tab_root": For local task menu items, the path of the closest non-tab + * item; same default as "tab_parent". + * - "block callback": Name of a function used to render the block on the + * system administration page for this item (called with no arguments). + * If not provided, system_admin_menu_block() is used to generate it. + * - "position": Position of the block ('left' or 'right') on the system + * administration page for this item. + * - "type": A bitmask of flags describing properties of the menu item. + * Many shortcut bitmasks are provided as constants in menu.inc: + * - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be + * moved/hidden by the administrator. + * - MENU_CALLBACK: Callbacks simply register a path so that the correct + * information is generated when the path is accessed. + * - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the + * administrator may enable. + * - MENU_LOCAL_ACTION: Local actions are menu items that describe actions + * on the parent item such as adding a new user or block, and are + * rendered in the action-links list in your theme. + * - MENU_LOCAL_TASK: Local tasks are menu items that describe different + * displays of data, and are generally rendered as tabs. + * - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one + * "default" task, which should display the same page as the parent item. + * If the "type" element is omitted, MENU_NORMAL_ITEM is assumed. + * + * For a detailed usage example, see page_example.module. + * For comprehensive documentation on the menu system, see + * http://drupal.org/node/102338. + */ +function hook_menu() { + $items['blog'] = array( + 'title' => 'blogs', + 'page callback' => 'blog_page', + 'access arguments' => array('access content'), + 'type' => MENU_SUGGESTED_ITEM, + ); + $items['blog/feed'] = array( + 'title' => 'RSS feed', + 'page callback' => 'blog_feed', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Alter the data being saved to the {menu_router} table after hook_menu is invoked. + * + * This hook is invoked by menu_router_build(). The menu definitions are passed + * in by reference. Each element of the $items array is one item returned + * by a module from hook_menu. Additional items may be added, or existing items + * altered. + * + * @param $items + * Associative array of menu router definitions returned from hook_menu(). + */ +function hook_menu_alter(&$items) { + // Example - disable the page at node/add + $items['node/add']['access callback'] = FALSE; +} + +/** + * Alter the data being saved to the {menu_links} table by menu_link_save(). + * + * @param $item + * Associative array defining a menu link as passed into menu_link_save(). + */ +function hook_menu_link_alter(&$item) { + // Example 1 - make all new admin links hidden (a.k.a disabled). + if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) { + $item['hidden'] = 1; + } + // Example 2 - flag a link to be altered by hook_translated_menu_link_alter() + if ($item['link_path'] == 'devel/cache/clear') { + $item['options']['alter'] = TRUE; + } +} + +/** + * Alter a menu link after it's translated, but before it's rendered. + * + * This hook may be used, for example, to add a page-specific query string. + * For performance reasons, only links that have $item['options']['alter'] == TRUE + * will be passed into this hook. The $item['options']['alter'] flag should + * generally be set using hook_menu_link_alter(). + * + * @param $item + * Associative array defining a menu link after _menu_link_translate() + * @param $map + * Associative array containing the menu $map (path parts and/or objects). + */ +function hook_translated_menu_link_alter(&$item, $map) { + if ($item['href'] == 'devel/cache/clear') { + $item['localized_options']['query'] = drupal_get_destination(); + } +} + +/** + * Inform modules that a menu link has been created. + * + * This hook is used to notify modules that menu items have been + * created. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * Associative array defining a menu link as passed into menu_link_save(). + * + * @see hook_menu_link_update() + * @see hook_menu_link_delete() + */ +function hook_menu_link_insert($link) { + // In our sample case, we track menu items as editing sections + // of the site. These are stored in our table as 'disabled' items. + $record['mlid'] = $link['mlid']; + $record['menu_name'] = $link['menu_name']; + $record['status'] = 0; + drupal_write_record('menu_example', $record); +} + +/** + * Inform modules that a menu link has been updated. + * + * This hook is used to notify modules that menu items have been + * updated. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * Associative array defining a menu link as passed into menu_link_save(). + * + * @see hook_menu_link_insert() + * @see hook_menu_link_delete() + */ +function hook_menu_link_update($link) { + // If the parent menu has changed, update our record. + $menu_name = db_result(db_query("SELECT mlid, menu_name, status FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid']))); + if ($menu_name != $link['menu_name']) { + db_update('menu_example') + ->fields(array('menu_name' => $link['menu_name'])) + ->condition('mlid', $link['mlid']) + ->execute(); + } +} + +/** + * Inform modules that a menu link has been deleted. + * + * This hook is used to notify modules that menu items have been + * deleted. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * Associative array defining a menu link as passed into menu_link_save(). + * + * @see hook_menu_link_insert() + * @see hook_menu_link_update() + */ +function hook_menu_link_delete($link) { + // Delete the record from our table. + db_delete('menu_example') + ->condition('mlid', $link['mlid']) + ->execute(); +} + +/** + * Alter tabs and actions displayed on the page before they are rendered. + * + * This hook is invoked by menu_local_tasks(). The system-determined tabs and + * actions are passed in by reference. Additional tabs or actions may be added, + * or existing items altered. + * + * Each tab or action is an associative array containing: + * - #theme: The theme function to use to render. + * - #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(). + * - #active: Whether the link should be marked as 'active'. + * + * @param $data + * An associative array containing: + * - actions: An associative array containing: + * - count: The amount of actions determined by the menu system, which can + * be ignored. + * - output: A list of of actions, each one being an associative array + * as described above. + * - tabs: An indexed array (list) of tab levels (up to 2 levels), each + * containing an associative array: + * - count: The amount of tabs determined by the menu system. This value + * does not need to be altered if there is more than one tab. + * - output: A list of of tabs, each one being an associative array as + * described above. + */ +function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) { + // Add an action linking to node/add to all pages. + $data['actions']['output'][] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => t('Add new content'), + 'href' => 'node/add', + 'localized_options' => array( + 'attributes' => array( + 'title' => t('Add new content'), + ), + ), + ), + ); + + // Add a tab linking to node/add to all pages. + $data['tabs'][0]['output'][] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => t('Example tab'), + 'href' => 'node/add', + 'localized_options' => array( + 'attributes' => array( + 'title' => t('Add new content'), + ), + ), + ), + // Define whether this link is active. This can be omitted for + // implementations that add links to pages outside of the current page + // context. + '#active' => ($router_item['path'] == $root_path), + ); +} + +/** + * Alter contextual links before they are rendered. + * + * This hook is invoked by menu_contextual_links(). The system-determined + * contextual links are passed in by reference. Additional links may be added + * or existing links can be altered. + * + * Each contextual link must at least contain: + * - title: The localized title of the link. + * - href: The system path to link to. + * - localized_options: An array of options to pass to url(). + * + * @param $links + * An associative array containing contextual links for the given $root_path, + * as described above. The array keys are used to build CSS class names for + * contextual links and must therefore be unique for each set of contextual + * links. + * @param $router_item + * The menu router item belonging to the $root_path being requested. + * @param $root_path + * The (parent) path that has been requested to build contextual links for. + * This is a normalized path, which means that an originally passed path of + * 'node/123' became 'node/%'. + * + * @see menu_contextual_links() + * @see hook_menu() + * @see system_preprocess() + */ +function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) { + // Add a link to all contextual links for nodes. + if ($root_path == 'node/%') { + $links['foo'] = array( + 'title' => t('Do fu'), + 'href' => 'foo/do', + 'localized_options' => array( + 'query' => array( + 'foo' => 'bar', + ), + ), + ); + } +} + /** * Perform alterations before a page is rendered. * @@ -745,10 +1255,14 @@ function hook_page_alter(&$page) { * Perform alterations before a form is rendered. * * One popular use of this hook is to add form elements to the node form. When - * altering a node form, the node object retrieved at from $form['#node']. + * altering a node form, the node object can be accessed at $form['#node']. * * Note that instead of hook_form_alter(), which is called for all forms, you - * can also use hook_form_FORM_ID_alter() to alter a specific form. + * can also use hook_form_FORM_ID_alter() to alter a specific form. For each + * module (in system weight order) the general form alter hook implementation + * is invoked first, then the form ID specific alter implementation is called. + * After all module hook implementations are invoked, the hook_form_alter() + * implementations from themes are invoked in the same manner. * * @param $form * Nested array of form elements that comprise the form. @@ -757,6 +1271,8 @@ function hook_page_alter(&$page) { * @param $form_id * String representing the name of the form itself. Typically this is the * name of the function that generated the form. + * + * @see hook_form_FORM_ID_alter() */ function hook_form_alter(&$form, &$form_state, $form_id) { if (isset($form['type']) && $form['type']['#value'] . '_node_settings' == $form_id) { @@ -776,15 +1292,12 @@ function hook_form_alter(&$form, &$form_state, $form_id) { * rather than implementing hook_form_alter() and checking the form ID, or * using long switch statements to alter multiple forms. * - * Note that this hook fires before hook_form_alter(). Therefore all - * implementations of hook_form_FORM_ID_alter() will run before all implementations - * of hook_form_alter(), regardless of the module order. - * * @param $form * Nested array of form elements that comprise the form. * @param $form_state * A keyed array containing the current state of the form. * + * @see hook_form_alter() * @see drupal_prepare_form() */ function hook_form_FORM_ID_alter(&$form, &$form_state) { @@ -1074,91 +1587,92 @@ function hook_permission() { /** * Register a module (or theme's) theme implementations. * - * Modules and themes implementing this return an array of arrays. The key - * to each sub-array is the internal name of the hook, and the array contains - * info about the hook. Each array may contain the following items: - * - * - variables: (required if "render element" not present) An array of - * variables that this theme hook uses. This value allows the theme layer to - * properly utilize templates. Each array key represents the name of the - * variable and the value will be used as the default value if it is not given - * when theme() is called. Template implementations receive these arguments as - * variables in the template file. Function implementations are passed this - * array data in the $variables parameter. - * - render element: (required if "variables" not present) A string that is the - * name of the sole renderable element to pass to the theme function. The - * string represents the name of the "variable" that will hold the renderable - * array inside any optional preprocess or process functions. Cannot be used - * with the "variables" item; only one or the other, not both, can be present - * in a hook's info array. - * - file: The file the implementation resides in. This file will be included - * prior to the theme being rendered, to make sure that the function or - * preprocess function (as needed) is actually loaded; this makes it possible - * to split theme functions out into separate files quite easily. - * - path: Override the path of the file to be used. Ordinarily the module or - * theme path will be used, but if the file will not be in the default path, - * include it here. This path should be relative to the Drupal root - * directory. - * - template: If specified, this theme implementation is a template, and this - * is the template file <b>without an extension</b>. Do not put .tpl.php - * on this file; that extension will be added automatically by the default - * rendering engine (which is PHPTemplate). If 'path', above, is specified, - * the template should also be in this path. - * - function: If specified, this will be the function name to invoke for this - * implementation. If neither file nor function is specified, a default - * function name will be assumed. For example, if a module registers - * the 'node' theme hook, 'theme_node' will be assigned to its function. - * If the chameleon theme registers the node hook, it will be assigned - * 'chameleon_node' as its function. - * - pattern: A regular expression pattern to be used to allow this theme - * implementation to have a dynamic name. The convention is to use __ to - * differentiate the dynamic portion of the theme. For example, to allow - * forums to be themed individually, the pattern might be: 'forum__'. Then, - * when the forum is themed, call: <code>theme(array('forum__' . $tid, 'forum'), - * $forum)</code>. - * - preprocess functions: A list of functions used to preprocess this data. - * Ordinarily this won't be used; it's automatically filled in. By default, - * for a module this will be filled in as template_preprocess_HOOK. For - * a theme this will be filled in as phptemplate_preprocess and - * phptemplate_preprocess_HOOK as well as themename_preprocess and - * themename_preprocess_HOOK. - * - override preprocess functions: Set to TRUE when a theme does NOT want the - * standard preprocess functions to run. This can be used to give a theme - * FULL control over how variables are set. For example, if a theme wants - * total control over how certain variables in the page.tpl.php are set, - * this can be set to true. Please keep in mind that when this is used - * by a theme, that theme becomes responsible for making sure necessary - * variables are set. - * - type: (automatically derived) Where the theme hook is defined: - * '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. - * * The following parameters are all optional. * - * @param $existing + * @param array $existing * An array of existing implementations that may be used for override * purposes. This is primarily useful for themes that may wish to examine * existing implementations to extract data (such as arguments) so that * it may properly register its own, higher priority implementations. * @param $type - * What 'type' is being processed. This is primarily useful so that themes - * tell if they are the actual theme being called or a parent theme. - * May be one of: - * - module: A module is being checked for theme implementations. - * - base_theme_engine: A theme engine is being checked for a theme which is a parent of the actual theme being used. - * - theme_engine: A theme engine is being checked for the actual theme being used. - * - base_theme: A base theme is being checked for theme implementations. - * - theme: The actual theme in use is being checked. + * Whether a theme, module, etc. is being processed. This is primarily useful + * so that themes tell if they are the actual theme being called or a parent + * theme. May be one of: + * - 'module': A module is being checked for theme implementations. + * - 'base_theme_engine': A theme engine is being checked for a theme that is + * a parent of the actual theme being used. + * - 'theme_engine': A theme engine is being checked for the actual theme + * being used. + * - 'base_theme': A base theme is being checked for theme implementations. + * - 'theme': The actual theme in use is being checked. * @param $theme - * The actual name of theme that is being being checked (mostly only useful for - * theme engine). + * The actual name of theme, module, etc. that is being being processed. * @param $path * The directory path of the theme or module, so that it doesn't need to be * looked up. * - * @return - * A keyed array of theme hooks. + * @return array + * An associative array of theme hook information. The keys on the outer + * array are the internal names of the hooks, and the values are arrays + * containing information about the hook. Each array may contain the + * following elements: + * - variables: (required if "render element" not present) An array of + * variables that this theme hook uses. This value allows the theme layer to + * properly utilize templates. Each array key represents the name of the + * variable and the value will be used as the default value if it is not + * given when theme() is called. Template implementations receive these + * arguments as variables in the template file. Function implementations + * are passed this array data in the $variables parameter. + * - render element: (required if "variables" not present) A string that is + * the name of the sole renderable element to pass to the theme function. + * The string represents the name of the "variable" that will hold the + * renderable array inside any optional preprocess or process functions. + * Cannot be used with the "variables" item; only one or the other, not + * both, can be present in a hook's info array. + * - file: The file the implementation resides in. This file will be included + * prior to the theme being rendered, to make sure that the function or + * preprocess function (as needed) is actually loaded; this makes it + * possible to split theme functions out into separate files quite easily. + * - path: Override the path of the file to be used. Ordinarily the module or + * theme path will be used, but if the file will not be in the default path, + * include it here. This path should be relative to the Drupal root + * directory. + * - template: If specified, this theme implementation is a template, and this + * is the template file without an extension. Do not put .tpl.php on this + * file; that extension will be added automatically by the default rendering + * engine (which is PHPTemplate). If 'path', above, is specified, the + * template should also be in this path. + * - function: If specified, this will be the function name to invoke for this + * implementation. If neither file nor function is specified, a default + * function name will be assumed. For example, if a module registers + * the 'node' theme hook, 'theme_node' will be assigned to its function. + * If the chameleon theme registers the node hook, it will be assigned + * 'chameleon_node' as its function. + * - pattern: A regular expression pattern to be used to allow this theme + * implementation to have a dynamic name. The convention is to use __ to + * differentiate the dynamic portion of the theme. For example, to allow + * forums to be themed individually, the pattern might be: 'forum__'. Then, + * when the forum is themed, call: + * @code + * theme(array('forum__' . $tid, 'forum'), $forum) + * @endcode + * - preprocess functions: A list of functions used to preprocess this data. + * Ordinarily this won't be used; it's automatically filled in. By default, + * for a module this will be filled in as template_preprocess_HOOK. For + * a theme this will be filled in as phptemplate_preprocess and + * phptemplate_preprocess_HOOK as well as themename_preprocess and + * themename_preprocess_HOOK. + * - override preprocess functions: Set to TRUE when a theme does NOT want the + * standard preprocess functions to run. This can be used to give a theme + * FULL control over how variables are set. For example, if a theme wants + * total control over how certain variables in the page.tpl.php are set, + * this can be set to true. Please keep in mind that when this is used + * by a theme, that theme becomes responsible for making sure necessary + * variables are set. + * - type: (automatically derived) Where the theme hook is defined: + * '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. */ function hook_theme($existing, $type, $theme, $path) { return array( @@ -1703,7 +2217,7 @@ function hook_file_validate(&$file) { * * @see file_save() */ -function hook_file_insert(&$file) { +function hook_file_insert($file) { } @@ -1717,7 +2231,7 @@ function hook_file_insert(&$file) { * * @see file_save() */ -function hook_file_update(&$file) { +function hook_file_update($file) { } @@ -2844,6 +3358,30 @@ function hook_date_format_types() { ); } +/** + * Modify existing date format types. + * + * Allows other modules to modify existing date types like 'long'. Called + * by _system_date_format_types_build(). For instance, A module may use this + * hook to apply settings across all date format types, such as locking all + * date format types so they appear to be provided by the system. + * + * @param $types + * An associative array of date format types containing: + * - types: An array of date format types including configuration settings + * for each type: + * - is_new: Set to FALSE to override previous settings. + * - module: The name of the module that created the date format type. + * - type: The date type name. + * - title: The title of the date type. + * - locked: Specifies that the date type is system-provided. + */ +function hook_date_format_types_alter(&$types) { + foreach ($types as $type_name => $type) { + $types[$type_name]['locked'] = 1; + } +} + /** * Defines additional date formats. * @@ -3275,6 +3813,66 @@ function hook_token_info_alter(&$data) { ); } + +/** + * Provide information on Updaters (classes that can update Drupal). + * + * An Updater is a class that knows how to update various parts of the Drupal + * file system, for example to update modules that have newer releases, or to + * install a new theme. + * + * @return + * An associative array of information about the updater(s) being provided. + * This array is keyed by a unique identifier for each updater, and the + * values are subarrays that can contain the following keys: + * - class: The name of the PHP class which implements this updater. + * - name: Human-readable name of this updater. + * - weight: Controls what order the Updater classes are consulted to decide + * which one should handle a given task. When an update task is being run, + * the system will loop through all the Updater classes defined in this + * registry in weight order and let each class respond to the task and + * decide if each Updater wants to handle the task. In general, this + * doesn't matter, but if you need to override an existing Updater, make + * sure your Updater has a lighter weight so that it comes first. + * + * @see drupal_get_updaters() + * @see hook_updater_info_alter() + */ +function hook_updater_info() { + return array( + 'module' => array( + 'class' => 'ModuleUpdater', + 'name' => t('Update modules'), + 'weight' => 0, + ), + 'theme' => array( + 'class' => 'ThemeUpdater', + 'name' => t('Update themes'), + 'weight' => 0, + ), + ); +} + +/** + * Alter the Updater information array. + * + * An Updater is a class that knows how to update various parts of the Drupal + * file system, for example to update modules that have newer releases, or to + * install a new theme. + * + * @param array $updaters + * Associative array of updaters as defined through hook_updater_info(). + * Alter this array directly. + * + * @see drupal_get_updaters() + * @see hook_updater_info() + */ +function hook_updater_info_alter(&$updaters) { + // Adjust weight so that the theme Updater gets a chance to handle a given + // update task before module updaters. + $updaters['theme']['weight'] = -1; +} + /** * Alter the default country list. * diff --git a/modules/system/system.css b/modules/system/system.css index a426d80b4..bdf9017de 100644 --- a/modules/system/system.css +++ b/modules/system/system.css @@ -1,4 +1,4 @@ -/* $Id: system.css,v 1.74 2010/04/01 14:47:16 dries Exp $ */ +/* $Id: system.css,v 1.76 2010/05/14 07:45:54 dries Exp $ */ /* ** HTML elements @@ -29,7 +29,8 @@ th { th.active img { display: inline; } -tr.even, tr.odd { +tr.even, +tr.odd { background-color: #eee; border-bottom: 1px solid #ccc; padding: 0.1em 0.6em; @@ -43,7 +44,8 @@ tr.drag-previous { td.active { background-color: #ddd; } -td.checkbox, th.checkbox { +td.checkbox, +th.checkbox { text-align: center; } tbody { @@ -86,18 +88,25 @@ thead th { margin-top: 1em; margin-bottom: 1em; } -tr.odd .form-item, tr.even .form-item { +tr.odd .form-item, +tr.even .form-item { margin-top: 0; margin-bottom: 0; white-space: nowrap; } -tr.merge-down, tr.merge-down td, tr.merge-down th { +tr.merge-down, +tr.merge-down td, +tr.merge-down th { border-bottom-width: 0 !important; } -tr.merge-up, tr.merge-up td, tr.merge-up th { +tr.merge-up, +tr.merge-up td, +tr.merge-up th { border-top-width: 0 !important; } -.form-item input.error, .form-item textarea.error, .form-item select.error { +.form-item input.error, +.form-item textarea.error, +.form-item select.error { border: 2px solid red; } .form-item .description { @@ -111,33 +120,40 @@ tr.merge-up, tr.merge-up td, tr.merge-up th { display: inline; font-weight: normal; } -.form-checkboxes, .form-radios { +.form-checkboxes, +.form-radios { margin: 1em 0; } -.form-checkboxes .form-item, .form-radios .form-item { +.form-checkboxes .form-item, +.form-radios .form-item { margin-top: 0.4em; margin-bottom: 0.4em; } -.form-type-radio .description, .form-type-checkbox .description { +.form-type-radio .description, +.form-type-checkbox .description { margin-left: 2.4em; } -input.form-checkbox, input.form-radio { +input.form-checkbox, +input.form-radio { vertical-align: middle; } -.marker, .form-required { +.marker, +.form-required { color: #f00; } .more-help-link { text-align: right; /* LTR */ } -.more-help-link a, a.module-link { +.more-help-link a, +a.module-link { padding: 1px 0 1px 20px; /* LTR */ } a.module-link { display: block; white-space: nowrap; } -.more-help-link a, a.module-link-help { +.more-help-link a, +a.module-link-help { background: url(../../misc/help.png) 0 50% no-repeat; /* LTR */ } a.module-link-permissions { @@ -238,6 +254,26 @@ tr.selected td { background: #ffc; } +/* +** To be used with displace.js +*/ +.displace-top, +.displace-bottom { + position: relative; + width: 100%; +} +.displace-processed .displace-top, +.displace-processed .displace-bottom { + position: fixed; + width: auto; + left: 0; + right: 0; +} +.displace-unsupported .displace-top, +.displace-unsupported .displace-bottom { + position: absolute; +} + /* ** Floating header for tableheader.js */ diff --git a/modules/system/system.info b/modules/system/system.info index 96ccc8132..b15f30f06 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -18,8 +18,8 @@ files[] = system.mail.inc required = TRUE configure = admin/config/system -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/system/system.install b/modules/system/system.install index 42922102c..923be4e9c 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -1,5 +1,5 @@ <?php -// $Id: system.install,v 1.461 2010/04/10 17:30:15 dries Exp $ +// $Id: system.install,v 1.469 2010/05/19 19:14:43 dries Exp $ /** * @file @@ -264,8 +264,10 @@ function system_requirements($phase) { // Test files directories. $directories = array( variable_get('file_public_path', conf_path() . '/files'), - variable_get('file_private_path', conf_path() . '/private/files'), - variable_get('file_temporary_path', conf_path() . '/private/temp'), + // By default no private files directory is configured. For private files + // to be secure the admin needs to provide a path outside the webroot. + variable_get('file_private_path', FALSE), + variable_get('file_temporary_path', sys_get_temp_dir()), ); $requirements['file system'] = array( 'title' => $t('File system'), @@ -274,6 +276,9 @@ function system_requirements($phase) { $error = ''; // For installer, create the directories if possible. foreach ($directories as $directory) { + if (!$directory) { + continue; + } if ($phase == 'install') { file_prepare_directory($directory, FILE_CREATE_DIRECTORY); } @@ -353,6 +358,62 @@ function system_requirements($phase) { $requirements['update access']['title'] = $t('Access to update.php'); } + // Display an error if a newly introduced dependency in a module is not resolved. + if ($phase == 'update') { + $files = system_rebuild_module_data(); + foreach ($files as $module => $file) { + // Ignore disabled modules. + if (!$file->status) { + continue; + } + // Check the module's PHP version. + $name = $file->info['name']; + $php = $file->info['php']; + if (version_compare($php, PHP_VERSION, '>')) { + $requirements['php']['description'] .= $t('@name requires at least PHP @version.', array('@name' => $name, '@version' => $php)); + $requirements['php']['severity'] = REQUIREMENT_ERROR; + } + // Check the module's required modules. + foreach ($file->requires as $requirement) { + $required_module = $requirement['name']; + // Check if the module exists. + if (!isset($files[$required_module])) { + $requirements["$module-$required_module"] = array( + 'title' => $t('Unresolved dependency'), + 'description' => $t('@name requires this module.', array('@name' => $name)), + 'value' => t('@required_name (Missing)', array('@required_name' => $required_module)), + 'severity' => REQUIREMENT_ERROR, + ); + continue; + } + // Check for an incompatible version. + $required_file = $files[$required_module]; + $required_name = $required_file->info['name']; + $version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $required_file->info['version']); + $compatibility = drupal_check_incompatibility($requirement, $version); + if ($compatibility) { + $compatibility = rtrim(substr($compatibility, 2), ')'); + $requirements["$module-$required_module"] = array( + 'title' => $t('Unresolved dependency'), + 'description' => $t('@name requires this module and version. Currently using @required_name version @version', array('@name' => $name, '@required_name' => $required_name, '@version' => $version)), + 'value' => t('@required_name (Version @compatibility required)', array('@required_name' => $required_name, '@compatibility' => $compatibility)), + 'severity' => REQUIREMENT_ERROR, + ); + continue; + } + // Check for a disabled dependency. + if (!$required_file->status) { + $requirements["$module-$required_module"] = array( + 'title' => $t('Unresolved dependency'), + 'description' => $t('@name requires this module.', array('@name' => $name)), + 'value' => $t('@required_name (Disabled)', array('@required_name' => $required_name)), + 'severity' => REQUIREMENT_ERROR, + ); + } + } + } + } + // Test Unicode library include_once DRUPAL_ROOT . '/includes/unicode.inc'; $requirements = array_merge($requirements, unicode_requirements()); @@ -414,7 +475,7 @@ function system_install() { ->execute(); // Populate the cron key variable. - $cron_key = md5(mt_rand()); + $cron_key = drupal_hash_base64(drupal_random_bytes(55)); variable_set('cron_key', $cron_key); } @@ -573,11 +634,6 @@ function system_schema() { 'not null' => TRUE, 'default' => 0, ), - 'headers' => array( - 'description' => 'Any custom HTTP headers to be added to cached data.', - 'type' => 'text', - 'not null' => FALSE, - ), 'serialized' => array( 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', 'type' => 'int', @@ -1513,107 +1569,9 @@ function system_schema() { // Updates for core. function system_update_last_removed() { - return 6047; -} - -/** - * @defgroup updates-6.x-extra Extra system updates for 6.x - * @{ - */ - -/** -* Increase the size of the 'load_functions' and 'to_arg_functions' fields in table 'menu_router'. -*/ -function system_update_6048() { - db_change_field('menu_router', 'load_functions', 'load_functions', array('type' => 'text', 'not null' => TRUE,)); - db_change_field('menu_router', 'to_arg_functions', 'to_arg_functions', array('type' => 'text', 'not null' => TRUE,)); -} - -/** - * Replace src index on the {url_alias} table with src, language. - */ -function system_update_6049() { - db_drop_index('url_alias', 'src'); - db_add_index('url_alias', 'src_language', array('src', 'language')); + return 6055; } -/** - * Clear any menu router blobs stored in the cache table. - */ -function system_update_6050() { - cache_clear_all('router:', 'cache_menu', TRUE); -} - -/** - * Create a signature_format column. - */ -function system_update_6051() { - - if (!db_field_exists('users', 'signature_format')) { - - // Set future text formats to FILTER_FORMAT_DEFAULT to ensure a safe default - // when incompatible modules insert into the users table. An actual format - // will be assigned when users save their signature. - - $schema = array( - 'type' => 'int', - 'size' => 'small', - 'not null' => TRUE, - 'default' => FILTER_FORMAT_DEFAULT, - 'description' => 'The {filter_formats}.format of the signature.', - ); - - db_add_field('users', 'signature_format', $schema); - - // Set the format of existing signatures to the current default text format. - if ($current_default_filter = variable_get('filter_default_format', 0)) { - db_update('users') - ->fields(array( - 'signature_format' => $current_default_filter, - )) - ->execute(); - } - - drupal_set_message("User signatures no longer inherit comment text formats. Each user's signature now has its own associated format that can be selected on the user's account page. Existing signatures have been set to your site's default text format."); - } -} - -/** - * Add a missing index on the {menu_router} table. - */ -function system_update_6052() { - db_add_index('menu_router', 'tab_root_weight_title', array(array('tab_root', 64), 'weight', 'title')); -} - -/** - * Add a {system} index on type and name. - */ -function system_update_6053() { - db_add_index('system', 'type_name', array(array('type', 12), 'name')); -} - -/** - * Improve indexes on the {url_alias} table. - */ -function system_update_6055() { - db_drop_index('url_alias', 'src_language'); - db_drop_unique_key('url_alias', 'dst_language'); - db_add_index('url_alias', 'src_language_pid', array('src', 'language', 'pid')); - db_add_unique_key('url_alias', 'dst_language_pid', array('dst', 'language', 'pid')); -} - -/** - * Add semaphore table. - */ -function system_update_6054() { - // Lives in update_fix_d7_requirements() -} - -/** - * @} End of "defgroup updates-6.x-extra" - * The next series of updates should start at 7000. - */ - /** * @defgroup updates-6.x-to-7.x System updates from 6.x to 7.x * @{ @@ -1650,7 +1608,7 @@ function system_update_7000() { * Generate a cron key and save it in the variables table. */ function system_update_7001() { - variable_set('cron_key', md5(mt_rand())); + variable_set('cron_key', drupal_hash_base64(drupal_random_bytes(55))); } /** @@ -2088,7 +2046,7 @@ function system_update_7032() { */ function system_update_7033() { if (variable_get('cache') == 2) { - variable_set('cache', CACHE_NORMAL); + variable_set('cache', 1); return t('Aggressive caching was disabled and replaced with normal caching. Read the page caching section in default.settings.php for more information on how to enable similar functionality.'); } } @@ -2397,6 +2355,37 @@ function system_update_7052() { db_change_field('menu_router', 'file', 'include_file', array('type' => 'text', 'size' => 'medium')); } +/** + * Upgrade standard blocks and menus. + */ +function system_update_7053() { + // Navigation block is now defined in system module. + db_update('block') + ->fields(array('module' => 'system')) + ->condition('module', 'user') + ->condition('delta', 'navigation') + ->execute(); + + // Create the same menus as in menu_install(). + db_insert('menu_custom') + ->fields(array('menu_name' => 'user-menu', 'title' => 'User Menu', 'description' => "The <em>User</em> menu contains links related to the user's account, as well as the 'Log out' link.")) + ->execute(); + + db_insert('menu_custom') + ->fields(array('menu_name' => 'management', 'title' => 'Management', 'description' => "The <em>Management</em> menu contains links for administrative tasks.")) + ->execute(); +} + +/** + * Remove {cache_*}.headers columns. + */ +function system_update_7054() { + $cache_tables = array('cache', 'cache_filter', 'cache_form', 'cache_menu', 'cache_page', 'cache_path'); + foreach ($cache_tables as $table) { + db_drop_field($table, 'headers'); + } +} + /** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. diff --git a/modules/system/system.module b/modules/system/system.module index 0819e63ef..031781023 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1,5 +1,5 @@ <?php -// $Id: system.module,v 1.923 2010/04/24 14:49:14 dries Exp $ +// $Id: system.module,v 1.933 2010/05/20 08:47:00 dries Exp $ /** * @file @@ -93,9 +93,9 @@ function system_help($path, $arg) { $output = '<p>' . t('Set and configure the default theme for your website. Alternative <a href="@themes">themes</a> are available.', array('@themes' => 'http://drupal.org/project/themes')) . '</p>'; return $output; case 'admin/appearance/settings/' . $arg[3]: - $reference = explode('.', $arg[3], 2); - $theme = array_pop($reference); - return '<p>' . t('These options control the display settings for the <code>%template</code> theme. When your site is displayed using this theme, these settings will be used.', array('%template' => $theme)) . '</p>'; + $theme_list = list_themes(); + $theme = $theme_list[$arg[3]]; + return '<p>' . t('These options control the display settings for the %name theme. When your site is displayed using this theme, these settings will be used.', array('%name' => $theme->info['name'])) . '</p>'; case 'admin/appearance/settings': return '<p>' . t('These options control the default display settings for your entire site, across all themes. Unless they have been overridden by a specific theme, these settings will be used.') . '</p>'; case 'admin/modules': @@ -217,7 +217,8 @@ function system_permission() { 'title' => t('Administer themes'), ), 'administer software updates' => array( - 'title' => t('Run software updates'), + 'title' => t('Administer software updates'), + 'restrict access' => TRUE, ), 'administer actions' => array( 'title' => t('Administer actions'), @@ -383,7 +384,6 @@ function system_element_info() { ); $types['checkboxes'] = array( '#input' => TRUE, - '#tree' => TRUE, '#process' => array('form_process_checkboxes'), '#theme_wrappers' => array('checkboxes'), '#pre_render' => array('form_pre_render_conditional_form_element'), @@ -957,7 +957,7 @@ function system_menu() { ); $items['admin/config/system/site-information'] = array( 'title' => 'Site information', - 'description' => 'Change basic site name, e-mail address, slogan, default front page, number of posts per page, error pages and cron.', + 'description' => 'Change site name, e-mail address, slogan, default front page, number of posts per page, error pages and cron.', 'page callback' => 'drupal_get_form', 'page arguments' => array('system_site_information_settings'), 'access arguments' => array('administer site configuration'), @@ -1008,7 +1008,7 @@ function system_menu() { 'title' => 'Status report', 'description' => "Get a status report about your site's operation and any detected problems.", 'page callback' => 'system_status', - 'weight' => 10, + 'weight' => -60, 'access arguments' => array('administer site configuration'), 'file' => 'system.admin.inc', ); @@ -1517,24 +1517,30 @@ function system_library() { * Implements hook_stream_wrappers(). */ function system_stream_wrappers() { - return array( + $wrappers = array( 'public' => array( 'name' => t('Public files'), 'class' => 'DrupalPublicStreamWrapper', 'description' => t('Public local files served by the webserver.'), ), - 'private' => array( - 'name' => t('Private files'), - 'class' => 'DrupalPrivateStreamWrapper', - 'description' => t('Private local files served by Drupal.'), - ), 'temporary' => array( 'name' => t('Temporary files'), 'class' => 'DrupalTemporaryStreamWrapper', 'description' => t('Temporary local files for upload and previews.'), 'type' => STREAM_WRAPPERS_HIDDEN, - ) + ), ); + + // Only register the private file stream wrapper if a file path has been set. + if (variable_get('file_private_path', FALSE)) { + $wrappers['private'] = array( + 'name' => t('Private files'), + 'class' => 'DrupalPrivateStreamWrapper', + 'description' => t('Private local files served by Drupal.'), + ); + } + + return $wrappers; } /** @@ -2046,6 +2052,9 @@ function system_admin_menu_block($item) { */ function system_check_directory($form_element) { $directory = $form_element['#value']; + if (strlen($directory) == 0) { + return $form_element; + } if (!is_dir($directory) && !drupal_mkdir($directory, NULL, TRUE)) { // If the directory does not exists and cannot be created. @@ -2058,7 +2067,7 @@ function system_check_directory($form_element) { form_set_error($form_element['#parents'][0], t('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory))); watchdog('file system', 'The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory), WATCHDOG_ERROR); } - else { + elseif (is_dir($directory)) { if ($form_element['#name'] == 'file_public_path') { // Create public .htaccess file. file_create_htaccess($directory, FALSE); @@ -2173,6 +2182,11 @@ function system_update_files_database(&$files, $type) { } } $query->execute(); + + // If any module or theme was moved to a new location, we need to reset the + // system_list() cache or we will continue to load the old copy, look for + // schema updates in the wrong place, etc. + system_list_reset(); } /** @@ -2542,7 +2556,7 @@ function system_system_info_alter(&$info, $file, $type) { * A string that is the region name. */ function system_default_region($theme) { - $regions = array_keys(system_region_list($theme)); + $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); return isset($regions[0]) ? $regions[0] : ''; } @@ -2635,41 +2649,46 @@ function _system_sort_requirements($a, $b) { } /** - * Output a confirmation form + * Generates a form array for a confirmation form. * - * This function returns a complete form for confirming an action. A link is - * offered to go back to the item that is being changed in case the user changes - * his/her mind. + * This function returns a complete form array for confirming an action. The + * form contains a confirm button as well as a cancellation link that allows a + * user to abort the action. * - * If the submit handler for this form is invoked, the user successfully - * confirmed the action. You should never directly inspect $_POST to see if an - * action was confirmed. + * If the submit handler for a form that implements confirm_form() is invoked, + * the user successfully confirmed the action. You should never directly + * inspect $_POST to see if an action was confirmed. * * Note - if the parameters $question, $description, $yes, or $no could contain * any user input (such as node titles or taxonomy terms), it is the * responsibility of the code calling confirm_form() to sanitize them first with * a function like check_plain() or filter_xss(). * - * @ingroup forms * @param $form - * Additional elements to inject into the form, for example hidden elements. + * Additional elements to add to the form; for example, hidden elements. * @param $question * The question to ask the user (e.g. "Are you sure you want to delete the - * block <em>foo</em>?"). + * block <em>foo</em>?"). The page title will be set to this value. * @param $path - * The page to go to if the user denies the action. - * Can be either a drupal path, or an array with the keys 'path', 'query', 'fragment'. + * The page to go to if the user cancels the action. This can be either: + * - A string containing a Drupal path. + * - An associative array with a 'path' key. Additional array values are + * passed as the $options parameter to l(). + * If the 'destination' query parameter is set in the URL when viewing a + * confirmation form, that value will be used instead of $path. * @param $description - * Additional text to display (defaults to "This action cannot be undone."). + * Additional text to display. Defaults to t('This action cannot be undone.'). * @param $yes - * A caption for the button which confirms the action (e.g. "Delete", - * "Replace", ...). + * A caption for the button that confirms the action (e.g. "Delete", + * "Replace", ...). Defaults to t('Confirm'). * @param $no - * A caption for the link which denies the action (e.g. "Cancel"). + * A caption for the link which cancels the action (e.g. "Cancel"). Defaults + * to t('Cancel'). * @param $name * The internal name used to refer to the confirmation item. + * * @return - * The form. + * The form array. */ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, $no = NULL, $name = 'confirm') { $description = isset($description) ? $description : t('This action cannot be undone.'); @@ -2918,9 +2937,9 @@ function system_send_email_action_form($context) { function system_send_email_action_validate($form, $form_state) { $form_values = $form_state['values']; // Validate the configuration form. - if (!valid_email_address($form_values['recipient']) && $form_values['recipient'] != '%author') { + if (!valid_email_address($form_values['recipient']) && strpos($form_values['recipient'], ':mail') === FALSE) { // We want the literal %author placeholder to be emphasized in the error message. - form_set_error('recipient', t('Enter a valid email address or %author.', array('%author' => '%author'))); + form_set_error('recipient', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]'))); } } diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index 4e5b3f227..60b8be890 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -1,5 +1,5 @@ <?php -// $Id: system.updater.inc,v 1.7 2010/04/10 09:49:49 dries Exp $ +// $Id: system.updater.inc,v 1.8 2010/05/18 18:11:13 dries Exp $ /** * @file @@ -53,8 +53,8 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { * Return available database schema updates one a new version is installed. */ public function getSchemaUpdates() { - require_once './includes/install.inc'; - require_once './includes/update.inc'; + require_once DRUPAL_ROOT . '/includes/install.inc'; + require_once DRUPAL_ROOT . '/includes/update.inc'; if (_update_get_project_type($project) != 'module') { return array(); diff --git a/modules/system/theme.api.php b/modules/system/theme.api.php index 14b9ffe73..9943e858b 100644 --- a/modules/system/theme.api.php +++ b/modules/system/theme.api.php @@ -1,5 +1,5 @@ <?php -// $Id: theme.api.php,v 1.2 2010/04/24 07:11:07 dries Exp $ +// $Id: theme.api.php,v 1.3 2010/04/28 20:00:34 dries Exp $ /** * @defgroup themeable Default theme implementations @@ -94,6 +94,108 @@ function hook_form_system_theme_settings_alter(&$form, &$form_state) { ); } +/** + * Preprocess theme variables. + * + * This hook allows modules to preprocess theme variables for theme templates. + * It is called for all invocations of theme(), to allow modules to add to + * or override variables for all theme hooks. + * + * @param $variables + * The variables array (modify in place). + * @param $hook + * The name of the theme hook. + */ +function hook_preprocess(&$variables, $hook) { + static $hooks; + + // Add contextual links to the variables, if the user has permission. + + if (!user_access('access contextual links')) { + return; + } + + if (!isset($hooks)) { + $hooks = theme_get_registry(); + } + + // Determine the primary theme function argument. + if (isset($hooks[$hook]['variables'])) { + $keys = array_keys($hooks[$hook]['variables']); + $key = $keys[0]; + } + else { + $key = $hooks[$hook]['render element']; + } + + if (isset($variables[$key])) { + $element = $variables[$key]; + } + + if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) { + $variables['title_suffix']['contextual_links'] = contextual_links_view($element); + if (!empty($variables['title_suffix']['contextual_links'])) { + $variables['classes_array'][] = 'contextual-links-region'; + } + } +} + +/** + * Preprocess theme variables for a specific theme hook. + * + * This hook allows modules to preprocess theme variables for a specific theme + * hook. It should only be used if a module needs to override or add to the + * theme preprocessing for a theme hook it didn't define. + * + * @param $variables + * The variables array (modify in place). + */ +function hook_preprocess_HOOK(&$variables) { + // This example is from rdf_preprocess_image(). It adds an RDF attribute + // to the image hook's variables. + $variables['attributes']['typeof'] = array('foaf:Image'); +} + +/** + * Process theme variables. + * + * This hook allows modules to process theme variables for theme templates. + * It is called for all invocations of theme(), to allow modules to add to + * or override variables for all theme hooks. + * + * @param $variables + * The variables array (modify in place). + * @param $hook + * The name of the theme hook. + */ +function hook_process(&$variables, $hook) { + // Wraps variables in RDF wrappers. + if (!empty($variables['rdf_template_variable_attributes_array'])) { + foreach ($variables['rdf_template_variable_attributes_array'] as $variable_name => $attributes) { + $context = array( + 'hook' => $hook, + 'variable_name' => $variable_name, + 'variables' => $variables, + ); + $variables[$variable_name] = theme('rdf_template_variable_wrapper', array('content' => $variables[$variable_name], 'attributes' => $attributes, 'context' => $context)); + } + } +} + +/** + * Process theme variables for a specific theme hook. + * + * This hook allows modules to process theme variables for a specific theme + * hook. It should only be used if a module needs to override or add to the + * theme processing for a theme hook it didn't define. + * + * @param $variables + * The variables array (modify in place). + */ +function hook_process_HOOK(&$variables) { + $variables['classes'] .= ' my_added_class'; +} + /** * Respond to themes being enabled. * diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc index 2a218661d..bba7f8593 100644 --- a/modules/taxonomy/taxonomy.admin.inc +++ b/modules/taxonomy/taxonomy.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.admin.inc,v 1.102 2010/04/24 14:49:14 dries Exp $ +// $Id: taxonomy.admin.inc,v 1.105 2010/05/13 07:53:02 dries Exp $ /** * @file @@ -101,7 +101,7 @@ function theme_taxonomy_overview_vocabularies($variables) { */ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { if (!is_array($edit)) { - $edit = (array)$edit; + $edit = (array) $edit; } $edit += array( 'name' => '', @@ -121,7 +121,7 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { '#default_value' => $edit['name'], '#maxlength' => 255, '#required' => TRUE, - '#field_suffix' => ' <small id="edit-name-suffix"> </small>', + '#field_suffix' => ' <small id="edit-name-suffix"> </small>', ); $js_settings = array( 'type' => 'setting', @@ -352,7 +352,7 @@ function taxonomy_overview_terms($form, &$form_state, $vocabulary) { // Build the actual form. foreach ($current_page as $key => $term) { // Save the term for the current page so we don't have to load it a second time. - $form[$key]['#term'] = (array)$term; + $form[$key]['#term'] = (array) $term; if (isset($term->parents)) { $form[$key]['#term']['parent'] = $term->parent = $term->parents[0]; unset($form[$key]['#term']['parents'], $term->parents); @@ -444,7 +444,7 @@ function taxonomy_overview_terms_submit($form, &$form_state) { // Build a list of all terms that need to be updated on previous pages. $weight = 0; - $term = (array)$tree[0]; + $term = (array) $tree[0]; while ($term['tid'] != $form['#first_tid']) { if ($term['parents'][0] == 0 && $term['weight'] != $weight) { $term['parent'] = $term['parents'][0]; @@ -453,7 +453,7 @@ function taxonomy_overview_terms_submit($form, &$form_state) { } $weight++; $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy; - $term = (array)$tree[$weight]; + $term = (array) $tree[$weight]; } // Renumber the current page weights and assign any new parents. @@ -486,7 +486,7 @@ function taxonomy_overview_terms_submit($form, &$form_state) { // Build a list of all terms that need to be updated on following pages. for ($weight; $weight < count($tree); $weight++) { - $term = (array)$tree[$weight]; + $term = (array) $tree[$weight]; if ($term['parents'][0] == 0 && $term['weight'] != $weight) { $term['parent'] = $term['parents'][0]; $term['weight'] = $weight; @@ -497,7 +497,7 @@ function taxonomy_overview_terms_submit($form, &$form_state) { // Save all updated terms. foreach ($changed_terms as $changed) { - $term = (object)$changed; + $term = (object) $changed; // Update term_hierachy and term_data directly since we don't have a // fully populated term object to save. db_update('taxonomy_term_hierarchy') @@ -619,7 +619,7 @@ function theme_taxonomy_overview_terms($variables) { function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) { if (!isset($vocabulary) && is_object($edit)) { $vocabulary = taxonomy_vocabulary_load($edit->vid); - $edit = (array)$edit; + $edit = (array) $edit; } $edit += array( 'name' => '', @@ -837,9 +837,15 @@ function taxonomy_form_term_submit($form, &$form_state) { */ function taxonomy_form_term_submit_builder($form, &$form_state) { $term = (object) $form_state['values']; + + // Convert text_format field into values expected by taxonomy_term_save(). + $description = $form_state['values']['description']; + $term->description = $description['value']; + $term->format = $description['format']; + field_attach_submit('taxonomy_term', $term, $form, $form_state); - $form_state['term'] = (array)$term; + $form_state['term'] = (array) $term; $form_state['rebuild'] = TRUE; return $term; diff --git a/modules/taxonomy/taxonomy.api.php b/modules/taxonomy/taxonomy.api.php index b0b2437fe..11f45d59b 100644 --- a/modules/taxonomy/taxonomy.api.php +++ b/modules/taxonomy/taxonomy.api.php @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.api.php,v 1.8 2009/12/04 15:54:37 dries Exp $ +// $Id: taxonomy.api.php,v 1.9 2010/05/14 04:41:54 webchick Exp $ /** * @file @@ -94,6 +94,19 @@ function hook_taxonomy_term_load($terms) { } } +/** + * Act on taxonomy terms before they are saved. + * + * Modules implementing this hook can act on the term object before it is + * inserted or updated. + * + * @param $term + * A term object. + */ +function hook_taxonomy_term_presave($term) { + $term->foo = 'bar'; +} + /** * Act on taxonomy terms when inserted. * diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index 6941518b5..4c4458070 100644 --- a/modules/taxonomy/taxonomy.info +++ b/modules/taxonomy/taxonomy.info @@ -12,8 +12,8 @@ files[] = taxonomy.test files[] = taxonomy.tokens.inc configure = admin/structure/taxonomy -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/taxonomy/taxonomy.install b/modules/taxonomy/taxonomy.install index 62a2bc34e..31a1d5514 100644 --- a/modules/taxonomy/taxonomy.install +++ b/modules/taxonomy/taxonomy.install @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.install,v 1.41 2010/04/20 07:47:55 webchick Exp $ +// $Id: taxonomy.install,v 1.42 2010/05/06 06:08:28 webchick Exp $ /** * @file @@ -260,7 +260,10 @@ function taxonomy_update_7002() { db_add_field('taxonomy_vocabulary', 'machine_name', $field); - foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) { + // Do a direct query here, rather than calling taxonomy_get_vocabularies(), + // in case Taxonomy module is disabled. + $vids = db_query('SELECT vid FROM {taxonomy_vocabulary}')->fetchCol(); + foreach ($vids as $vid) { $machine_name = 'vocabulary_' . $vid; db_update('taxonomy_vocabulary') ->fields(array('machine_name' => $machine_name)) diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module index d16d2aa14..7762e7e99 100644 --- a/modules/taxonomy/taxonomy.module +++ b/modules/taxonomy/taxonomy.module @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.module,v 1.587 2010/04/23 07:54:44 webchick Exp $ +// $Id: taxonomy.module,v 1.591 2010/05/21 20:27:45 dries Exp $ /** * @file @@ -143,9 +143,9 @@ function taxonomy_term_uri($term) { */ function taxonomy_field_extra_fields() { $return = array(); - - foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) { - $return['taxonomy_term'][$machine_name] = array( + $info = entity_get_info('taxonomy_term'); + foreach (array_keys($info['bundles']) as $bundle) { + $return['taxonomy_term'][$bundle] = array( 'name' => array( 'label' => t('Name'), 'description' => t('Term name textfield'), @@ -432,7 +432,7 @@ function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) { foreach ($tree as $term) { // Update the changed term with the new parent value before comparison. if ($term->tid == $changed_term['tid']) { - $term = (object)$changed_term; + $term = (object) $changed_term; $term->parents = $term->parent; } // Check this term's parent count. @@ -469,6 +469,7 @@ function taxonomy_term_save($term) { } field_attach_presave('taxonomy_term', $term); + module_invoke_all('taxonomy_term_presave', $term); if (empty($term->tid)) { $status = drupal_write_record('taxonomy_term_data', $term); @@ -565,7 +566,6 @@ function taxonomy_term_delete($tid) { return SAVED_DELETED; } - /** * Generate an array for rendering the given term. * @@ -615,7 +615,7 @@ function template_preprocess_taxonomy_term(&$variables) { $variables['page'] = taxonomy_term_is_page($term); // Flatten the term object's member fields. - $variables = array_merge((array)$term, $variables); + $variables = array_merge((array) $term, $variables); // Helpful $content variable for templates. foreach (element_children($variables['elements']) as $key) { @@ -1018,13 +1018,15 @@ function taxonomy_implode_tags($tags, $vid = NULL) { foreach ($tags as $tag) { // Extract terms belonging to the vocabulary in question. if (is_null($vid) || $tag->vid == $vid) { + // Make sure we have a completed loaded taxonomy term. + if (isset($tag->name)) { + // Commas and quotes in tag names are special cases, so encode 'em. + if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { + $tag->name = '"' . str_replace('"', '""', $tag->name) . '"'; + } - // Commas and quotes in tag names are special cases, so encode 'em. - if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { - $tag->name = '"' . str_replace('"', '""', $tag->name) . '"'; + $typed_tags[] = $tag->name; } - - $typed_tags[] = $tag->name; } } return implode(', ', $typed_tags); @@ -1273,6 +1275,8 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, // Iterate through the fieldable entities again to attach the loaded term data. foreach ($entities as $id => $entity) { + $rekey = FALSE; + foreach ($items[$id] as $delta => $item) { // Check whether the taxonomy term field instance value could be loaded. if (isset($terms[$item['tid']])) { @@ -1282,8 +1286,14 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, // Otherwise, unset the instance value, since the term does not exist. else { unset($items[$id][$delta]); + $rekey = TRUE; } } + + if ($rekey) { + // Rekey the items array. + $items[$id] = array_values($items[$id]); + } } } } diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index ac8ef0d1b..cbd659ee0 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.test,v 1.77 2010/04/20 09:48:06 webchick Exp $ +// $Id: taxonomy.test,v 1.78 2010/04/30 12:52:10 dries Exp $ /** * @file @@ -422,7 +422,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { /** * Test term creation with a free-tagging vocabulary from the node form. */ - function testNodeTermCreation() { + function testNodeTermCreationAndDeletion() { // Enable tags in the vocabulary. $instance = $this->instance; $instance['widget'] = array('type' => 'taxonomy_autocomplete'); @@ -446,6 +446,23 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { foreach ($terms as $term) { $this->assertText($term, t('The term was saved and appears on the node page')); } + + // Get the created terms. + list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid); + + // Delete one term. + $this->drupalPost('taxonomy/term/' . $term1->tid . '/edit', array(), t('Delete')); + $this->drupalPost(NULL, NULL, t('Delete')); + $term_names = array($term2->name, $term3->name); + + // Get the node. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->drupalGet('node/' . $node->nid); + + foreach ($term_names as $term_name) { + $this->assertText($term_name, t('The term %name appears on the node page after one term %deleted was deleted', array('%name' => $term_name, '%deleted' => $term1->name))); + } + $this->assertNoText($term1->name, t('The deleted term %name does not appear on the node page.', array('%name' => $term1->name))); } /** diff --git a/modules/toolbar/toolbar.css b/modules/toolbar/toolbar.css index 8ccfef2d7..f276c59c0 100644 --- a/modules/toolbar/toolbar.css +++ b/modules/toolbar/toolbar.css @@ -1,12 +1,6 @@ -/* $Id: toolbar.css,v 1.20 2010/04/04 17:11:20 dries Exp $ */ +/* $Id: toolbar.css,v 1.21 2010/05/14 07:45:54 dries Exp $ */ -body.toolbar { - padding-top: 2.2em; -} -body.toolbar-drawer { - padding-top: 5.3em; -} /** * Aggressive resets so we can achieve a consistent look in hostile CSS @@ -32,10 +26,8 @@ body.toolbar-drawer { font: normal 0.9em "Lucida Grande", Verdana, sans-serif; background: #666; color: #ccc; - position: fixed; - top: 0; - left: 0; - right: 0; +} +.displace-processed #toolbar { margin: 0 -20px; padding: 0 20px; z-index: 600; @@ -45,6 +37,14 @@ body.toolbar-drawer { filter: progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10'); -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10')"; } +.displace-unsupported #toolbar { + margin: 0; + padding-right: 0; + left: -20px; + right: 0; + width: 100%; +} + #toolbar div.collapsed { display: none; @@ -132,18 +132,3 @@ body.toolbar-drawer { position: relative; padding: 0 10px; } - -/** - * IE 6 Fix. - * - * IE 6 shows elements with position:fixed as position:static so we replace - * it with position:absolute; toolbar needs it's z-index to stay above overlay. - */ -* html #toolbar { - position: absolute; - margin: 0; - padding-right: 0; - left: -20px; - right: 0; - width: 100%; -} diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info index b3560833b..a14c51536 100644 --- a/modules/toolbar/toolbar.info +++ b/modules/toolbar/toolbar.info @@ -6,8 +6,8 @@ package = Core version = VERSION files[] = toolbar.module -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/toolbar/toolbar.js b/modules/toolbar/toolbar.js index 09fd86537..5fb9ad6c1 100644 --- a/modules/toolbar/toolbar.js +++ b/modules/toolbar/toolbar.js @@ -1,4 +1,4 @@ -// $Id: toolbar.js,v 1.16 2010/04/04 20:27:08 dries Exp $ +// $Id: toolbar.js,v 1.17 2010/05/14 07:45:54 dries Exp $ (function ($) { Drupal.toolbar = Drupal.toolbar || {}; @@ -15,9 +15,8 @@ Drupal.behaviors.toolbar = { // Toggling toolbar drawer. $('#toolbar a.toggle', context).once('toolbar-toggle').click(function(e) { Drupal.toolbar.toggle(); - // As the toolbar is an overlay displaced region, overlay should be - // notified of it's height change to adapt its position. - $(window).triggerHandler('resize.overlay-event'); + // Allow resize event handlers to recalculate sizes/positions. + $(window).triggerHandler('resize'); return false; }); } @@ -49,7 +48,6 @@ Drupal.toolbar.collapse = function() { .removeClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); - $('body').removeClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 1, @@ -71,7 +69,6 @@ Drupal.toolbar.expand = function() { .addClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); - $('body').addClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 0, @@ -95,14 +92,4 @@ Drupal.toolbar.toggle = function() { } }; -Drupal.toolbar.height = function() { - var height = $('#toolbar').outerHeight(); - // In IE, Shadow filter adds some extra height, so we need to remove it from - // the returned height. - if ($('#toolbar').css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) { - height -= $('#toolbar').get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength; - } - return height; -}; - })(jQuery); diff --git a/modules/toolbar/toolbar.module b/modules/toolbar/toolbar.module index 588eac8fd..3f978a539 100644 --- a/modules/toolbar/toolbar.module +++ b/modules/toolbar/toolbar.module @@ -1,5 +1,5 @@ <?php -// $Id: toolbar.module,v 1.36 2010/04/04 17:11:20 dries Exp $ +// $Id: toolbar.module,v 1.39 2010/05/14 16:52:27 dries Exp $ /** * @file @@ -153,12 +153,15 @@ function toolbar_preprocess_html(&$vars) { /** * Implements hook_system_info_alter(). * - * If the overlay module is enabled, indicate that the 'page_top' region (in - * which the toolbar will be displayed) is one of the overlay supplemental - * regions that should be refreshed whenever its content is updated. + * Indicate that the 'page_top' region (in which the toolbar will be displayed) + * is an overlay supplemental region that should be refreshed whenever its + * content is updated. + * + * This information is provided for any module that might need to use it, not + * just the core Overlay module. */ function toolbar_system_info_alter(&$info, $file, $type) { - if (module_exists('overlay') && $type == 'theme') { + if ($type == 'theme') { $info['overlay_supplemental_regions'][] = 'page_top'; } } @@ -174,12 +177,9 @@ function toolbar_view() { '#theme' => 'toolbar', '#attached'=> array( 'js' => array( - $module_path . '/toolbar.js', + array('data' => 'misc/displace.js', 'weight' => JS_LIBRARY - 1), array('data' => 'misc/jquery.cookie.js', 'weight' => JS_LIBRARY + 2), - array( - 'data' => array('tableHeaderOffset' => 'Drupal.toolbar.height'), - 'type' => 'setting' - ), + $module_path . '/toolbar.js', ), 'css' => array( $module_path . '/toolbar.css', @@ -282,14 +282,13 @@ function toolbar_get_menu_tree() { /** * Generate a links array from a menu tree array. * - * Based on menu_navigation_links(). Adds in path based IDs, icon placeholders - * and overlay classes for the links. + * Based on menu_navigation_links(). Adds path based IDs and icon placeholders + * to the links. */ function toolbar_menu_navigation_links($tree) { $links = array(); foreach ($tree as $item) { if (!$item['link']['hidden'] && $item['link']['access']) { - $class = ''; // Make sure we have a path specific ID in place, so we can attach icons // and behaviors to the items. $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['href']); @@ -298,8 +297,8 @@ function toolbar_menu_navigation_links($tree) { $link['href'] = $item['link']['href']; // Add icon placeholder. $link['title'] = '<span class="icon"></span>' . $item['link']['title']; - // Add admin link ID and to-overlay class for the overlay. - $link['attributes'] = array('id' => 'toolbar-link-' . $id, 'class' => array('to-overlay')); + // Add admin link ID. + $link['attributes'] = array('id' => 'toolbar-link-' . $id); if (!empty($item['link']['description'])) { $link['title'] .= ' <span class="element-invisible">(' . $item['link']['description'] . ')</span>'; $link['attributes']['title'] = $item['link']['description']; diff --git a/modules/toolbar/toolbar.tpl.php b/modules/toolbar/toolbar.tpl.php index 951a5f00b..8f96bbd65 100644 --- a/modules/toolbar/toolbar.tpl.php +++ b/modules/toolbar/toolbar.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: toolbar.tpl.php,v 1.9 2010/02/10 10:54:12 dries Exp $ +// $Id: toolbar.tpl.php,v 1.10 2010/05/14 07:45:54 dries Exp $ /** * @file @@ -22,7 +22,7 @@ * @see template_preprocess_toolbar() */ ?> -<div id="toolbar" class="<?php print $classes; ?> clearfix"> +<div id="toolbar" class="<?php print $classes; ?> displace-top clearfix"> <div class="toolbar-menu clearfix"> <?php print render($toolbar['toolbar_home']); ?> <?php print render($toolbar['toolbar_user']); ?> diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info index 76bea25c3..2e11e659f 100644 --- a/modules/tracker/tracker.info +++ b/modules/tracker/tracker.info @@ -9,8 +9,8 @@ files[] = tracker.module files[] = tracker.pages.inc files[] = tracker.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index d52de0610..706d1e735 100644 --- a/modules/translation/translation.info +++ b/modules/translation/translation.info @@ -9,8 +9,8 @@ files[] = translation.module files[] = translation.pages.inc files[] = translation.test -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index 5c870488e..dde61ad2b 100644 --- a/modules/trigger/tests/trigger_test.info +++ b/modules/trigger/tests/trigger_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = trigger_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/trigger/trigger.admin.inc b/modules/trigger/trigger.admin.inc index fa87afc12..c9c02115a 100644 --- a/modules/trigger/trigger.admin.inc +++ b/modules/trigger/trigger.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: trigger.admin.inc,v 1.25 2010/04/21 08:23:42 webchick Exp $ +// $Id: trigger.admin.inc,v 1.26 2010/05/01 08:12:23 dries Exp $ /** * @file @@ -155,10 +155,11 @@ function trigger_assign_form($form, $form_state, $module, $hook, $label) { foreach ($actions as $aid => $info) { // If action is defined unassign it, otherwise offer to delete all orphaned // actions. - if (actions_function_lookup(md5($aid))) { + $hash = drupal_hash_base64($aid, TRUE); + if (actions_function_lookup($hash)) { $form[$hook]['assigned']['#value'][$aid] = array( 'label' => $info['label'], - 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$module/$hook/" . md5($aid)), + 'link' => l(t('unassign'), "admin/structure/trigger/unassign/$module/$hook/$hash"), ); } else { diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index da4cada48..19bc329a2 100644 --- a/modules/trigger/trigger.info +++ b/modules/trigger/trigger.info @@ -10,8 +10,8 @@ files[] = trigger.install files[] = trigger.test configure = admin/structure/trigger -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test index 408eb2b3e..d9af9c363 100644 --- a/modules/trigger/trigger.test +++ b/modules/trigger/trigger.test @@ -1,5 +1,5 @@ <?php -// $Id: trigger.test,v 1.31 2010/04/10 10:49:15 dries Exp $ +// $Id: trigger.test,v 1.32 2010/05/01 08:12:23 dries Exp $ /** * Class with common helper methods. @@ -19,7 +19,7 @@ class TriggerWebTestCase extends DrupalWebTestCase { */ protected function configureAdvancedAction($action, $edit) { // Create an advanced action. - $hash = md5($action); + $hash = drupal_hash_base64($action); $this->drupalPost("admin/config/system/actions/configure/$hash", $edit, t('Save')); $this->assertText(t('The action has been successfully saved.')); @@ -58,7 +58,7 @@ class TriggerContentTestCase extends TriggerWebTestCase { $test_user = $this->drupalCreateUser(array('administer actions')); $web_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer nodes')); foreach ($content_actions as $action) { - $hash = md5($action); + $hash = drupal_hash_base64($action); $info = $this->actionInfo($action); // Assign an action to a trigger, then pull the trigger, and make sure @@ -112,7 +112,7 @@ class TriggerContentTestCase extends TriggerWebTestCase { } $action_id = 'trigger_test_generic_any_action'; - $hash = md5($action_id); + $hash = drupal_hash_base64($action_id); $edit = array('aid' => $hash); $this->drupalPost('admin/structure/trigger/node', $edit, t('Assign'), array(), array(), 'trigger-node-update-assign-form'); @@ -200,7 +200,7 @@ class TriggerCronTestCase extends TriggerWebTestCase { $this->drupalLogin($test_user); // Assign a non-configurable action to the cron run trigger. - $edit = array('aid' => md5('trigger_test_system_cron_action')); + $edit = array('aid' => drupal_hash_base64('trigger_test_system_cron_action')); $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'), array(), array(), 'trigger-cron-assign-form'); // Assign a configurable action to the cron trigger. @@ -212,7 +212,7 @@ class TriggerCronTestCase extends TriggerWebTestCase { $aid = $this->configureAdvancedAction('trigger_test_system_cron_conf_action', $edit); // $aid is likely 3 but if we add more uses for the sequences table in // core it might break, so it is easier to get the value from the database. - $edit = array('aid' => md5($aid)); + $edit = array('aid' => drupal_hash_base64($aid)); $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'), array(), array(), 'trigger-cron-assign-form'); // Add a second configurable action to the cron trigger. @@ -222,7 +222,7 @@ class TriggerCronTestCase extends TriggerWebTestCase { 'subject' => $action_label, ); $aid = $this->configureAdvancedAction('trigger_test_system_cron_conf_action', $edit); - $edit = array('aid' => md5($aid)); + $edit = array('aid' => drupal_hash_base64($aid)); $this->drupalPost('admin/structure/trigger/system', $edit, t('Assign'), array(), array(), 'trigger-cron-assign-form'); // Force a cron run. @@ -265,7 +265,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { $test_user = $this->drupalCreateUser(array('administer actions')); $this->drupalLogin($test_user); $action_id = 'trigger_test_generic_action'; - $hash = md5($action_id); + $hash = drupal_hash_base64($action_id); $edit = array('aid' => $hash); $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-insert-assign-form'); @@ -300,7 +300,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { // Configure an advanced action that we can assign. $aid = $this->configureAdvancedAction('system_message_action', $action_edit); - $edit = array('aid' => md5($aid)); + $edit = array('aid' => drupal_hash_base64($aid)); $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-login-assign-form'); // Verify that the action has been assigned to the correct hook. @@ -322,7 +322,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { $test_user = $this->drupalCreateUser(array('administer actions')); $this->drupalLogin($test_user); $action_id = 'trigger_test_generic_action'; - $hash = md5($action_id); + $hash = drupal_hash_base64($action_id); $edit = array('aid' => $hash); $this->drupalPost('admin/structure/trigger/comment', $edit, t('Assign'), array(), array(), 'trigger-comment-insert-assign-form'); @@ -351,7 +351,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { $test_user = $this->drupalCreateUser(array('administer actions')); $this->drupalLogin($test_user); $action_id = 'trigger_test_generic_action'; - $hash = md5($action_id); + $hash = drupal_hash_base64($action_id); $edit = array('aid' => $hash); $this->drupalPost('admin/structure/trigger/taxonomy', $edit, t('Assign'), array(), array(), 'trigger-taxonomy-term-insert-assign-form'); @@ -403,7 +403,7 @@ class TriggerOrphanedActionsTestCase extends DrupalWebTestCase { */ function testActionsOrphaned() { $action = 'trigger_test_generic_any_action'; - $hash = md5($action); + $hash = drupal_hash_base64($action); // Assign an action from a disable-able module to a trigger, then pull the // trigger, and make sure the actions fire. diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index 590e88c63..3446b3c83 100644 --- a/modules/update/tests/aaa_update_test.info +++ b/modules/update/tests/aaa_update_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = aaa_update_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index 6c3f6a5e7..9506f04d6 100644 --- a/modules/update/tests/bbb_update_test.info +++ b/modules/update/tests/bbb_update_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = bbb_update_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index b6f29dd43..5627ee6b8 100644 --- a/modules/update/tests/ccc_update_test.info +++ b/modules/update/tests/ccc_update_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = ccc_update_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index 87b4cf889..fde942338 100644 --- a/modules/update/tests/update_test.info +++ b/modules/update/tests/update_test.info @@ -7,8 +7,8 @@ core = 7.x files[] = update_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/update/update.api.php b/modules/update/update.api.php index d8ee6e2b6..c1b251578 100644 --- a/modules/update/update.api.php +++ b/modules/update/update.api.php @@ -1,5 +1,5 @@ <?php -// $Id: update.api.php,v 1.4 2009/08/22 14:34:23 webchick Exp $ +// $Id: update.api.php,v 1.5 2010/04/30 16:27:02 webchick Exp $ /** * @file @@ -104,6 +104,29 @@ function hook_update_status_alter(&$projects) { } } +/** + * Verify an archive after it has been downloaded and extracted. + * + * @param string $project + * The short name of the project that has been downloaded. + * @param string $archive_file + * The filename of the unextracted archive. + * @param string $directory + * The directory that the archive was extracted into. + * + * @return + * If there is a problem, return any non-null value. If there is no problem, + * don't return anything (null). + * + * @see update_manager_archive_verify() + */ +function hook_verify_update_archive($project, $archive_file, $directory) { + if (!file_exists($directory)) { + return TRUE; + } + // Add other checks on the archive integrity here. +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/update/update.css b/modules/update/update.css index a6011ca26..f19db9b64 100644 --- a/modules/update/update.css +++ b/modules/update/update.css @@ -1,4 +1,4 @@ -/* $Id: update.css,v 1.6 2009/10/15 21:19:31 webchick Exp $ */ +/* $Id: update.css,v 1.7 2010/04/28 20:08:39 dries Exp $ */ .update .project { font-weight: bold; @@ -52,7 +52,8 @@ background: #ffe; } -.current-version, .new-version { +.current-version, +.new-version { direction: ltr; /* Note: version numbers should always be LTR. */ } diff --git a/modules/update/update.fetch.inc b/modules/update/update.fetch.inc index 7944828ae..63847d26d 100644 --- a/modules/update/update.fetch.inc +++ b/modules/update/update.fetch.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.fetch.inc,v 1.26 2009/12/29 07:21:34 webchick Exp $ +// $Id: update.fetch.inc,v 1.28 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -137,7 +137,7 @@ function _update_process_fetch_task($project) { $success = FALSE; $available = array(); - $site_key = md5($base_url . drupal_get_private_key()); + $site_key = drupal_hmac_base64($base_url, drupal_get_private_key()); $url = _update_build_fetch_url($project, $site_key); $fetch_url_base = _update_get_fetch_url_base($project); $project_name = $project['name']; @@ -361,26 +361,26 @@ function update_parse_xml($raw_xml) { if (!isset($xml->short_name)) { return; } - $short_name = (string)$xml->short_name; + $short_name = (string) $xml->short_name; $data = array(); foreach ($xml as $k => $v) { - $data[$k] = (string)$v; + $data[$k] = (string) $v; } $data['releases'] = array(); if (isset($xml->releases)) { foreach ($xml->releases->children() as $release) { - $version = (string)$release->version; + $version = (string) $release->version; $data['releases'][$version] = array(); foreach ($release->children() as $k => $v) { - $data['releases'][$version][$k] = (string)$v; + $data['releases'][$version][$k] = (string) $v; } $data['releases'][$version]['terms'] = array(); if ($release->terms) { foreach ($release->terms->children() as $term) { - if (!isset($data['releases'][$version]['terms'][(string)$term->name])) { - $data['releases'][$version]['terms'][(string)$term->name] = array(); + if (!isset($data['releases'][$version]['terms'][(string) $term->name])) { + $data['releases'][$version]['terms'][(string) $term->name] = array(); } - $data['releases'][$version]['terms'][(string)$term->name][] = (string)$term->value; + $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value; } } } diff --git a/modules/update/update.info b/modules/update/update.info index 0ff0012b3..171fa18a6 100644 --- a/modules/update/update.info +++ b/modules/update/update.info @@ -15,8 +15,8 @@ files[] = update.settings.inc files[] = update.test configure = admin/reports/updates/settings -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/update/update.install b/modules/update/update.install index 50a68ebe7..242b04d3c 100644 --- a/modules/update/update.install +++ b/modules/update/update.install @@ -1,5 +1,5 @@ <?php -// $Id: update.install,v 1.16 2010/01/15 10:12:36 dries Exp $ +// $Id: update.install,v 1.18 2010/05/19 19:14:43 dries Exp $ /** * @file @@ -167,3 +167,10 @@ function update_update_7000() { $queue = DrupalQueue::get('update_fetch_tasks'); $queue->createQueue(); } + +/** + * Remove {cache_update}.headers column. + */ +function update_update_7001() { + db_drop_field('cache_update', 'headers'); +} diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index c9517fceb..8dbef1545 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.manager.inc,v 1.21 2010/04/24 14:49:14 dries Exp $ +// $Id: update.manager.inc,v 1.22 2010/05/14 04:50:18 webchick Exp $ /** * @file @@ -635,7 +635,7 @@ function update_manager_install_form_submit($form, &$form_state) { function _update_manager_extract_directory() { $directory = &drupal_static(__FUNCTION__, ''); if (empty($directory)) { - $directory = DRUPAL_ROOT . '/' . file_directory_path('temporary') . '/update-extraction'; + $directory = 'temporary://update-extraction'; if (!file_exists($directory)) { mkdir($directory); } @@ -708,8 +708,8 @@ function update_manager_file_get($url) { } // Check the cache and download the file if needed. - $local = 'temporary://update-cache/' . basename($parsed_url['path']); - $cache_directory = DRUPAL_ROOT . '/' . file_directory_path('temporary') . '/update-cache/'; + $cache_directory = 'temporary://update-cache'; + $local = $cache_directory . '/' . basename($parsed_url['path']); if (!file_exists($cache_directory)) { mkdir($cache_directory); diff --git a/modules/update/update.module b/modules/update/update.module index 6920bc942..746ec24a5 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -1,5 +1,5 @@ <?php -// $Id: update.module,v 1.65 2010/04/13 15:23:03 dries Exp $ +// $Id: update.module,v 1.67 2010/05/18 18:26:30 dries Exp $ /** * @file @@ -159,7 +159,7 @@ function update_menu() { 'description' => 'Get a status report about available updates for your installed modules and themes.', 'page callback' => 'update_status', 'access arguments' => array('administer site configuration'), - 'weight' => 10, + 'weight' => -50, 'file' => 'update.report.inc', ); $items['admin/reports/updates/list'] = array( @@ -653,9 +653,6 @@ function theme_update_last_check($variables) { /** * Store data in the private update status cache table. * - * Note: this function completely ignores the {cache_update}.headers field - * since that is meaningless for the kinds of data we're caching. - * * @param $cid * The cache ID to save the data with. * @param $data @@ -671,7 +668,6 @@ function _update_cache_set($cid, $data, $expire) { $fields = array( 'created' => REQUEST_TIME, 'expire' => $expire, - 'headers' => NULL, ); if (!is_string($data)) { $fields['data'] = serialize($data); diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 4fef3274c..dedfd1d55 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: user.admin.inc,v 1.108 2010/04/24 14:49:14 dries Exp $ +// $Id: user.admin.inc,v 1.109 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -975,7 +975,7 @@ function user_admin_role_validate($form, &$form_state) { * Form submit handler for the user_admin_role() form. */ function user_admin_role_submit($form, &$form_state) { - $role = (object)$form_state['values']; + $role = (object) $form_state['values']; if ($form_state['values']['op'] == t('Save role')) { user_role_save($role); drupal_set_message(t('The role has been renamed.')); diff --git a/modules/user/user.css b/modules/user/user.css index 81c76c612..ecf6870a1 100644 --- a/modules/user/user.css +++ b/modules/user/user.css @@ -1,4 +1,4 @@ -/* $Id: user.css,v 1.20 2010/03/24 07:34:10 dries Exp $ */ +/* $Id: user.css,v 1.21 2010/04/28 20:08:39 dries Exp $ */ #permissions td.module { font-weight: bold; @@ -6,11 +6,13 @@ #permissions td.permission { padding-left: 1.5em; /* LTR */ } -#permissions tr.odd .form-item, #permissions tr.even .form-item { +#permissions tr.odd .form-item, +#permissions tr.even .form-item { white-space: normal; } /* Override the default multiselect layout in system-behavior.css. */ -#user-filter-form dl.multiselect dd, dl.multiselect dd .form-item { +#user-filter-form dl.multiselect dd, +dl.multiselect dd .form-item { width: 20em; /* 6em label + 14em select */ } #user-filter-form dl.multiselect dd .form-item label { diff --git a/modules/user/user.info b/modules/user/user.info index 8294107ad..5e3139cca 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -13,8 +13,8 @@ files[] = user.tokens.inc required = TRUE configure = admin/config/people -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/modules/user/user.install b/modules/user/user.install index f4866f755..21f1da02e 100644 --- a/modules/user/user.install +++ b/modules/user/user.install @@ -1,5 +1,5 @@ <?php -// $Id: user.install,v 1.45 2010/04/13 15:15:07 dries Exp $ +// $Id: user.install,v 1.46 2010/05/05 06:55:25 webchick Exp $ /** * @file @@ -483,7 +483,7 @@ function user_update_7004(&$sandbox) { 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'description' => t("Foreign key: {file_managed}.fid of user's picture."), + 'description' => "Foreign key: {file_managed}.fid of user's picture.", ); if (!isset($sandbox['progress'])) { diff --git a/modules/user/user.module b/modules/user/user.module index 4cb5a7a3c..505fe687d 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1,5 +1,5 @@ <?php -// $Id: user.module,v 1.1163 2010/04/24 14:51:49 dries Exp $ +// $Id: user.module,v 1.1170 2010/05/12 08:35:39 dries Exp $ /** * @file @@ -187,6 +187,15 @@ function user_field_extra_fields() { return $return; } +/** + * Fetches a user object based on an external authentication source. + * + * @param string $authname + * The external authentication username. + * + * @return + * A fully-loaded user object if the user is found or FALSE if not found. + */ function user_external_load($authname) { $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField(); @@ -273,14 +282,24 @@ class UserController extends DrupalDefaultEntityController { } /** - * Fetch a user object. + * Loads a user object. + * + * Drupal has a global $user object, which represents the currently-logged-in + * user. So to avoid confusion and to avoid clobbering the global $user object, + * it is a good idea to assign the result of this function to a different local + * variable, generally $account. If you actually do want to act as the user you + * are loading, it is essential to call @code session_save_session(FALSE); + * @endcode first. See @link http://drupal.org/node/218104 Safely impersonating + * another user @endlink for more information. * * @param $uid - * Integer specifying the user id. + * Integer specifying the user ID to load. * @param $reset - * A boolean indicating that the internal cache should be reset. + * TRUE to reset the internal cache and load from the database; FALSE + * (default) to load from the internal cache, if set. + * * @return - * A fully-loaded $user object upon successful user load or FALSE if user + * A fully-loaded user object upon successful user load, or FALSE if the user * cannot be loaded. * * @see user_load_multiple() @@ -565,6 +584,19 @@ function user_validate_name($name) { } } +/** + * Validates a user's email address. + * + * Checks that a user's email address exists and follows all standard + * validation rules. Returns error messages when the address is invalid. + * + * @param $mail + * A user's email address. + * + * @return + * If the address is invalid, a human-readable error message is returned. + * If the address is valid, nothing is returned. + */ function user_validate_mail($mail) { $mail = trim($mail); if (!$mail) { @@ -575,6 +607,11 @@ function user_validate_mail($mail) { } } +/** + * Validates an image uploaded by a user. + * + * @see user_account_form() + */ function user_validate_picture(&$form, &$form_state) { // If required, validate the uploaded picture. $validators = array( @@ -817,9 +854,10 @@ function user_search_execute($keys = NULL) { // Replace wildcards with MySQL/PostgreSQL wildcards. $keys = preg_replace('!\*+!', '%', $keys); $query = db_select('users')->extend('PagerDefault'); - $query->fields('users', array('name', 'uid', 'mail')); + $query->fields('users', array('name', 'uid')); if (user_access('administer users')) { // Administrators can also search in the otherwise private email field. + $query->fields('users', array('mail')); $query->condition(db_or()-> condition('name', '%' . db_like($keys) . '%', 'LIKE')-> condition('mail', '%' . db_like($keys) . '%', 'LIKE')); @@ -830,8 +868,15 @@ function user_search_execute($keys = NULL) { $result = $query ->limit(15) ->execute(); - foreach ($result as $account) { - $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE))); + if (user_access('administer users')) { + foreach ($result as $account) { + $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE))); + } + } + else { + foreach ($result as $account) { + $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE))); + } } return $find; } @@ -875,6 +920,11 @@ function user_user_view($account) { /** * Helper function to add default user account fields to user registration and edit form. + * + * @see user_account_form_validate() + * @see user_validate_current_pass() + * @see user_validate_picture() + * @see user_validate_email() */ function user_account_form(&$form, &$form_state) { global $user; @@ -1048,6 +1098,8 @@ function user_account_form(&$form, &$form_state) { /** * Form validation handler for the current password on the user_account_form(). + * + * @see user_account_form() */ function user_validate_current_pass(&$form, &$form_state) { global $user; @@ -1072,6 +1124,8 @@ function user_validate_current_pass(&$form, &$form_state) { /** * Form validation handler for user_account_form(). + * + * @see user_account_form() */ function user_account_form_validate($form, &$form_state) { if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') { @@ -1368,7 +1422,7 @@ function user_is_anonymous() { } function user_is_logged_in() { - return (bool)$GLOBALS['user']->uid; + return (bool) $GLOBALS['user']->uid; } function user_register_access() { @@ -2082,8 +2136,7 @@ function user_cancel_url($account) { } function user_pass_rehash($password, $timestamp, $login) { - // A single md5() is vulnerable to length-extension attacks, so use it twice. - return md5(drupal_get_hash_salt() . md5($timestamp . $password . $login)); + return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password); } /** @@ -3001,8 +3054,8 @@ function _user_categories() { } function _user_sort($a, $b) { - $a = (array)$a + array('weight' => 0, 'title' => ''); - $b = (array)$b + array('weight' => 0, 'title' => ''); + $a = (array) $a + array('weight' => 0, 'title' => ''); + $b = (array) $b + array('weight' => 0, 'title' => ''); return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1)); } diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 643d5b852..9bbf49087 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: user.pages.inc,v 1.70 2010/04/24 14:49:14 dries Exp $ +// $Id: user.pages.inc,v 1.72 2010/05/06 05:59:31 webchick Exp $ /** * @file @@ -134,7 +134,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a user_login_finalize(); 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 = md5(drupal_random_bytes(55)); + $token = drupal_hash_base64(drupal_random_bytes(55)); $_SESSION['pass_reset_' . $user->uid] = $token; drupal_goto('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token))); } @@ -278,7 +278,7 @@ function user_profile_form($form, &$form_state, $account, $category = 'account') * Validation function for the user account and profile editing form. */ function user_profile_form_validate($form, &$form_state) { - $edit = (object)$form_state['values']; + $edit = (object) $form_state['values']; field_attach_form_validate('user', $edit, $form, $form_state); } @@ -291,9 +291,9 @@ function user_profile_form_submit($form, &$form_state) { // Remove unneeded values. form_state_values_clean($form_state); - $edit = (object)$form_state['values']; + $edit = (object) $form_state['values']; field_attach_submit('user', $edit, $form, $form_state); - $edit = (array)$edit; + $edit = (array) $edit; user_save($account, $edit, $category); $form_state['values']['uid'] = $account->uid; diff --git a/modules/user/user.test b/modules/user/user.test index 30572cba9..560ca7a8c 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -1,5 +1,5 @@ <?php -// $Id: user.test,v 1.89 2010/04/20 09:48:06 webchick Exp $ +// $Id: user.test,v 1.92 2010/05/07 14:48:10 dries Exp $ class UserRegistrationTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -55,7 +55,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $edit['pass[pass1]'] = '99999.0'; $edit['pass[pass2]'] = '99999'; $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('The specified passwords do not match.'), t('Type mismatched passwords display an error message.')); + $this->assertText(t('The specified passwords do not match.'), t('Typing mismatched passwords displays an error message.')); // Enter a correct password. $edit['pass[pass1]'] = $new_pass = $this->randomName(); @@ -1259,7 +1259,7 @@ class UserBlocksUnitTests extends DrupalWebTestCase { private function insertSession(array $fields = array()) { $fields += array( 'uid' => 0, - 'sid' => md5(uniqid(mt_rand(), TRUE)), + 'sid' => drupal_hash_base64(uniqid(mt_rand(), TRUE)), 'timestamp' => REQUEST_TIME, ); db_insert('sessions') @@ -1391,6 +1391,18 @@ class UserEditTestCase extends DrupalWebTestCase { $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); + // Check that filling out a single password field does not validate. + $edit = array(); + $edit['pass[pass1]'] = ''; + $edit['pass[pass2]'] = $this->randomName(); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); + + $edit['pass[pass1]'] = $this->randomName(); + $edit['pass[pass2]'] = ''; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); + // Test that the error message appears when attempting to change the mail or // pass without the current password. $edit = array(); @@ -1585,3 +1597,36 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase { } } } + +/** + * Test user search. + */ +class UserUserSearchTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'User search', + 'description' => 'Testing that only user with the right permission can see the email address in the user search.', + 'group' => 'User', + ); + } + + function testUserSearch() { + $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search')); + $this->drupalLogin($user1); + $keys = $user1->mail; + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertNoText($keys); + $this->drupalLogout(); + + $user2 = $this->drupalCreateUser(array('administer users', 'access user profiles', 'search content', 'use advanced search')); + $this->drupalLogin($user2); + $keys = $user2->mail; + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($keys); + $this->drupalLogout(); + } +} + + diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info index 8d5880db9..190e4b49e 100644 --- a/profiles/minimal/minimal.info +++ b/profiles/minimal/minimal.info @@ -7,8 +7,8 @@ dependencies[] = block dependencies[] = dblog files[] = minimal.profile -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info index 334742c2c..961212523 100644 --- a/profiles/standard/standard.info +++ b/profiles/standard/standard.info @@ -23,8 +23,8 @@ dependencies[] = file dependencies[] = rdf files[] = standard.profile -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install index a9de9dc5d..fe43d5b73 100644 --- a/profiles/standard/standard.install +++ b/profiles/standard/standard.install @@ -1,5 +1,5 @@ <?php -// $Id: standard.install,v 1.13 2010/04/22 09:55:32 webchick Exp $ +// $Id: standard.install,v 1.15 2010/05/16 10:41:04 dries Exp $ /** * Implements hook_install(). @@ -227,6 +227,7 @@ function standard_install() { foreach ($types as $type) { $type = node_type_set_defaults($type); node_type_save($type); + node_add_body_field($type); } // Insert default pre-defined RDF mapping into the database. @@ -414,6 +415,11 @@ function standard_install() { // Set this as the administrator role. variable_set('user_admin_role', $admin_role->rid); + // Assign user 1 the "administrator" role. + db_insert('users_roles') + ->fields(array('uid' => 1, 'rid' => $admin_role->rid)) + ->execute(); + // Update the menu router information. menu_rebuild(); diff --git a/scripts/password-hash.sh b/scripts/password-hash.sh index a7660fbd9..3a7243e9b 100755 --- a/scripts/password-hash.sh +++ b/scripts/password-hash.sh @@ -1,6 +1,6 @@ #!/usr/bin/php <?php -// $Id: password-hash.sh,v 1.6 2009/02/26 07:30:29 webchick Exp $ +// $Id: password-hash.sh,v 1.7 2010/05/01 08:12:23 dries Exp $ /** * Drupal hash script - to generate a hash from a plaintext password @@ -13,10 +13,6 @@ * Plain-text passwords in quotes (or with spaces backslash escaped). */ -function variable_get($x, $default) { - return $default; -} - if (version_compare(PHP_VERSION, "5.2.0", "<")) { $version = PHP_VERSION; echo <<<EOF @@ -86,7 +82,7 @@ while ($param = array_shift($_SERVER['argv'])) { define('DRUPAL_ROOT', getcwd()); include_once DRUPAL_ROOT . '/includes/password.inc'; -include_once DRUPAL_ROOT . '/includes/common.inc'; +include_once DRUPAL_ROOT . '/includes/bootstrap.inc'; foreach ($passwords as $password) { print("\npassword: $password \t\thash: ". user_hash_password($password) ."\n"); diff --git a/themes/garland/color/preview.css b/themes/garland/color/preview.css index 314391e9f..b63c078c2 100644 --- a/themes/garland/color/preview.css +++ b/themes/garland/color/preview.css @@ -1,11 +1,12 @@ -/* $Id: preview.css,v 1.4 2010/04/22 05:18:21 webchick Exp $ */ +/* $Id: preview.css,v 1.5 2010/04/28 20:08:39 dries Exp $ */ /* Positioning */ #preview { overflow: hidden; max-width: 100%; } -#preview, #preview #img { +#preview, +#preview #img { width: 600px; height: 371px; } @@ -50,7 +51,8 @@ #preview p { margin: .5em 0; } -#preview a:link, #preview a:visited { +#preview a:link, +#preview a:visited { text-decoration: none; font-weight: normal; } diff --git a/themes/garland/fix-ie-rtl.css b/themes/garland/fix-ie-rtl.css index 665af8e99..481625483 100644 --- a/themes/garland/fix-ie-rtl.css +++ b/themes/garland/fix-ie-rtl.css @@ -1,4 +1,4 @@ -/* $Id: fix-ie-rtl.css,v 1.5 2010/03/31 20:24:13 dries Exp $ */ +/* $Id: fix-ie-rtl.css,v 1.6 2010/04/28 20:08:39 dries Exp $ */ body { /* Center layout */ @@ -10,11 +10,12 @@ body { direction: ltr; } -#squeeze .left-corner{ +#squeeze .left-corner { direction: rtl } -#header-region, #wrapper #container { +#header-region, +#wrapper #container { /* Reset text alignment */ text-align: right; } @@ -54,7 +55,8 @@ tr.menu-disabled { height: 1em; } -#attach-hide label, #uploadprogress div.message { +#attach-hide label, +#uploadprogress div.message { /* Fading elements in IE causes the text to bleed unless they have a background. */ background-color: #ffffff; } diff --git a/themes/garland/fix-ie.css b/themes/garland/fix-ie.css index 865efed6f..8753da1de 100644 --- a/themes/garland/fix-ie.css +++ b/themes/garland/fix-ie.css @@ -1,11 +1,12 @@ -/* $Id: fix-ie.css,v 1.12 2010/03/31 20:24:13 dries Exp $ */ +/* $Id: fix-ie.css,v 1.13 2010/04/28 20:08:39 dries Exp $ */ body { /* Center layout */ text-align: center; } -#header-region, #wrapper #container { +#header-region, +#wrapper #container { /* Reset text alignment */ text-align: left; /* LTR */ } @@ -58,7 +59,8 @@ tr.taxonomy-term-preview { filter: alpha(opacity=50); } -#attach-hide label, #uploadprogress div.message { +#attach-hide label, +#uploadprogress div.message { /* Fading elements in IE causes the text to bleed unless they have a background. */ background-color: #ffffff; } diff --git a/themes/garland/garland.info b/themes/garland/garland.info index 85de5721a..381dd6f3e 100644 --- a/themes/garland/garland.info +++ b/themes/garland/garland.info @@ -9,8 +9,8 @@ stylesheets[all][] = style.css stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/themes/garland/print.css b/themes/garland/print.css index 6f7565380..2b74d1677 100644 --- a/themes/garland/print.css +++ b/themes/garland/print.css @@ -1,23 +1,35 @@ -/* $Id: print.css,v 1.8 2009/08/03 03:04:34 webchick Exp $ */ +/* $Id: print.css,v 1.9 2010/04/28 20:08:39 dries Exp $ */ -body, input, textarea, select { +body, +input, +textarea, +select { color: #000; background: none; font-family: Verdana, sans-serif; font-size: 11pt; } -ul.main-menu, ul.secondary-menu, -#header-region, .sidebar { +ul.main-menu, +ul.secondary-menu, +#header-region, +.sidebar { display: none; } -body.two-sidebars, body.sidebar-first, body.sidebar-second, body { +body.two-sidebars, +body.sidebar-first, +body.sidebar-second, +body { width: 640px; } -body.sidebar-first #center, body.sidebar-second #center, body.two-sidebars #center, -body.sidebar-first #squeeze, body.sidebar-second #squeeze, body.two-sidebars #squeeze { +body.sidebar-first #center, +body.sidebar-second #center, +body.two-sidebars #center, +body.sidebar-first #squeeze, +body.sidebar-second #squeeze, +body.two-sidebars #squeeze { margin: 0; } @@ -42,7 +54,9 @@ body.sidebar-first #squeeze, body.sidebar-second #squeeze, body.two-sidebars #sq height: 130px; } -#wrapper #container #header h1, #wrapper #container #header h1 a:link, #wrapper #container #header h1 a:visited { +#wrapper #container #header h1, +#wrapper #container #header h1 a:link, +#wrapper #container #header h1 a:visited { text-shadow: none; color: #000; } diff --git a/themes/garland/style-rtl.css b/themes/garland/style-rtl.css index 9d1e8dff6..657b8b45d 100644 --- a/themes/garland/style-rtl.css +++ b/themes/garland/style-rtl.css @@ -1,4 +1,4 @@ -/* $Id: style-rtl.css,v 1.19 2010/04/08 18:26:42 dries Exp $ */ +/* $Id: style-rtl.css,v 1.22 2010/05/05 16:28:06 dries Exp $ */ html { direction: rtl; @@ -11,15 +11,22 @@ body { direction: rtl; } -ul, .block ul, ol { +ul, +.block ul, +ol { padding: 0 1.5em 0 0; } -ul.menu, .item-list ul { +ul.menu, +.item-list ul { margin: 0.35em -0.5em 0 0; } -ol li, ul li, ul.menu li, .item-list ul li, li.leaf { +ol li, +ul li, +ul.menu li, +.item-list ul li, +li.leaf { margin: 0.15em .5em 0.15em 0; } @@ -68,7 +75,8 @@ dl dd { padding: 0 1.3 0 0; } -.form-button, .form-submit { +.form-button, +.form-submit { margin: 2em 0 1em 0.5em; } @@ -181,8 +189,14 @@ h1.with-tabs { margin: 0 0 0 2em; } -ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited, -ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited { +ul.primary li a, +ul.primary li.active a, +ul.primary li a:hover, +ul.primary li a:visited, +ul.secondary li a, +ul.secondary li.active a, +ul.secondary li a:hover, +ul.secondary li a:visited { margin: 0 1px 0 0; } @@ -196,8 +210,10 @@ ul.links li, ul.inline li { padding-right: 0; } -.node .links, .comment .links { +.node .links, +.comment .links { text-align: right; + padding-right: 0; } .user-picture, @@ -258,6 +274,14 @@ div.vertical-tabs { #user-login-form ul { text-align: right; } +#user-login-form .openid-links { + padding-left: 0; + padding-right: 0.5em; +} +#user-login-form .openid-links li.user-link { + padding-left: 0; + padding-right: 1em; +} div.admin .left { float: right; @@ -284,8 +308,14 @@ div.admin .right { */ /* Position:relative on these breaks IE7. */ -ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited, -ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited { +ul.primary li a, +ul.primary li.active a, +ul.primary li a:hover, +ul.primary li a:visited, +ul.secondary li a, +ul.secondary li.active a, +ul.secondary li a:hover, +ul.secondary li a:visited { position: static; } diff --git a/themes/garland/style.css b/themes/garland/style.css index 4b7c79bad..49d856137 100644 --- a/themes/garland/style.css +++ b/themes/garland/style.css @@ -1,4 +1,4 @@ -/* $Id: style.css,v 1.78 2010/04/08 18:26:42 dries Exp $ */ +/* $Id: style.css,v 1.82 2010/05/18 11:56:59 dries Exp $ */ /** * Generic elements @@ -16,12 +16,18 @@ input { color: #494949; } -textarea, select { +textarea, +select { font: 1em/160% Verdana, sans-serif; color: #494949; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin: 0; padding: 0; font-weight: normal; @@ -32,7 +38,8 @@ h1 { font-size: 170%; } -h2, #center h1 { +h2, +#center h1 { font-size: 160%; line-height: 130%; } @@ -53,11 +60,15 @@ h6 { font-size: 110%; } -quote, code, fieldset { +quote, +code, +fieldset { margin: .5em 0; } -code, pre, kbd { +code, +pre, +kbd { font-size: 115%; } @@ -66,7 +77,8 @@ p { padding: 0; } -a:link, a:visited { +a:link, +a:visited { color: #027AC6; text-decoration: none; } @@ -76,7 +88,8 @@ a:hover { text-decoration: underline; } -a:active, a.active { +a:active, +a.active { color: #5895be; } @@ -88,13 +101,16 @@ hr { background: #5294c1; } -ul, .block ul, ol { +ul, +.block ul, +ol { margin: 0.5em 0 1em; padding: 0 0 0 1.5em; /* LTR */ } /* Default to menu leaf bullet for unordered list items. "ul" used here so it can cascade to list items and "li.leaf" to override the system leaf image. */ -ul, li.leaf { +ul, +ul li.leaf { list-style-image: url(images/menu-leaf.gif); } @@ -103,11 +119,16 @@ ol { list-style-image: none; } -ul.menu, .item-list ul { +ul.menu, +.item-list ul { margin: 0.35em 0 0 -0.5em; /* LTR */ } -ol li, ul li, ul.menu li, .item-list ul li, li.leaf { +ol li, +ul li, +ul.menu li, +.item-list ul li, +li.leaf { margin: 0.15em 0 0.15em .5em; /* LTR */ padding-bottom: .1em; } @@ -120,7 +141,9 @@ ul li.collapsed { list-style-image: url(images/menu-collapsed.gif); /* LTR */ } -ul li.leaf a, ul li.expanded a, ul li.collapsed a { +ul li.leaf a, +ul li.expanded a, +ul li.collapsed a { display: block; } @@ -166,7 +189,8 @@ dl dd { margin: 0 0 .5em 1.5em; /* LTR */ } -img, a img { +img, +a img { border: none; } @@ -181,20 +205,25 @@ thead th { font-weight: bold; } -th a:link, th a:visited { +th a:link, +th a:visited { color: #6f9dbd; } -td, th { +td, +th { padding: .3em .5em; } -tr.even, tr.odd, tbody th { +tr.even, +tr.odd, +tbody th { border: solid #d3e7f4; border-width: 1px 0; } -tr.odd, tr.info { +tr.odd, +tr.info { background-color: #edf5fa; } @@ -218,7 +247,10 @@ tr.even td.active { background-color: #e6f1f7; } -td.region-title, td.module, td.container, td.category { +td.region-title, +td.module, +td.container, +td.category { border-top: 1.5em solid #fff; border-bottom: 1px solid #b4d7f0; background-color: #d4e7f3; @@ -226,7 +258,10 @@ td.region-title, td.module, td.container, td.category { font-weight: bold; } -tr:first-child td.region-title, tr:first-child td.module, tr:first-child td.container, tr:first-child td.category { +tr:first-child td.region-title, +tr:first-child td.module, +tr:first-child td.container, +tr:first-child td.category { border-top-width: 0; } @@ -234,7 +269,8 @@ span.form-required { color: #ffae00; } -.submitted, .description, .vertical-tab-button .summary { +.submitted, .description, +.vertical-tab-button .summary { font-size: 0.92em; color: #898989; } @@ -268,7 +304,10 @@ div.messages { padding: 0 0 0 1.3em; /* LTR */ } -.form-checkboxes, .form-radios, .form-checkboxes .form-item, .form-radios .form-item { +.form-checkboxes, +.form-radios, +.form-checkboxes .form-item, +.form-radios .form-item { margin: 0.25em 0; } @@ -276,7 +315,8 @@ div.messages { margin-bottom: 2em; } -.form-button, .form-submit { +.form-button, +.form-submit { margin: 2em 0.5em 1em 0; /* LTR */ } @@ -284,16 +324,20 @@ div.messages { .confirmation .form-submit, .search-form .form-submit, .poll .form-submit, -fieldset .form-button, fieldset .form-submit, -.sidebar .form-button, .sidebar .form-submit, -table .form-button, table .form-submit { +fieldset .form-button, +fieldset .form-submit, +.sidebar .form-button, +.sidebar .form-submit, +table .form-button, +table .form-submit { margin: 0; } /** * Skip link */ -#skip-link a:link, #skip-link a:visited { +#skip-link a:link, +#skip-link a:visited { font-weight: bold; background: #fff; padding: 0px 5px; @@ -305,7 +349,9 @@ table .form-button, table .form-submit { position: absolute; } -#skip-link a:hover, #skip-link a:focus, #skip-link a:active { +#skip-link a:hover, +#skip-link a:focus, +#skip-link a:active { height: auto; width: auto; overflow: visible; @@ -348,7 +394,8 @@ table .form-button, table .form-submit { display: none; } -.region-header p, .region-header img { +.region-header p, +.region-header img { margin-top: 0.5em; } @@ -356,7 +403,9 @@ table .form-button, table .form-submit { margin: 0 1em 0 0; /* LTR */ } -.region-header h3, .region-header label, .region-header li { +.region-header h3, +.region-header label, +.region-header li { margin: 0 1em; padding: 0; background: none; @@ -395,7 +444,9 @@ body.fluid-width #wrapper #container { position: absolute; } -#branding, #branding a:link, #branding a:visited { +#branding, +#branding a:link, +#branding a:visited { line-height: 120px; position: relative; z-index: 2; @@ -417,7 +468,8 @@ body.two-sidebars { min-width: 980px; } /* With 2 columns, require a minimum width of 800px. */ -body.sidebar-first, body.sidebar-second { +body.sidebar-first, +body.sidebar-second { min-width: 780px; } @@ -549,7 +601,9 @@ div#branding strong { font-weight: normal; } -#branding, #branding a:link, #branding a:visited { +#branding, +#branding a:link, +#branding a:visited { color: #fff; text-shadow: #1659ac 0px 1px 3px; font-size: 1.5em; @@ -563,7 +617,8 @@ div#branding strong { font-size: 0.92em; } -#wrapper #container .breadcrumb, #wrapper #container .breadcrumb a { +#wrapper #container .breadcrumb, +#wrapper #container .breadcrumb a { color: #529ad6; } @@ -602,7 +657,9 @@ ul.main-menu li { background-image: none; } -ul.main-menu li a, ul.main-menu li a:link, ul.main-menu li a:visited { +ul.main-menu li a, +ul.main-menu li a:link, +ul.main-menu li a:visited { display: block; margin: 0 1em; padding: .75em 0 0; @@ -610,7 +667,8 @@ ul.main-menu li a, ul.main-menu li a:link, ul.main-menu li a:visited { background: transparent url(images/bg-navigation-item.png) no-repeat 50% 0; } -ul.main-menu li a:hover, ul.main-menu li a.active { +ul.main-menu li a:hover, +ul.main-menu li a.active { color: #fff; background: transparent url(images/bg-navigation-item-hover.png) no-repeat 50% 0; } @@ -634,7 +692,9 @@ ul.secondary-menu li { background-image: none; } -ul.secondary-menu li a, ul.secondary-menu li a:link, ul.secondary-menu li a:visited { +ul.secondary-menu li a, +ul.secondary-menu li a:link, +ul.secondary-menu li a:visited { display: block; margin: 0 1em; padding: .75em 0 0; @@ -642,7 +702,8 @@ ul.secondary-menu li a, ul.secondary-menu li a:link, ul.secondary-menu li a:visi background: transparent; } -ul.secondary-menu li a:hover, ul.secondary-menu li a.active { +ul.secondary-menu li a:hover, +ul.secondary-menu li a.active { color: #cde3f1; background: transparent; } @@ -650,7 +711,10 @@ ul.secondary-menu li a:hover, ul.secondary-menu li a.active { /** * Local tasks */ -ul.primary, ul.primary li, ul.secondary, ul.secondary li { +ul.primary, +ul.primary li, +ul.secondary, +ul.secondary li { border: 0; background: none; margin: 0; @@ -680,8 +744,14 @@ h1.with-tabs { padding: 0; } -ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited, -ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited { +ul.primary li a, +ul.primary li.active a, +ul.primary li a:hover, +ul.primary li a:visited, +ul.secondary li a, +ul.secondary li.active a, +ul.secondary li a:hover, +ul.secondary li a:visited { border: 0; background: transparent; padding: 4px 1em; @@ -692,8 +762,14 @@ ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.seconda top: -1px; display: inline-block; } -ul.primary li.active a, ul.primary li.active a:link, ul.primary li.active a:visited, ul.primary li a:hover, -ul.secondary li.active a, ul.secondary li.active a:link, ul.secondary li.active a:visited, ul.secondary li a:hover { +ul.primary li.active a, +ul.primary li.active a:link, +ul.primary li.active a:visited, +ul.primary li a:hover, +ul.secondary li.active a, +ul.secondary li.active a:link, +ul.secondary li.active a:visited, +ul.secondary li a:hover { background: url(images/bg-tab.png) repeat-x 0 50%; color: #fff; } @@ -711,7 +787,8 @@ ul.secondary li.active a { padding: 1.5em 16px; } -ul.links li, ul.inline li { +ul.links li, +ul.inline li { margin-left: 0; margin-right: 0; padding-left: 0; /* LTR */ @@ -719,8 +796,10 @@ ul.links li, ul.inline li { background-image: none; } -.node .links, .comment .links { +.node .links, +.comment .links { text-align: left; /* LTR */ + padding-left: 0; /* LTR */ } .user-picture, @@ -737,7 +816,9 @@ ul.links li, ul.inline li { float: right; /* LTR */ } -.preview .node, .preview .comment, .node-sticky { +.preview .node, +.preview .comment, +.node-sticky { margin: 0; padding: 0.5em 0; border: 0; @@ -779,7 +860,8 @@ ul.links li, ul.inline li { color: #494949; } -.node .content, .comment .content { +.node .content, +.comment .content { margin: 0.6em 0; } @@ -954,7 +1036,8 @@ div.vertical-tabs ul.vertical-tabs-list li.selected a strong { padding-right: 20px; /* LTR */ } -#block-node-syndicate img, .feed-icon { +#block-node-syndicate img, +.feed-icon { float: right; /* LTR */ padding-top: 4px; } @@ -972,6 +1055,15 @@ div.vertical-tabs ul.vertical-tabs-list li.selected a strong { #user-login-form ul { text-align: left; /* LTR */ } +#user-login .openid-links { + padding: 0; +} +#user-login-form .openid-links { + padding-left: 0.5em; /* LTR */ +} +#user-login-form .openid-links li.user-link { + padding-left: 1em; /* LTR */ +} /** * User profiles. @@ -1045,12 +1137,16 @@ table.system-status-report th { border-color: #d3e7f4; } -#autocomplete li.selected, tr.selected td, tr.selected td.active { +#autocomplete li.selected, +tr.selected td, +tr.selected td.active { background: #027ac6; color: #fff; } -tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active { +tr.selected td a:link, +tr.selected td a:visited, +tr.selected td a:active { color: #d3e7f4; } @@ -1104,7 +1200,8 @@ div.error { border: 1px solid #d77; } -div.error, tr.error { +div.error, +tr.error { color: #a30000; background-color: #FFCCCC; } @@ -1115,7 +1212,8 @@ div.warning { color: #220; } -.form-item input.error, .form-item textarea.error { +.form-item input.error, +.form-item textarea.error { border: 1px solid #c52020; color: #363636; } @@ -1154,26 +1252,31 @@ tr.dblog-error { tr.dblog-error td.active { background-color: #fbdbdb; } -tr.dblog-page-not-found, tr.dblog-access-denied { +tr.dblog-page-not-found, +tr.dblog-access-denied { background: #d7ffd7; } -tr.dblog-page-not-found td.active, tr.dblog-access-denied td.active { +tr.dblog-page-not-found td.active, +tr.dblog-access-denied td.active { background: #c7eec7; } /** * Status report colors. */ -table.system-status-report tr.error, table.system-status-report tr.error th { +table.system-status-report tr.error, +table.system-status-report tr.error th { background-color: #fcc; border-color: #ebb; color: #200; } -table.system-status-report tr.warning, table.system-status-report tr.warning th { +table.system-status-report tr.warning, +table.system-status-report tr.warning th { background-color: #ffd; border-color: #eeb; } -table.system-status-report tr.ok, table.system-status-report tr.ok th { +table.system-status-report tr.ok, +table.system-status-report tr.ok th { background-color: #dfd; border-color: #beb; } diff --git a/themes/seven/ie6.css b/themes/seven/ie6.css index 6133fe7d2..0e7d7483e 100644 --- a/themes/seven/ie6.css +++ b/themes/seven/ie6.css @@ -1,4 +1,4 @@ -/* $Id: ie6.css,v 1.5 2010/03/05 15:23:25 dries Exp $ */ +/* $Id: ie6.css,v 1.6 2010/04/28 20:08:39 dries Exp $ */ ul.menu li, ul.menu li a, @@ -8,11 +8,13 @@ ul.links li a, #page { height: 1%; } -#block-system-main ul.node-type-list li a, #block-system-main ul.admin-list li a { +#block-system-main ul.node-type-list li a, +#block-system-main ul.admin-list li a { height: 1px; position: relative; display: block; } -#block-system-main ul.node-type-list li div.description a, #block-system-main ul.admin-list li div.description a { +#block-system-main ul.node-type-list li div.description a, +#block-system-main ul.admin-list li div.description a { display: inline; } diff --git a/themes/seven/reset.css b/themes/seven/reset.css index c2d04a47d..aa9c8044a 100644 --- a/themes/seven/reset.css +++ b/themes/seven/reset.css @@ -1,4 +1,4 @@ -/* $Id: reset.css,v 1.8 2010/04/12 17:33:35 webchick Exp $ */ +/* $Id: reset.css,v 1.9 2010/04/28 20:08:39 dries Exp $ */ /** * Reset CSS styles. @@ -150,7 +150,8 @@ q { } blockquote:before, blockquote:after, -q:before, q:after { +q:before, +q:after { content: ''; content: none; } diff --git a/themes/seven/seven.info b/themes/seven/seven.info index 81afb5995..b719a4355 100644 --- a/themes/seven/seven.info +++ b/themes/seven/seven.info @@ -15,8 +15,8 @@ regions[page_bottom] = Page bottom regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/themes/seven/style.css b/themes/seven/style.css index 709c8ed45..fcf02f788 100644 --- a/themes/seven/style.css +++ b/themes/seven/style.css @@ -1,4 +1,4 @@ -/* $Id: style.css,v 1.52 2010/04/26 13:33:43 dries Exp $ */ +/* $Id: style.css,v 1.57 2010/05/05 16:41:57 dries Exp $ */ /** * Generic elements. @@ -28,7 +28,12 @@ hr { legend { font-weight: bold; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { font-weight: bold; margin: 10px 0; } @@ -67,10 +72,13 @@ blockquote { address { font-style: italic; } -u, ins { +u, +ins { text-decoration: underline; } -s, strike, del { +s, +strike, +del { text-decoration: line-through; } big { @@ -92,15 +100,21 @@ sup { nobr { white-space: nowrap; } -abbr, acronym { +abbr, +acronym { border-bottom: dotted 1px; } -ul, .block ul, .item-list ul, .item-list ul { +ul, +.block ul, +.item-list ul, +.item-list ul { list-style-type: disc; list-style-image: none; - margin: 0.25em 0 0.25em 1.5em; + margin: 0.25em 0 0.25em 1.5em; /* LTR */ } -.item-list ul li, li.leaf, ul.menu li { +.item-list ul li, +li.leaf, +ul.menu li { list-style-type: disc; list-style-image: none; } @@ -111,18 +125,23 @@ ol { list-style-type: decimal; margin: 0.25em 0 0.25em 2em; } -.item-list ul li.collapsed, ul.menu li.collapsed { +.item-list ul li.collapsed, +ul.menu li.collapsed { list-style-image:url(../../misc/menu-collapsed.png); list-style-type:disc; } -.item-list ul li.expanded, ul.menu li.expanded { +.item-list ul li.expanded, +ul.menu li.expanded { list-style-image:url(../../misc/menu-expanded.png); list-style-type:circle; } -quote, code { +quote, +code { margin: .5em 0; } -code, pre, kbd { +code, +pre, +kbd { font-size: 1.25em; } @@ -325,27 +344,28 @@ ul.secondary { clear: both; font-size: 12px; text-align: right; - padding: 5px 10px 4px; - line-height: 20px; + padding: 4px 10px 10px; + line-height: 18px; overflow: hidden; - border-bottom: 1px solid #ccc; - background: #fff; + background-color: #fff; } ul.secondary li { padding-left: 10px; } ul.secondary li a { - color: #05a; + background-color: #ddd; + color: #000; } ul.secondary li a, ul.secondary li a:hover, ul.secondary li.active a, ul.secondary li.active a.active { - padding: 0 10px; + padding: 2px 10px; border-radius: 7px; - -moz-border-radius: 10px; + -moz-border-radius: 7px; -webkit-border-radius: 7px; } +ul.secondary li a:hover, ul.secondary li.active a, ul.secondary li.active a.active { color: #fff; @@ -430,7 +450,8 @@ ul.admin-list li:last-child { ul.node-type-list .label { font-size: 15px; } -ul.node-type-list li a, ul.admin-list li a { +ul.node-type-list li a, +ul.admin-list li a { margin-left: -30px; padding: 0px 0 4px 30px; min-height: 0; @@ -439,7 +460,8 @@ ul.admin-list.compact li a { margin-left: 0; padding: 0; } -ul.node-type-list li div.description a, ul.admin-list li div.description a { +ul.node-type-list li div.description a, +ul.admin-list li div.description a { margin-left: 0px; padding: 0px; min-height: inherit; @@ -454,28 +476,15 @@ table { margin: 0 0 10px; border: 1px solid #bebfb9; } -table.system-status-report th, table td, table th { vertical-align: middle; padding: 8px 10px; + border: 0; + color: #000; } -table.system-status-report th { - padding-left: 30px; -} -table.system-status-report tr.ok > * { - background-color: #dfd; -} -table.system-status-report tr.info > * { - background-color: #bdf; -} -table.system-status-report tr.warning > * { - background-color: #ffd; -} -table.system-status-report tr.error > * { - background-color: #fdd; -} -tr.even, tr.odd { +tr.even, +tr.odd { border-width: 0 1px 0 1px; border-style: solid; border-color: #bebfb9; @@ -500,7 +509,6 @@ table th { border-color: #bebfb9; padding: 3px 10px; } - table th.active { background: #bdbeb9; } @@ -524,7 +532,21 @@ table tr.selected td { background: #ffc; border-color: #eeb; } - +table.system-status-report tr { + border-bottom: 1px solid #bebfb9; +} +table.system-status-report tr.ok td { + background-color: #dfd; +} +table.system-status-report tr.info td { + background-color: #bdf; +} +table.system-status-report tr.warning td { + background-color: #ffd; +} +table.system-status-report tr.error td { + background-color: #fdd; +} /** * Fieldsets. * @@ -632,10 +654,12 @@ div.description, ul.tips li { margin: 0.25em 0 0.25em 1.5em; } -body div.form-type-radio div.description, body div.form-type-checkbox div.description { +body div.form-type-radio div.description, +body div.form-type-checkbox div.description { margin-left: 1.5em; } -input.form-submit, a.button { +input.form-submit, +a.button { cursor: pointer; padding: 4px 17px; margin-bottom: 1em; @@ -653,7 +677,10 @@ input.form-submit, a.button { -moz-border-radius: 20px; -webkit-border-radius: 15px; } -a.button:link, a.button:visited, a.button:hover, a.button:active { +a.button:link, +a.button:visited, +a.button:hover, +a.button:active { text-decoration: none; color: #5a5a5a; } @@ -881,8 +908,7 @@ ol.task-list li.done { } .overlay ul.secondary { background: transparent none; - margin: -2.4em 0 0.5em 0; - padding: 3px 10px; + margin: -2.4em 0 0.3em 0; } .overlay #content { padding: 0; @@ -925,3 +951,11 @@ div.add-or-remove-shortcuts { #block-node-recent .more-link { padding: 0 5px 5px 0; } + +/* User login block */ +#user-login-form .openid-links { + margin-left: 0; /* LTR */ +} +#user-login-form .openid-links .user-link { + margin-left: 1.5em; /* LTR */ +} diff --git a/themes/seven/template.php b/themes/seven/template.php index 1b86efdf4..09773e2ae 100644 --- a/themes/seven/template.php +++ b/themes/seven/template.php @@ -1,5 +1,17 @@ <?php -// $Id: template.php,v 1.16 2010/04/21 06:55:23 webchick Exp $ +// $Id: template.php,v 1.17 2010/05/12 09:22:24 dries Exp $ + +/** + * Override or insert variables into the maintenance page template. + */ +function seven_preprocess_maintenance_page(&$vars) { + // While markup for normal pages is split into page.tpl.php and html.tpl.php, + // the markup for the maintenance page is all in the single + // maintenance-page.tpl.php template. So, to have what's done in + // seven_preprocess_html() also happen on the maintenance page, it has to be + // called here. + seven_preprocess_html($vars); +} /** * Override or insert variables into the html template. diff --git a/themes/stark/stark.info b/themes/stark/stark.info index fe6b2a043..62661c613 100644 --- a/themes/stark/stark.info +++ b/themes/stark/stark.info @@ -7,8 +7,8 @@ core = 7.x engine = phptemplate stylesheets[all][] = layout.css -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/themes/tests/test_theme/test_theme.info b/themes/tests/test_theme/test_theme.info index 78a9ddcb8..d1d76d73e 100644 --- a/themes/tests/test_theme/test_theme.info +++ b/themes/tests/test_theme/test_theme.info @@ -4,8 +4,8 @@ description = Theme for testing the theme system core = 7.x engine = phptemplate hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/themes/tests/update_test_basetheme/update_test_basetheme.info b/themes/tests/update_test_basetheme/update_test_basetheme.info index 6ce7e183b..86fcb269f 100644 --- a/themes/tests/update_test_basetheme/update_test_basetheme.info +++ b/themes/tests/update_test_basetheme/update_test_basetheme.info @@ -5,8 +5,8 @@ core = 7.x engine = phptemplate hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/themes/tests/update_test_subtheme/update_test_subtheme.info b/themes/tests/update_test_subtheme/update_test_subtheme.info index 636987892..00a72491d 100644 --- a/themes/tests/update_test_subtheme/update_test_subtheme.info +++ b/themes/tests/update_test_subtheme/update_test_subtheme.info @@ -6,8 +6,8 @@ engine = phptemplate base theme = update_test_basetheme hidden = TRUE -; Information added by drupal.org packaging script on 2010-04-26 -version = "7.0-alpha4" +; Information added by drupal.org packaging script on 2010-05-23 +version = "7.0-alpha5" project = "drupal" -datestamp = "1272318008" +datestamp = "1274628610" diff --git a/update.php b/update.php index 527706142..1554e05e1 100644 --- a/update.php +++ b/update.php @@ -1,5 +1,5 @@ <?php -// $Id: update.php,v 1.320 2010/04/24 14:49:13 dries Exp $ +// $Id: update.php,v 1.323 2010/05/18 18:11:12 dries Exp $ /** * Root directory of Drupal installation. @@ -226,7 +226,7 @@ function update_info_page() { update_task_list('info'); drupal_set_title('Drupal database update'); $token = drupal_get_token('update'); - $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>'; + $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, see the <a href="http://drupal.org/upgrade">upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>'; $output .= "<ol>\n"; $output .= "<li><strong>Back up your database</strong>. This process will change your database values and in case of emergency you may need to revert to a backup.</li>\n"; $output .= "<li><strong>Back up your code</strong>. Hint: when backing up module code, do not leave that backup in the 'modules' or 'sites/*/modules' directories as this may confuse Drupal's auto-discovery mechanism.</li>\n"; @@ -268,7 +268,7 @@ function update_access_allowed() { // Calls to user_access() might fail during the Drupal 6 to 7 update process, // so we fall back on requiring that the user be logged in as user #1. try { - require_once drupal_get_path('module', 'user') . '/user.module'; + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.module'; return user_access('administer software updates'); } catch (Exception $e) { @@ -333,6 +333,7 @@ ini_set('display_errors', FALSE); require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; require_once DRUPAL_ROOT . '/includes/update.inc'; require_once DRUPAL_ROOT . '/includes/common.inc'; +require_once DRUPAL_ROOT . '/includes/file.inc'; require_once DRUPAL_ROOT . '/includes/entity.inc'; require_once DRUPAL_ROOT . '/includes/unicode.inc'; update_prepare_d7_bootstrap(); @@ -346,7 +347,6 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; if (empty($op) && update_access_allowed()) { require_once DRUPAL_ROOT . '/includes/install.inc'; - require_once DRUPAL_ROOT . '/includes/file.inc'; require_once DRUPAL_ROOT . '/modules/system/system.install'; // Load module basics. -- GitLab